-
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
Editor: Reshape editor state for optimized and accurate dirtiness detection #10844
Conversation
export function hasChangedContent( state ) { | ||
return ( | ||
state.editor.present.blocks.isDirty || | ||
'content' in state.editor.present.edits |
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 don't really get what this second condition is checking for.
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 don't really get what this second condition is checking for.
I'll add a comment to clarify, but it's a combination of two things which require this consideration:
- While content in Visual mode is represented by the blocks state, in Text mode it is tracked by
edits.content
. edits
is intended to contain only the different property values, so it is expected that the mere presence of any property is an indicator that the value is different than what is known to be saved.
resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ], | ||
ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ], | ||
} ), | ||
] )( { |
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.
Is everything below here just indented? It's a bit hard to see the code changes, if there are any.
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.
Funny that this Higher order reducer moved from "edits" to just "blocks". It almost feels like we need an editBlocks
:)
If we're swapping something from |
To be clear, we're not swapping it because of performance, we're swapping it to work around the errors which are logged by the shape validation in the Redux implementation, triggered by the main work here in using To your question, is the point that we should bring it in rather than rely on it as a third-party dependency? I don't really feel strongly one way or the other. I implemented it as a separate project largely to disable the availability of transpilation, for performances' sake (it's a weekend side hack project, something I felt fine to "waste" time over-optimizing). |
8067549
to
3f2afc1
Compare
I've rebased and pushed a number of revisions, including updated unit tests. There's failing end-to-end tests which are legitimate, and which highlight an issue I've dealt with previously: We can't naively consider emptiness of I'll need to think on this some more. Related: #9403 |
3f2afc1
to
b81f0e2
Compare
b81f0e2
to
5755c05
Compare
5755c05
to
d628647
Compare
This has been rebased and is now passing. The changes are still substantial, and I've extracted at least one more commit out (the initial edits refactoring, to #11267). It was originally developed separately, but cherry-picked into this branch to verify its viability for addressing the remaining E2E test failures. |
d628647
to
05c40dd
Compare
05c40dd
to
679c79d
Compare
#11315 has been separately merged and this one rebased in response, reducing from +1,556 −1,044 changes to +550 −242. |
9fc77bb
to
804a206
Compare
#11267 has been separately merged and this one rebased in response, reducing from +660 -243 changes to +261 -111. I'm not planning on extracting any other separate pull requests. This one should be considered ready for final review as-is. |
return true; | ||
} | ||
|
||
return inSomeHistory( state, isEditedPostDirty ); |
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.
So if I understand properly. We conder a post dirty until it's completely saved (successful response)? right? any particular reason? should we clarify why in a comment? (I know it's not new in this PR though)
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.
So if I understand properly. We conder a post dirty until it's completely saved (successful response)? right? any particular reason? should we clarify why in a comment? (I know it's not new in this PR though)
It's because change detection (now only for blocks) is reset at the start of the save, but we still want to avoid allowing a user to reload while that request is still pending.
It's covered in this test:
gutenberg/test/e2e/specs/change-detection.test.js
Lines 216 to 230 in 26ccc15
it( 'Should prompt if changes and save is in-flight', async () => { | |
await page.type( '.editor-post-title__input', 'Hello World' ); | |
// Hold the posts request so we don't deal with race conditions of the | |
// save completing early. Other requests should be allowed to continue, | |
// for example the page reload test. | |
await interceptSave(); | |
// Keyboard shortcut Ctrl+S save. | |
await pressWithModifier( META_KEY, 'S' ); | |
await releaseSaveIntercept(); | |
await assertIsDirty( true ); | |
} ); |
I'll see if I can form a useful comment here.
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'll see if I can form a useful comment here.
Added in rebased fd7a8ec.
// flag in place of content field comparison against the known autosave. | ||
// This is not strictly accurate, and relies on a tolerance toward autosave | ||
// request failures for unnecessary saves. | ||
if ( hasChangedContent( 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.
I guess this is the fix for the performance issue.
It might not be easy but I wonder if we can add an e2e test checking that we don't run save
functions as we type :)
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 might not be easy but I wonder if we can add an e2e test checking that we don't run
save
functions as we type :)
Hmm, might not be too hard! I'll give it a shot.
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.
LGTM 👍
804a206
to
aa36d29
Compare
Closes #10427
Closes #7409
This pull request seeks to refactor editor state to to enable a more sensible, accurate, and performant dirty detection routine. Rather than representing
isDirty
as a reflection of the entirety of thestate.editor
state object, it's now been moved to only handle the newstate.editor.blocks
, which containsbyClientId
andorder
(previouslystate.editor.blocksByClientId
andstate.editor.blockOrder
). Dirty detection for fields other than content are reflected by the presence of any keys instate.editor.edits
.This helps to support:
isEditedPostAutosaveable
(which, as noted in Performance: save components are called for *every* block on *every* keystroke #10427, became very negatively impactful)Previous attempts to implement this had been unsuccessful in that
withChangeDetection
adds a property to the combined reducer which was incompatible with Redux'scombineReducers
(logging an error to the console). To work around this, thecombineReducers
exported from@wordpress/data
was swapped with an in-place-compatible, more-performant variationturbo-combine-reducers
(authored by yours truly).Testing Instructions:
Verify that there is no regressions in the behavior of autosave, namely that it occurs after delay if and only if one of content, title, or excerpt is updated.
Conversely, verify that if a field other than the three aforementioned is updated (e.g. categories added or removed), it is reflected by the UI as needing save ("Save Draft" becomes active). An autosave should not occur, but you should be prompted about unsaved changes if you attempt to leave the page before pressing "Save Draft".
Finally, verify that if changing both one of content, title, excerpt and another field, that the autosave does occur, and once completed, the editor is still in a "dirty" state ("Save Changes" present, after brief delay once save finishes). This is because autosaves do not include other fields.