gutenberg icon indicating copy to clipboard operation
gutenberg copied to clipboard

Try: Use Suspense for site editor

Open tyxla opened this issue 1 year ago • 42 comments

What?

This is an experiment - DO NOT MERGE!

This PR aims to experiment with Suspense, specifically with our useSuspenseSelect() implementation to load the site editor behind the scenes, and display it once it's finished loading.

Why?

We're aiming to find a way to improve the editor loading experience.

As @mtias says:

The end goal should be that things load like a "screenshot"; all fully loaded when you start interacting.

See #35503 for more details and motivation.

How?

We're essentially replacing a bunch of useSelect() with useSuspenseSelect, adding a Suspense boundary with a dummy loading screen, and some artificial delay to prevent from flickering.

This is obviously not landable in this version - we'd likely need a better and more selective approach to which useSelect to alter and which not.

Testing Instructions

  • Load the site editor and observe the loading experience.
  • Observe that at the time the fallback placeholder has been hidden, the editor has fully loaded.

Screenshots or screencast

https://user-images.githubusercontent.com/8436925/179744565-eaa4deb3-6ffe-4185-b3c0-9c16b01a9973.mov

tyxla avatar Jul 19 '22 09:07 tyxla

Size Change: +619 B (0%)

Total Size: 1.25 MB

Filename Size Change
build/block-editor/index.min.js 163 kB +244 B (0%)
build/block-editor/style-rtl.css 15.3 kB +70 B (0%)
build/block-editor/style.css 15.3 kB +70 B (0%)
build/block-library/index.min.js 190 kB +255 B (0%)
build/components/index.min.js 198 kB -49 B (0%)
build/components/style-rtl.css 11.5 kB -14 B (0%)
build/components/style.css 11.5 kB -12 B (0%)
build/data/index.min.js 8.09 kB +25 B (0%)
build/edit-post/index.min.js 30.7 kB +11 B (0%)
build/edit-site/index.min.js 57.7 kB +19 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 7.05 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 126 B
build/block-library/blocks/audio/theme.css 126 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 84 B
build/block-library/blocks/avatar/style.css 84 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 482 B
build/block-library/blocks/button/editor.css 482 B
build/block-library/blocks/button/style-rtl.css 523 B
build/block-library/blocks/button/style.css 523 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 100 B
build/block-library/blocks/categories/style.css 100 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 187 B
build/block-library/blocks/comment-template/style.css 185 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 834 B
build/block-library/blocks/comments/editor.css 832 B
build/block-library/blocks/comments/style-rtl.css 632 B
build/block-library/blocks/comments/style.css 630 B
build/block-library/blocks/cover/editor-rtl.css 605 B
build/block-library/blocks/cover/editor.css 607 B
build/block-library/blocks/cover/style-rtl.css 1.57 kB
build/block-library/blocks/cover/style.css 1.55 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 126 B
build/block-library/blocks/embed/theme.css 126 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 337 B
build/block-library/blocks/group/editor.css 337 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 876 B
build/block-library/blocks/image/editor.css 873 B
build/block-library/blocks/image/style-rtl.css 627 B
build/block-library/blocks/image/style.css 630 B
build/block-library/blocks/image/theme-rtl.css 126 B
build/block-library/blocks/image/theme.css 126 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 507 B
build/block-library/blocks/media-text/style.css 505 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation-submenu/view.min.js 423 B
build/block-library/blocks/navigation/editor-rtl.css 1.99 kB
build/block-library/blocks/navigation/editor.css 2 kB
build/block-library/blocks/navigation/style-rtl.css 2.17 kB
build/block-library/blocks/navigation/style.css 2.16 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 174 B
build/block-library/blocks/paragraph/editor.css 174 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 493 B
build/block-library/blocks/post-comments-form/style.css 493 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 547 B
build/block-library/blocks/post-featured-image/editor.css 545 B
build/block-library/blocks/post-featured-image/style-rtl.css 315 B
build/block-library/blocks/post-featured-image/style.css 315 B
build/block-library/blocks/post-navigation-link/style-rtl.css 153 B
build/block-library/blocks/post-navigation-link/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 282 B
build/block-library/blocks/post-template/style.css 282 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 326 B
build/block-library/blocks/pullquote/style.css 325 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 282 B
build/block-library/blocks/query-pagination/style.css 278 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 439 B
build/block-library/blocks/query/editor.css 439 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 409 B
build/block-library/blocks/search/style.css 406 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 234 B
build/block-library/blocks/separator/style.css 234 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 488 B
build/block-library/blocks/site-logo/editor.css 488 B
build/block-library/blocks/site-logo/style-rtl.css 203 B
build/block-library/blocks/site-logo/style.css 203 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.4 kB
build/block-library/blocks/social-links/style.css 1.39 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 190 B
build/block-library/blocks/table/theme.css 190 B
build/block-library/blocks/tag-cloud/style-rtl.css 239 B
build/block-library/blocks/tag-cloud/style.css 239 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 174 B
build/block-library/blocks/video/style.css 174 B
build/block-library/blocks/video/theme-rtl.css 126 B
build/block-library/blocks/video/theme.css 126 B
build/block-library/common-rtl.css 1.02 kB
build/block-library/common.css 1.02 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11 kB
build/block-library/editor.css 11 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 12.2 kB
build/block-library/style.css 12.2 kB
build/block-library/theme-rtl.css 719 B
build/block-library/theme.css 722 B
build/block-serialization-default-parser/index.min.js 1.1 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 49.6 kB
build/compose/index.min.js 12 kB
build/core-data/index.min.js 15.5 kB
build/customize-widgets/index.min.js 11.3 kB
build/customize-widgets/style-rtl.css 1.38 kB
build/customize-widgets/style.css 1.38 kB
build/data-controls/index.min.js 653 B
build/date/index.min.js 32.1 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.7 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 3.99 kB
build/edit-navigation/style.css 4 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/style-rtl.css 6.94 kB
build/edit-post/style.css 6.94 kB
build/edit-site/style-rtl.css 8.3 kB
build/edit-site/style.css 8.28 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.34 kB
build/edit-widgets/style.css 4.34 kB
build/editor/index.min.js 41.7 kB
build/editor/style-rtl.css 3.66 kB
build/editor/style.css 3.65 kB
build/element/index.min.js 4.68 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 6.75 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.81 kB
build/list-reusable-blocks/index.min.js 1.74 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 953 B
build/nux/index.min.js 2.05 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.3 kB
build/primitives/index.min.js 933 B
build/priority-queue/index.min.js 612 B
build/react-i18n/index.min.js 696 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.74 kB
build/reusable-blocks/index.min.js 2.21 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.4 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.53 kB
build/style-engine/index.min.js 1.53 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.09 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.19 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.19 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

github-actions[bot] avatar Jul 19 '22 10:07 github-actions[bot]

Very excited to see some work on this! :)

I'm unable to explain why, but I am seeing small flashes of content behind the loader:

https://user-images.githubusercontent.com/846565/179752673-1378299b-5f21-4d07-8457-1c11fb2d0e63.mp4

Let me know how I can help debug that.

It's generally not recommended to use a logo for a loading graphic as it can create a negative association with the brand. I'm not sure how much truth there is to that, but it might be best to use something like the Spinner component for now anyways? Hopefully in the future we can use a determinate progress bar.

jameskoster avatar Jul 19 '22 12:07 jameskoster

This is great, thanks for working on it! It'll have a significant impact on the perception of the editor and the fragility waterfalls tend to signal.

mtias avatar Jul 29 '22 09:07 mtias

@tyxla, impressive work so far. It shows the power of new React APIs ❤️

Have you experimented with giving priorities to suspense calls so we could render some more critical chunks of the page faster?

gziolo avatar Aug 02 '22 07:08 gziolo

Thanks for your feedback everyone!

I'm unable to explain why, but I am seeing small flashes of content behind the loader:

Yes, those are easy to reproduce with CPU throttling. Those flashes will generally occur in periods when there are no async requests in flight, and our suspense boundary "thinks" that loading has finished, thus no longer presents us with the fallback. However, if this happens in the middle of the loading, when a new request is issued shortly, the fallback is displayed again. This is a downside of the lack of control we have over the current mechanism, and demonstrates the need to create a centralized mechanism that allows for better granularity and control (cc @griffbrad as we've been discussing that before). I've also demonstrated this in a simpler and isolated way in #42188. Alternatively, we could hack around this by debouncing the rendering with a small delay, however there's no guarantee it'll cover the flickering, because how short (or how long) a period without requests in the middle of the loading experience can vary greatly.

It's generally not recommended to use a logo for a loading graphic as it can create a negative association with the brand. I'm not sure how much truth there is to that, but it might be best to use something like the Spinner component for now anyways?

Of course, this was just for experimentation and demonstration purposes and never meant to land as-is. I've replaced the logo with a spinner component in the meantime if that helps.

Have you experimented with giving priorities to suspense calls so we could render some more critical chunks of the page faster?

No, I haven't. Actually, I'm not sure this makes a lot of sense for a few reasons:

  • As per the discussion in this issue, we'd like to display the page load "as a screenshot", not load section by section. IIUC, that means that we don't load and display certain parts of the page faster; we display it all at the same time, when everything has loaded and is ready for display.
  • How would you decide what to prioritize? To ensure everything will have loaded when finally displaying the editor, we need to asynchronize all requests. At the same time, we don't know what data is going to be necessary, because some of the components that will issue requests haven't still been mounted. Also the data is dynamic, and may (or not) contain various pieces, where priority may vary between different themes and templates. Does that infer the creation of a mechanism to point out the priority? Maybe, but it would add a lot of complexity and I'd really like us to be able to improve the loading experience without requiring extra APIs to be used.

Of course, I might be misunderstanding, so let me know if that's the case.

tyxla avatar Aug 03 '22 07:08 tyxla

It's generally not recommended to use a logo for a loading graphic as it can create a negative association with the brand.

@jameskoster I think this is kind of nonsense :D

As per the discussion in this issue, we'd like to display the page load "as a screenshot", not load section by section. IIUC, that means that we don't load and display certain parts of the page faster; we display it all at the same time, when everything has loaded and is ready for display.

Correct. We might want to identify some structural parts like editor header and navigation bits (W menu) first and display those as soon as ready so you can go elsewhere and not wait for everything to load before navigating.

mtias avatar Aug 03 '22 07:08 mtias

We might want to identify some structural parts like editor header and navigation bits (W menu) first and display those as soon as ready so you can go elsewhere and not wait for everything to load before navigating.

Oh, interesting. I'd love to see some sort of a raw preview or demo of how you envision this working so I could tinker with it.

tyxla avatar Aug 03 '22 12:08 tyxla

@jameskoster I think this is kind of nonsense :D

Maybe :D

I'd love to see some sort of a raw preview or demo

A very rough idea:

load

Note how the W button finishes loading first, then the top bar, then the canvas.

I do wonder about detaching the top bar / canvas though. What happens if you open the Inspector before the canvas is loaded? Or try to add a block via the Inserter? I'm not sure there's any real benefit to loading those tools before the canvas. It might be better to simply load those together. An alternative:

load2

jameskoster avatar Aug 04 '22 09:08 jameskoster

Thanks for the demo!

At a first glance, this would be a bit troublesome with the current interface skeleton structure. Let me clarify.

Currently, we have this rough structure for the site editor:

  • Site Editor app
    • Suspense boundary
      • Editor
        • Interface Skeleton
          • Header
          • Content
          • Sidebar

To achieve what's suggested on the video, we'd have:

  • Site Editor app
    • Editor
      • Interface Skeleton
        • Header
          • Suspense boundary
        • Content
          • Suspense boundary
        • Sidebar
          • Suspense boundary

However, for this to work, we need to have no async suspense select calls outside of suspense boundaries. This is tough, because the Editor and the Header, for example, will share some data dependencies, and we can't (and shouldn't) query those data dependencies both ways.

