-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Block and Component Lazy Loading #2768
Comments
We can probably get some big wins by lazily loading in the assets that are queued when we call:
|
Yes, this seems more and important to get in place. |
Two outstanding issues to bear in mind when we eventually revisit how asynchronous components are handled in Gutenberg:
|
I've been thinking about this a little and thought I'd dump some of the contents of my brain. It would be good to get the conversation going. I recall that @youknowriad was mulling over some ideas in this area at WordCamp Europe. There's two things we're talking about in this issue:
Gutenberg assetsTo do this, we can make use of dynamic imports and the import( /* webpackChunkName: "wp-block-gallery" */ './gallery' ).then( ( { name, settings } ) => {
registerBlockType( name, settings );
} ); Here, the code in WordPress assetsThis is trickier. One idea is that we could write our own webpack loader which lets you specify a list of WordPress script or style assets that you wish to load. import 'wp-script-loader!wp-codemirror,code-editor,htmlhint,csslint,jshint,htmlhint-kses';
import 'wp-style-loader!wp-codemirror,code-editor'; The webpack loader would return a script that, when executed by the browser, inserts a We can then combine this technique with the Promise.all( [
import( /* webpackMode: "eager" */ 'wp-script-loader!wp-codemirror,code-editor,htmlhint,csslint,jshint,htmlhint-kses' ),
import( /* webpackMode: "eager" */ 'wp-style-loader!wp-codemirror,code-editor' ),
] ).then( () => {
wp.codeEditor.initialize( textarea, settings );
} ); Here, the As cool as this is, it's not clear to me that it's a better approach than e.g. writing a |
thanks for the discussion @noisysocks one idea this leads me to is a realization that we have some resources we need as soon as possible and some that can load later. for example we want to know the name, category, icon, and other meta details about a block so that we can do things like load the block inserter. however, since some blocks are complicated they might only need to load on first use (which could be a preview in the block inserter or could be in the editor itself). on that note it also seems reasonable that we could encourage coding blocks that have dynamically loaded elements. this could actually solve the dilemma while leaving open the door that some plugins will abuse it by loading everything up front. registerBlockType( 'my-cool-block', {
title: __( 'My Cool Block' ),
attributes: [ … ],
save: dynamicLoad( './my-cool-save' ),
edit: dynamicLoad( './my-cool-edit' ),
} ); with this we can provide a generic loading view for components which are loading but immediately replace them once loaded. behind the scenes we use |
I am putting the future label on this, but if it's something that would be really beneficial for 5.0, please move it into the appropriate milestone. |
It’s going to be essential for blocks discovery at some point, 5.1 should be fine as target. |
Bringing comment from @ockham shared in #12232:
|
it's also worth discussing the implications of our editing flow and our block invalidation flow. until our editing flow is asynchronous we might still run into pretty massive issues trying to make one part of it asynchronous. in #7970 I proposed an asynchronous parsing flow which @aduth helped me with but we ran into the same issue that the entire stack is built upon a synchronous model. the issue of the editor invalidating blocks between unregistering on and re-registering highlights the problem well. it would be really helpful for us to discuss the semantics of an asynchronous editing session and nail down some common vocabulary before hacking away at it - actually I'm sure this is something we should defer until later and not try to get in before "the merge" or even shortly afterwards. the implications here of moving to an asynchronous model are numerous:
in the short-term for block registration we might be able to get away with creating a type Block
= Pending BlockInfo
| Loaded BlockInfo
type Document
= Uninitialized
| Parsing (Stream [Block])
| Parsed [Block]
type Editor
= Uninitialized
| LoadingCoreEditor
| Pending Document
| Running Document Practically some of these states will connote things like "I can or cannot edit this block right now" and "we should wait to invalidate the block content until it's ready" and "the editor itself is changing to stop all input processing momentarily" etc… |
So I think that |
@youknowriad Do you think it would be worthwhile to revisit your original solution over the one I've proposed? Given what you know of each approach, do you have a sense of whether one will be better off long term? Trying to make sure my energy in this is spent wisely. I've been thinking more over the weekend and I think if I continue down the path I'm proposing I would go down the strategy pattern path and implement a The only problem with this is that I'm not sure the static analysis required to make something like the |
I know it's not helpful but the answer is really I don't know. NPM registry supposes that everything just works out of the box, which means the dependencies are static there That said, I do wonder how important is it for us to support these use-cases. For WordPress, it is definitely not important but are we ok giving up on tools like asblocks using core blocks, drupal Gutenberg using core blocks... |
Agreed. I'll take some time this week to revisit your previous attempt and try to revive it. One thing to note though is that the classic block currently depends silently and heavily on WordPress specific |
Yes, there are some blocks that are very WP-dependent, and Classic block is one of them. At some point we should try to separate these blocks from generic ones. |
The React core team recently shared their top-level overview of how SSR would look like in React 18: reactwg/react-18#37 It might worth a read, and be taken into consideration when designing our async loading strategy. |
hi @kevin940726 - thanks for the link. was there something specific in there that you thought was important for this issue? |
Nothing specific, it's just a reminder 😅 . since SSR in React will get a significant update, I just want to make sure that we're leveraging the best practices and not re-inventing the wheels. :) |
Thanks for sharing @kevin940726 - I don't think these two systems will overlap much. I'm not even sure there's any likely scenario in which we would apply SSR to this stage of Gutenberg loading, or if that would be practical/possible/beneficial. |
It's not only related to SSR though, React 18 will also include Concurrent Rendering and Suspense etc. These are not separate features though, they are related to each other and we should pay attention to how we integrate them into Gutenberg. For instance, we should really use That said, it's just a reminder. I don't know how much it applies to our architecture yet. But let's just keep an eye on it. Maybe it doesn't have anything to do with this issue, then we can just ignore my comments 😛. |
Indeed, @gziolo, although that PR still needs some polishing and exploration. It is indeed meant to be a very simple way to deal with the async loading of blocks, likely serving temporarily until we have a better solution. I think we still should explore using JS modules and import maps as a more robust way to manage scripts and styles through a better long-term solution for WordPress as a whole. |
@jsnajdr is experimenting with async block loading in this PR and has kicked off a discussion about the approaches and decisions there - it could definitely use some additional eyes and feedback. |
Update@westonruter suggested using a different phrase than "async loading" in #53260 (comment). We concluded that "Lazy Loading" fits best and aligns with how people refer to it in the JavaScript ecosystem. I updated the title to reflect that. Last week, @jsnajdr landed #53807 with refactoring to the block registration as a preparation for the follow-up work to allow lazy loading parts of the block definition. I started looking in WordPress/wordpress-develop#5118 at ways in WordPress Core to automate consuming shared scripts generated in the build pipeline. My main motivation was to figure out a way to fit the concepts of code splitting (runtime chunk, split chunks, dynamic imports) into the existing API shaped around |
Tasks List
Issue Overview
Initially raised by @mtias in #665 and something that @dmsnell has already started exploring when working on CodeMirror block. It would be beneficial to have a standardized way to defer loading optional parts of the block and/or the whole block in general when it is resource heavy. I might even risk saying that it would make sense to have all external blocks by default loaded on demand using chunk splitting technique. It would help us ensure that only essential parts of Gutenberg are loaded on initial page load, which would lead to better performance and overall first-time impression. We could start pre-loading optional blocks when the browser is idle or whenever a user is about to select such block using the drop-down menu or
/
command. All that should help scale Gutenberg as the number of available blocks grows.Related Issues, PRs and discussions
The text was updated successfully, but these errors were encountered: