gutenberg
gutenberg copied to clipboard
Try: Use Suspense for site editor
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
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 |
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.
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.
@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?
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.
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.
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.
@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:
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:
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
- Interface Skeleton
- Editor
- Suspense boundary
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
- Header
- Interface Skeleton
- Editor
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
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.
@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.
Here's this PR as things stand:
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](https://user-images.githubusercontent.com/846565/185076579-4b89f9df-da98-4253-8be7-b7042c3b2dde.png)
Yep, that last one is what I had in mind
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?)
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?
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](https://user-images.githubusercontent.com/846565/185166540-670bacea-e922-4606-bc09-1b8de35cfb3d.png)
Can we get this one through the finish line?
I plan to focus on addressing the feedback and getting it to a more shippable state next week.
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.
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.
I'm curious if content-visibility
would be a better native mechanism for coming-into-view at some point.
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](https://user-images.githubusercontent.com/664258/189677756-4709c735-76d8-4af8-a66a-7435f99200b9.png)
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.
@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! 😊🙌
@mtias @ockham I'm prioritizing this till the end of this week.
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!
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.
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 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.
Thanks for the clarification @jsnajdr, removing the backport label.
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.