To be able to do that, we'd need to structurally pull out the header from the interface skeleton, so it would be outside of the Editor, in order for the Editor and the Header to have separate suspense boundaries.

We should be able to get around this with useTransition(), however, this feature is available from React 18, and AFAIK this requires us to also upgrade to React Native 0.69 (https://reactnative.dev/blog/2022/06/21/version-069) but we're still stuck on an older version.

That being said, I wonder what still prevents us from upgrading to React 18 and if we can push that forward. cc @gziolo

tyxla avatar Aug 05 '22 12:08 tyxla

However, for this to work, we need to have no async suspense select calls outside of suspense boundaries.

We also probably won't need to use suspense select everywhere. Maybe start with selectors that fetch entities from the server.

Mamaduka avatar Aug 05 '22 12:08 Mamaduka

@tyxla @jameskoster we don't need anything too complicated at first here. Rendering a very basic skeleton (without pulsing elements) just outlining the header and site icon slot is fine. More important to get the foundation in so we can refine.

mtias avatar Aug 16 '22 16:08 mtias

Here's this PR as things stand:

loader

It's already in a decent spot imo, and potentially mergeable assuming subsequent refinements. If it's easy to try, we could use a slightly larger spinner, and print a very simple indication of where the site logo / top bar will appear:

Screenshot 2022-08-17 at 09 48 27

jameskoster avatar Aug 17 '22 08:08 jameskoster

Yep, that last one is what I had in mind

mtias avatar Aug 17 '22 09:08 mtias

One issue to fix: The loading state should (for now) only be displayed when you first open the Site Editor. At the moment I noticed it pops up when you perform actions like:

  • Click 'Replace' in the Template Part toolbar
  • Open Global Styles
  • Switch to the Templates list (shouldn't this be included in the initial load?)

jameskoster avatar Aug 17 '22 09:08 jameskoster

Thanks for the feedback everyone!

I've done some changes and here's how it looks now:

https://user-images.githubusercontent.com/8436925/185119864-4cf1474e-2816-4073-8b9c-2c0d3cc6c54c.mov

Basically, the header is now outside and since it generally loads quickly and doesn't have a ton of dependencies, I don't see a reason to add suspense to it at this time. We could, of course, do it if we find a good use case that makes it load slowly.

Beyond that, I've moved the boundary for the content and since will take more time to load, we'll see the spinner. I've increased the size of that a bit, just to demonstrate how things look that way.

Let me know what you think!

The loading state should (for now) only be displayed when you first open the Site Editor

I'd be happy to tinker with that, but was curious - what happens when we start interacting with the app and new data gets requested? Is it going to work just like it did before - with placeholders and spinners all over the place?

tyxla avatar Aug 17 '22 13:08 tyxla

It's not a strong opinion, but it's a little strange to have access to things like the Inserter before the document is visible. It might be worth trying a version where the icons/buttons in the top bar are hidden until the document loads.

If you click the W button during the loading state, the menu isn't visible:

Screenshot 2022-08-17 at 15 40 19

jameskoster avatar Aug 17 '22 14:08 jameskoster

Can we get this one through the finish line?

mtias avatar Sep 07 '22 11:09 mtias

I plan to focus on addressing the feedback and getting it to a more shippable state next week.

tyxla avatar Sep 09 '22 10:09 tyxla

I've been playing with this PR, trying to understand what exactly is going on when the Site Editor loads. And discovered some inefficiencies. I noticed that during the editor load, the PostTemplateEdit component rerenders 86 times. That's a lot -- we don't have that many moving parts and network loads that would justify such a high number.

While investigating this, I found the useTaxonomiesInfo hook. This hook is inefficient and causes many wasted rerenders. It's because its useSelect callback returns a different value (taxonomiesInfo) each time it's called, even though the taxonomies data in the underlying store (coreStore) haven't changed at all. That's because the return value is a result of .map.

The result is that every time anything in the coreStore changes, all components calling useTaxonomiesInfo will be forced to rerender.

Moving the .map call outside useSelect is not easy because the .map callback uses getEntityRecords selector for each taxonomy type. A more complex refactoring will be needed.

One further inefficiency is that most of the time we don't use the useTaxonomiesInfo result at all, even though we load it. By default, the Query Loop block has the "Inherit query from template" option turned on, meaning that it doesn't have a custom query to edit. It's only when we're editing a custom query for the block when the taxonomies info is actually used.

This functionality was added by @ntsekouras in #38063.

I suspect the Site Editor load will contain further inefficiencies like this, to be discovered after we fix this one.

jsnajdr avatar Sep 12 '22 07:09 jsnajdr

One area where the Suspense spinner adds some new dynamic is block visibility. There is an intersection observer observing block DOM elements and setting visibility flags in blockEditorStore. That info is then used to switch blocks between sync and async data mode as they scroll in and out of view.

Now visibility is being set also when the spinner is being displayed. The block editor UI is actually in DOM all the time, just hidden with style="display: none !important". There is not just the spinner. As new blocks are being progressively rendered, they are detected as hidden by the intersection observer, and data state updates are triggered.

I don't see any that would be buggy or harmful, just some not exactly expected state activity (and associated rerenders) that's redundant in this context.

jsnajdr avatar Sep 12 '22 10:09 jsnajdr

I'm curious if content-visibility would be a better native mechanism for coming-into-view at some point.

mtias avatar Sep 12 '22 12:09 mtias

It's been already discussed that there is often a flash of content while loading, where the spinner disappears for a while in the middle of the load. I tried to find out how exactly it happens and discovered that it's a combination of classic non-suspense useSelect and the new useSuspenseSelect. Imagine three nested components where the first and third load data with Suspense, while the second, middle one loads traditionally:

function First() {
  const { firstData } = useSuspenseSelect( select => { ... } );
  return <Second data={ firstData } />;
}

function Second() {
  const { secondData, hasResolved } = useSelect( select => { ... } );
  if ( ! hasResolved ) {
    return <LocalSpinner />;
  }
  return <Third data={ secondData } />;
}

function Third() {
  const { thirdData } = useSuspenseSelect( select => { ... } );
  return <div>{ thirdData.text }</div>;
}

Here we have a cascade of three data loads, where each waits one for the previous one to load before it starts. Because a component is not even rendered and mounted before the parent's load finishes. It's an anti-pattern but anyway.

The waiting is implemented either by Suspense throwing and suspending, or by rendering conditionally only after hasResolved becomes true. If all three components used Suspense, the rendering would gradually progress forward, and suspending on each unfinished load. The global spinner would be rendered the entire time.

But here, after the first load finishes, Suspense no longer suspends and renders a "complete" UI with a local spinner inside <Second />. Then <Third /> suspends again and you see the global spinner again. That's exactly the flash.

In practice, the "Page List" block that's part of the default Twenty Twenty-Two template behaves exactly this way:

Screenshot 2022-09-12 at 16 17 37

It loads the page list without Suspense, with its usePageData hook, and then the inner PageList components loads stuff with Suspense, in its useFrontPageId hook. Source: https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/page-list/edit.js

That's how you get the content flash when displaying a page with that block. If usePageData used Suspense, too, the loading experience would be smooth.

jsnajdr avatar Sep 12 '22 14:09 jsnajdr

@tyxla @jsnajdr Thanks a lot for working on this! As @mtias said, we'd love to include it in WP 6.1. In order for that to happen, we should land this PR before Feature Freeze (September 20) -- ideally with a bit of leeway to cherry-pick and publish new npm packages for use by Core.

Do you think that'll be enough time? (Is there any chance you could prioritize it?) Thanks in advance! 😊🙌

ockham avatar Sep 13 '22 14:09 ockham

@mtias @ockham I'm prioritizing this till the end of this week.

tyxla avatar Sep 14 '22 07:09 tyxla

I've spent some time with this today, rebasing it and working on the feedback here. Here's a preview:

https://user-images.githubusercontent.com/8436925/190165333-5b393c44-a819-48e5-a80d-9e45774102e4.mov

Since the last version, now we've addressed all remaining useSelect() instances in the block library to use useSuspenseSelect, which @jsnajdr suggested in an earlier comment as it was causing unexpected flickering. For what it's worth, some of the useSelect usages were introduced after the PR was opened, which is why there were so many of them remaining.

I've also improved the placeholder to use a <Modal /> which allows us to occupy the entire screen with the overlay in order to disallow header interactions - something that came up in feedback by @jameskoster earlier. I've tinkered with the modal styles a bit - removing the overlay background but keeping it to disallow header interactions. I've added a white background to the modal frame and updated it to extend to the entire screen.

Another feature I've tinkered with is displaying the loading placeholder only on initial loading, as suggested by @jameskoster and @mtias. You can see in the preview that the image and page list no longer cause the loading screen to appear. Admittedly, it's in a hacky state, because with Suspense we don't have the luxury to have it display only on the first load; Suspense doesn't care whether it's the first or last load. So this requires some hacks, including runtime interchanging of useSuspenseSelect to work like useSelect after loading, and some artificial delay after loading to conceal the rerender flicker of elements that occurs when we re-render the editor without the suspense boundary.

Something I don't particularly like is that because we use the same blocks in all editors, that means that we need a suspense boundary in all editors, so I've added one to the post editor as well. I'm not particularly happy with this, and one way to get around it might be to trick useSuspenseSelect() to work like useSuspense() for the other editors, as we did after the loading screen gets away.

One downside with the way things are right now is that React Native doesn't have a Modal at all, so we have to reconsider the native implementation for the loading screen. We might want to keep it simple there. For now, I've reverted the suspense changes there, although it would be nice to explore a better loading experience there as well.

Of course, this needs some more love, too. We've added the loading screen context in the data package for the purpose of the experiment, but there will surely be a better place for it. There might be more flickering here and there depending on the theme, templates and blocks used, and unfortunately, there aren't many ways to get around it other than adding artificial timeouts, because right now we don't have a whole lot of control and granularity with Suspense and how it manages the placeholder. We might want to extend Suspense to add some additional control over how and when we display the fallback and the children components.

To summarize, this is still an experiment and is not production-ready, and I doubt we can polish it well soon enough without breaking stuff, so it might be best to postpone it.

I'd love to get some feedback, and I'm happy for y'all to chime in with suggestions and updates to the PR directly. Thanks!

tyxla avatar Sep 14 '22 13:09 tyxla

One major downside is that all blocks, even the 3rd party ones that we don't control, need to use Suspense consistently. Otherwise, when classic and Suspense data loading is mixed together, the loading indicator will flicker.

Maybe we don't need to use Suspense at all. When the editor UI is suspended, it means that the DOM is being incrementally built inside a hidden element (display: none !important), and the fallback spinner is displayed. We could approximately implement the same thing without Suspense: while any data are loading, hide the UI with display or content-visibility, and reveal it only after everything is done.

The biggest difference is running effects. Suspense won't run any effects while the UI is suspended -- it will only build the DOM. A normal hidden div, however, won't prevent effects from running. That could have some unwanted consequences for, e.g., effects that measure DOM dimensions, which are all zero.

jsnajdr avatar Sep 19 '22 12:09 jsnajdr

Hello all, can I get a status update on this one? Given that 6.1 Beta 1 has been released, can I remove this PR from the 6.1 Project Board and remove the Backport Label? @mtias @tyxla

ndiego avatar Sep 21 '22 18:09 ndiego

@ndiego This work is unfortunately still very experimental and not likely to be in 6.1. You can safely remove it from any 6.1 project tracking stuff.

jsnajdr avatar Sep 22 '22 10:09 jsnajdr

Thanks for the clarification @jsnajdr, removing the backport label.

ndiego avatar Sep 26 '22 12:09 ndiego

One major downside is that all blocks, even the 3rd party ones that we don't control, need to use Suspense consistently. Otherwise, when classic and Suspense data loading is mixed together, the loading indicator will flicker.

Indeed. One way around this would be having a mechanism to always decide on runtime whether to use useSelect or useSuspenseSelect, and going with that for all usages that use useSelect as well. That way plugins would also benefit from that improvement. I still think it's worth experimenting with that. It might solve some of the problems created by the current approach we're exploring here.

tyxla avatar Sep 26 '22 13:09 tyxla