-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Asynchronously load TinyMCE (when possible) #23068
Asynchronously load TinyMCE (when possible) #23068
Conversation
This component allows us to wrap existing components and delay them until required 3rd party scripts and styles are asynchronously loaded. It also provides a hook for post-load setup that resolves before the children are rendered, guaranteeing that the children aren't active until the world around them is set up correctly.
Later on we'll be able to expand this pattern generically, but for now we'll keep it focused on TinyMCE.
Meta boxes _may_ depend on TinyMCE already being initialized. Because of this, we cannot utilize asynchronous TinyMCE when any "non backwards compat only" meta boxes are detected for the current post type.
One option would be setting So, I guess the safest solution here is to use the |
We cannot delay loading TinyMCE in this case because when the editor first boots up it renders each block's `edit` component. Because this is expected to be synchronous, the classic block never gets a chance to finish rendering its content and you end up with just the placeholder block.
Thanks for the tip @diegohaz! I took a shot at writing something appropriate, hopefully someone can confirm whether that kind of message is appropriate or if something else would be better. |
Closing in favor of #29681 |
The problem
TinyMCE is a huge dependency that is used rarely in the context of the block editor. Primarily it is used to support the classic block and some meta boxes. In total (TinyMCE core and plugins) it costs 272.274KB in compressed trasferred JS and 1,007.209KB parsed (that's over a MB in parse!). This is a big cost to pay for something that many users will never interact with.
The solution I'm exploring in this PR is to offset loading TinyMCE as often as possible until it is needed. Given certain edge cases and exclusions, this would offset the cost of loading TinyMCE except in the following case, which are the minority of use cases:
Otherwise, when the classic block has not already been added to a post or when custom meta boxes aren't installed, we can offset the TinyMCE dependency until the block's
edit
is run. Indeed, in this case, if someone never uses the classic block, they will not pay the penalty for having it.There was an existing draft PR for asynchronously loading TinyMCE that built upon an existing PR in Gutenberg that introduced a REST API for retrieving a list of dependencies. This REST API isn't technically necessary for asynchronously loading TinyMCE for the classic block, it was included in the draft PR as a way of looking forward to enabling similar async behavior in other blocks.
However, the REST API itself presents some complexities and concerns:
WP_Scripts
).Because we can get the URL for TinyMCE without using a REST API (partially it's already available in the
tinyMCEPreInit
object but this won't be sufficient as I describe below) I think we can shortcut getting a win against TinyMCE around the REST API.To accomplish this async TinyMCE project, I propose the following:
LazyLoadTinyMCE
component to wrap theClassicEdit
component.LazyLoadTinyMCE
component is basically a TinyMCE specific version of the the solution described in this PR.TinyMCE URLs
There is one caveat about using
tinyMCEPreInit.baseURL
is that we need to decide which TinyMCE script to use as different scripts are used for different environments and compression settings. I’m not totally sure how to get this information to the frontend. One potential solution is to use$wp_scripts->registered['wp-tinymce']->deps
and then follow a strategy similar toget_url
in the REST API PR to retrieve the URI for thewp-tinymce
scripts and then add those to an array the frontend can use. Adding functionality likeget_url
toWP_Scripts
directly would be good for making the functionality available generally. It could alternatively be added as a utility function in Gutenberg's plugin, but in any case, it would need to be available outside the REST API.Stopping WordPress from equeueing TinyMCE
Along with that, we also need to be enable Gutenberg to prevent core from enqueuing the TinyMCE scripts. To do that we ought to move the printing of the editor scripts into an action that can be overwritten by the plugin. This trac ticket proposes that change: https://core.trac.wordpress.org/ticket/49964
Edge cases/exclusions
Meta boxes
As stated above, there are some exceptions to when we can do this. Meta boxes present a backwards compatability issue as some of them depend directly on TinyMCE. Meta box development hasn’t stopped and widely used plugins like Advanced Custom Fields depend on them. Preserving the ability for meta boxes that depend on TinyMCE to continue to work is part of the work for v1. We’ll do this by disabling async TinyMCE when we detect that custom meta boxes are being used.
From what I understand at the moment
edit-form-blocks.php
enqueues the editor scripts before processing metabox data. Ideally we would like to have already run through processing meta boxes before we render scripts for the editor so that we have some basis for deciding whether meta boxes are really being used.register_and_do_post_meta_boxes
takes care of processing meta boxes for a post. I think we can move this logic before the call towp_enqueue_editor
inedit-form-blocks.php
without consequence and then look into theglobal $wp_meta_boxes
in the action we'll add in Gutenberg to override TinyMCE script printing.When the classic block is already used on a post
When a post is first loaded into the block editor, the
edit
for each block gets run. This means that when a classic block exists on a post, TinyMCE will be needed immediately on page load. Asynchronously loading TinyMCE here isn't possible because we need to render the block's contents into the editor. I think we should solve this by cutting this from the scope of the async TinyMCE project and instead solve it as part of the wider project to async all block dependencies whenever possible (if indeed it is a solvable problem).We'll need to introspect into a posts
post_content
and decide whether we think the classic block is being used. There already exists a functionhas_blocks
, however it relies on the block's boundaries being renderedinto thepost_content
and unfortunately, the classic block doesn't render block markup comments. So the only way I can think of right now to do it is to run the post throughparse_blocks
and then walk the block tree and see if acore/freeform
block exists.Summary of changes
To summarize, we need to make changes to WordPress core and Gutenberg.
LazyLoadTinyMCE
once the dependency is loaded.LazyLoadTinyMCE
.Completing the above will deliver a significant decrease in JavaScript download and parse times for most Gutenberg users.
Alternatives
We could move TinyMCE into the block directory, however then every post would pay the cost of the dependency, regardless of whether it was going to be used, so I don't think this is an adequate solution.
Alternatively, we could move it into the block directory and then have the block directory offset loading a block's dependencies until it is edited... but that's just the other more general solution documented in #2768.
What this PR actually does
This PR adds a
LazyLoadTinyMCE
component that wraps the classic block. This component delays the rendering of the classic block's edit component until TinyMCE has been loaded.The PR depends on changes proposed in WordPress/wordpress-develop#232.
On a clean WordPress and Gutenberg install, this offsets approximately 272.274KB in transfer and 1,007.209KB parsed (that's over a MB in parse!) after TinyMCE itself and all the various plugins.
How has this been tested?
Using a WP installation running the changes in WordPress/wordpress-develop#232:
window.tinymce
exists__back_compat_meta_box
in its args (using ACF for example) for a specific post type. Edit and create a new post of that post type and verify that TinyMCE is loaded on page load and not re-loaded when a classic block is added or edited.Types of changes
This is a new feature. The only user-facing change in behavior will be a delay between when a classic block is "edited" and when it is actually "editable".
Checklist: