-
Notifications
You must be signed in to change notification settings - Fork 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
Gutenberg: make sure that editor initialization is executed only once #28302
Conversation
90eb78e
to
19a65aa
Compare
Previously the editor initialization fucntions were executed whenever we navigated away and returned back from it. This resulted in unwanted consequences like trying to register the same blocks twice and applying the same middleware two times.
Now that we have an initialization function that is guaranteed to execute only once, we can move the API middleware initialization into it too.
84d87c7
to
ed44754
Compare
We want to avoid putting any Calypso custom code into edit-post folder. New editor initialization function seems like a better place to do it.
ed44754
to
b328877
Compare
|
||
// We need to ensure that his function is executed only once to avoid duplicate | ||
// block registration, API middleware application etc. | ||
export const initGutenberg = once( ( userId, siteSlug ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
once
is not applied unique per argument set. The function will be called once ever.
How then do we ensure applyAPIMiddleware( siteSlug )
is called if I edit a post for a second site after I'd already initialized it once before for a first site?
Instead you might want to use a memoization utility capable of handling multiple argument. Lodash's does not. Gutenberg uses (my) memize to this effect.
⇒ n_
n_ > const initGutenberg = ( userId, siteSlug ) => console.log( 'called with', userId, siteSlug );
undefined
n_ > const initGutenbergWithOnce = _.once( initGutenberg );
undefined
n_ > initGutenbergWithOnce( 1, 'example1.wordpress.com' )
called with 1 example1.wordpress.com
undefined
n_ > initGutenbergWithOnce( 1, 'example2.wordpress.com' )
undefined
n_ > const initGutenbergWithLodashMemoize = _.memoize( initGutenberg );
undefined
n_ > initGutenbergWithLodashMemoize( 1, 'example1.wordpress.com' )
called with 1 example1.wordpress.com
undefined
n_ > initGutenbergWithLodashMemoize( 1, 'example2.wordpress.com' )
undefined
n_ > const initGutenbergWithMemize = require( 'memize' )( initGutenberg );
undefined
n_ > initGutenbergWithMemize( 1, 'example1.wordpress.com' )
called with 1 example1.wordpress.com
undefined
n_ > initGutenbergWithMemize( 1, 'example2.wordpress.com' )
called with 1 example2.wordpress.com
undefined
Or, if the rest of the code is not meant to / cannot be run, save for the API middleware application, maybe it ought to be pulled out and applied separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initGutenberg
function is a mix of initialization tasks we really want to do just once per app lifetime (like various require
s and module inits) and other tasks we want to do per site or even per editor instance.
The siteSlug
here is used to rewrite the URLs of API requests and point them to the per-site WP.com endpoints. once
means that these request will be made to the first site visited, forever, even after site switch. We can rewrite that to a callback function:
() => getSelectedSiteSlug( store.getState() );
Then each request will use the the correct site ID, but will still write the results to the singleton core/data
store, mixing results from different sites. Not exactly an improvement. Memoizing the init function better won't help either, as long as the data stores remain global singletons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @aduth! I haven't considered that while refactoring the initialization.
Or, if the rest of the code is not meant to / cannot be run, save for the API middleware application, maybe it ought to be pulled out and applied separately.
I think it's fine to run the rest only once - for example, we don't want to attempt to register core blocks twice (would generate warnings) etc. So it looks like we should only pull out API middleware application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then each request will use the the correct site ID, but will still write the results to the singleton core/data store, mixing results from different sites. Not exactly an improvement. Memoizing the init function better won't help either, as long as the data stores remain global singletons.
@jsnajdr I'm not sure how to resolve this, given the way core data stores and packages are currently working. Do you think that extending this PR by @Copons to reinitialize the editor on each site switch could work? - #29310
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the combination of the following two changes should work:
- Reset the
core
store on each site switch.core
is currently not among the resettable stores in Gutenlypso: Reset part of the Gutenberg stores when navigating to the editor #29310 and needs reset only on site switch, not on each editor load. - Instead of passing a constant
siteSlug
to thewpcomPathMappingMiddleware
, pass agetSiteSlug
callback that returns the latestsiteSlug
on every call. That makes the middleware map to a different path on a site switch.
If you end up wpcomPathMappingMiddleware
, I propose the following API change: instead of this:
wpcomPathMappingMiddleware = ( options, next, getSiteSlug ) => ...
use( partialRight( wpcomPathMappingMiddleware, getSiteSlug ) );
do this:
wpcomPathMappingMiddleware = getSiteSlug => ( options, next ) => ...
use( wpcomPathMappingMiddleware( getSiteSlug ) );
(if you agree it looks like an improvement)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing a constant siteSlug to the wpcomPathMappingMiddleware, pass a getSiteSlug callback that returns the latest siteSlug on every call. That makes the middleware map to a different path on a site switch.
Landed this in #29368. I'm going to look into point 1 next.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be 😄
Fixes #27882 and #27993
Changes proposed in this Pull Request
The problem with our current editor implementation is that whenever we navigate away from it (by clicking
Close
) and then navigate back, all of the initialization logic will be executed again. That means that API middleware will be applied twice, core blocks registration will be attempted again (producing warnings) etc.I discussed this with @Automattic/team-calypso in #27882 and @jsnajdr proposed two options:
client/gutenberg/editor/index.js
.post
controller wrapped withlodash/once
.After experimenting a bit with both approaches, I decided to go with option 2. It seems more readable and easier to reason about to me, because all of the setup logic and sequencing is in one place. We also don't have to pull in
redux-bridge
for it.Here is the summary of what has been moved to new initialization function:
main.jsx
- This includes: data plugins registration,core-data
init, API middleware setup, Classic Block init, Core Blocks registration, and TinyMCE plugins init.edit-post
folder, and we'd like to avoid adding Calypso's custom modifications there as much as possible. Maintaining it is hard enough with frequent core changes, and adding our custom code there only makes it harder. We are working to move away from it as soon as possible, but it's not an immediate priority.Testing instructions
localStorage.setItem( 'debug', 'calypso:gutenberg' );
Close
.Back
in your browser (at this point there is a similar visual glitch that we observed in Calypsoify redirects, not related to this PR and present in wpcalypso too).wpcalypso
you'd see repeated core blocks registration warnings.