CARVIEW |
Navigation Menu
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Refactor: Content only template locking block editing modes to reducer #67606
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
Refactor: Content only template locking block editing modes to reducer #67606
Conversation
Size Change: +133 B (+0.01%) Total Size: 1.93 MB
ℹ️ View Unchanged
|
Flaky tests detected in 1d93ad3. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/17542532945
|
f3b59c2
to
6022b63
Compare
// If the editor *is* in navigation mode, the block editing mode states | ||
// are stored in the derivedNavModeBlockEditingModes map. | ||
if ( isNavMode && state.derivedNavModeBlockEditingModes?.has( clientId ) ) { | ||
return state.derivedNavModeBlockEditingModes.get( clientId ); | ||
} |
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 we only have if ( isNavMode )
here so it will return contentOnly
as a fallback if state.derivedNavModeBlockEditingModes?.has( clientId )
is false? I'm concerned about edge cases where an item isn't in the derivedNavModeBlockEditingModes
for some reason, and this ends up returning default
.
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 guess there are no cases where default
is used in Write Mode?
If so, I think what you suggest is probably a good idea, though perhaps disabled
as the fallback instead of contentOnly
.
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.
Lets handle it in a separate PR given the code for this is already in trunk.
const rootClientId = getBlockRootClientId( state, clientId ); | ||
const templateLock = getTemplateLock( state, rootClientId ); | ||
// If the parent of the block is contentOnly locked, check whether it's a content block. | ||
if ( templateLock === 'contentOnly' ) { | ||
const name = getBlockName( state, clientId ); | ||
const { hasContentRoleAttribute } = unlock( | ||
select( blocksStore ) | ||
); | ||
const isContent = hasContentRoleAttribute( name ); | ||
return isContent ? 'contentOnly' : 'disabled'; | ||
} | ||
return 'default'; | ||
} |
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 really like the refactor to move this logic to the reducer rather than spreading it out within the getter.
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.
Just so I understand, is the selling point that previously, every call to getBlockEditingMode would traverse the entire block tree and recalculate. Now it's computed once per state change?
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 history is that some time ago Synced Patterns didn't work properly in Write Mode.
I tried fixing that by adding extra code to the getBlockEditingMode
selector, but it caused a huge performance regression (#65408 (comment)).
I had to buy some more performance in order to be able to fix that bug, and so I've done that by moving code from the selector.
I think the code is still far from good, but it's a tough problem to solve, and I think it's reflective of how complicated the features that use blockEditingModes are. We should try to simplify both the features and the code where we can.
What do you think @talldan? Is this still something that can be achieved for 6.9? Thanks! 🍺 |
6022b63
to
516ead0
Compare
…ocking Move contentOnly template locking code from selector to reducer Simplify getBlockEditingMode tests and add additional cases Remove unused dependencies WIP
516ead0
to
e32f963
Compare
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.
This is awesome, thanks for reviving it, @talldan
It's working as described for me.
Pluses I see:
- separation of concerns
- great test coverage
Is there another way you'd like folks to test?
Do you think it'd help to have documentation around this somewhere?
) { | ||
return state.derivedBlockEditingModes.get( clientId ); | ||
} | ||
const isNavMode = isNavigationMode( state ); |
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.
It is related and not. Write Mode has now taken that tool's place, but the API used is still the same. So navigation mode === write mode.
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.
My brain hurts.
const rootClientId = getBlockRootClientId( state, clientId ); | ||
const templateLock = getTemplateLock( state, rootClientId ); | ||
// If the parent of the block is contentOnly locked, check whether it's a content block. | ||
if ( templateLock === 'contentOnly' ) { | ||
const name = getBlockName( state, clientId ); | ||
const { hasContentRoleAttribute } = unlock( | ||
select( blocksStore ) | ||
); | ||
const isContent = hasContentRoleAttribute( name ); | ||
return isContent ? 'contentOnly' : 'disabled'; | ||
} | ||
return 'default'; | ||
} |
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.
Just so I understand, is the selling point that previously, every call to getBlockEditingMode would traverse the entire block tree and recalculate. Now it's computed once per state change?
break; | ||
} | ||
|
||
const nextDerivedBlockEditingModes = |
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.
Do you think it's worth adding performance tests later?
I could be totally barking up the wrong tree, but let's say we have a document with 10,000 blocks, does this mean we're storing 10,000 block editing modes entries in memory?
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 did a very crude js heap comparision using window.performance.memory
and I couldn't see any memory degradation comparing trunk with this PR based on a derivedNavModeBlockEditingModes
count of 40. Total heap size hovered around 200-280 MB
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.
We do already have performance tests in CI, but then I don't think they test these modes.
I could be totally barking up the wrong tree, but let's say we have a document with 10,000 blocks, does this mean we're storing 10,000 block editing modes entries in memory?
Not really, not every block gets a block editing mode. Only contentOnly
or disabled
blocks are given one. Most of the time blocks will just be default
and then they have no mode set.
But anyway, if we have 10,000 blocks in the post, we'd be storing other state for those blocks, so why not a block editing mode? It is only a short string.
I think the issue is more about whether we update 10,000 blocks on every state change, and we don't.
Here's some of the performance related checks the reducer does:
- Only updates subtrees of blocks - e.g. if a synced pattern is inserted only that pattern and its children have block editing modes recomputed
- Takes care to only update on particular actions - e.g. insert blocks, remove blocks, changing templateLock and others
- Avoids unnecessary store updates as much as possible - e.g. don't replace objects with new references if the data in those objects hasn't changed. This will prevent react renders that don't do anything.
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.
Thanks for the explainer! 🙇🏻
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
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've been testing this on both blocks with "templateLock":"contentOnly" and in write mode (and both together) and behaviour matches trunk, so looks good from that side!
I'm not super knowledgeable about these parts of the code but the changes seem alright too.
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've tested this across the given scenarios, and can't find any regressions.
Let's get it in and see how it fares.
Thanks again for all the work on this.
WordPress#67606) * Update derived block editing modes to support content only template locking Move contentOnly template locking code from selector to reducer Simplify getBlockEditingMode tests and add additional cases Remove unused dependencies ---- Co-authored-by: talldan <talldanwp@git.wordpress.org> Co-authored-by: jeryj <jeryj@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org> WIP * Remove defunct code * Fix `getEnabledBlockParents` test * Fix test for explicitly set blockEditingMode
What?
Follow-up to #67026.
Fixes #71551
Refactors some of how
templateLock: contentOnly
works.Why?
Unlike other
templateLock
options,templateLock: contentOnly
uses theblockEditingMode
API internally. In trunk, these block editing modes are calculated in a selector.Selectors can be called, so expensive selectors isn't a good idea. Usually the solution is memoization, but that doesn't work for all cases, like when there are a lot of dependencies or complex dependencies for the memoization.
The option chosen to resolve this in #67026 was to move the calculation of block editing modes to the block editor reducer instead.
#67026 did this for Write Mode and synced patterns. This PR does the same for
templateLock: contentOnly
How?
The code ends up looking quite different because of the different natures of selectors and reducers.
Testing Instructions
Everything should work the same as
trunk
.Here's some block markup to test
templateLock: contentOnly
with:Other things to test:
Screenshots or screencast
TBC