-
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
Blocks: Adding versioning and migrations to the Block API #3327
Conversation
b354ba4
to
01478f0
Compare
01478f0
to
ed66989
Compare
Codecov Report
@@ Coverage Diff @@
## master #3327 +/- ##
==========================================
+ Coverage 36.93% 37.15% +0.22%
==========================================
Files 268 268
Lines 6676 6701 +25
Branches 1203 1209 +6
==========================================
+ Hits 2466 2490 +24
- Misses 3557 3558 +1
Partials 653 653
Continue to review full report at Codecov.
|
blocks/api/post.pegjs
Outdated
@@ -296,12 +352,19 @@ Block_Name_Part | |||
= $( [a-z][a-z0-9_-]* ) | |||
|
|||
Block_Attributes | |||
= attrs:$("{" (!("}" WS+ """/"? "-->") .)* "}") | |||
= attrs:$("{" (!("}") .)* "}") |
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.
I'd strongly discourage the changes as they are because I think it overcomplicates the parser and it breaks JSON.
The nice thing about the system we were using is that it fits within normal HTML comments without breaking and only imposes the additional constraint that --
be escaped, which would be necessary regardless of JSON since we're already inside an HTML comment.
immediately I can imagine a few alternatives here:
- put the version string before the attributes. this requires few changes and would be quite easy in the grammar spec. the version being prefixed would immediately fail when not present, so we can just add it in before the attributes as an optional value
- store the version inside the attributes - no changes to the parser are even required.
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's worth noting too that the existing code is a massive optimization. We could support a full JSON parse here and not worry about eating all the input until the block header ends, but then we'd have to include a full JSON parser in this grammar. As-is, we do a fast scan to the end then feed in the text to the native JSON parser built in the browser or in PHP which will be magnitudes faster than if we hand-built one.
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 tried something like
Block_Header
= "<!--" WS+ "wp:" blockName:Block_Name WS+ version:(v:Block_Version WS+ {
/** <?php return $v; ?> **/
return $v;
})? attrs:(a:Block_Attributes WS+ {
/** <?php return $a; ?> **/
return a;
})? "-->"
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'version' => $version ? $version : 1,
);
?> **/
return {
blockName: blockName,
attrs: attrs,
version: version
};
}
Similar to what we had before, but it's not working and I'm not sure what I am doing wrong.
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.
Ok! I was able to revert the JSON parsing change by inverting the version and the attributes.
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.
What is problematic about storing the version in the attributes?
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.
Nothing aside the "version" becomes a special attribute and we should guard against its usage in regular attributes.
What's problematic without storing it outside the attributes, since it's not an attribute semantically speaking?
Just to be clear, I'm not against this 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.
It’s a special attribute for sure, though with a stunningly obvious meaning and one we could easily communicate about and guard against (for example, but stripping it before blocks get their attributes).
I’d say the cost is adding complexity to the parser where we already have a system for storing attributes. I’m not sure I’d argue that the version isn’t an attribute.
Of course it’s fine if this is one of those intrinsic things every block has and is duly different than attributes to store it elsewhere, though I’m guessing it only feels different because we already combined separate attributes in the HTML style into the JSON structure in order to be more terse and eliminate escaping-confusion.
How would this be different if attributes had the same kind of key-value pairs in the block header?
/** <?php return $a; ?> **/ | ||
return a; | ||
})? "-->" | ||
= "<!--" WS+ header:Block_Header WS+ "-->" |
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 feels a bit obscure and I'm not sure what value it brings us to pull out the Block_Header
here into its own rule. We're not saving much repetition, and we end up with more code even I think.
Is there a reason why we created this wrapper? The wrapper rule isn't even doing anything but opaquely passing everything through Block_Header
and so it seems to make it harder to figure out what the rule is actually doing. I would hope that as much as is possible we could keep the structure of the rules close to the structure of the document being parsed; in this case maybe that means including some repetitive bits because at a glance we can see how it's supposed to be.
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 a factorization because the same thing is used in Block_Start and Block_Void
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.
Oh yeah it’s obvious what it is; but it’s not clear how this is any better. I was simply saying that to me it appears to obscure the grammar and I don’t find that helpful.
Why is the factoring being done and how does it help someone reading or using it? If all it’s doing is removing one duplication of a few lines and it’s cost is a level of indirection that’s doing nothing then I don’t think that’s a very good bargain
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.
Why is the factoring being done and how does it help someone reading or using it?
I do think it helps people reading and maintaing the grammar. In fact when I implemented the versioning, it was broken for void blocks at first. It's easy to miss the void blocks if you're not aware they exist.
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'd be interested in hearing some additional thoughts on that. To me it makes the grammar more confusing though I can understand where a view might be different than mine.
{ | ||
/** <?php return (int) $v; ?> **/ | ||
return parseInt( v, 10 ); | ||
} |
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 looks like we're wanting to see something like v153840
here - is there a reason not to rely instead on an HTML-like attribute? seems like that could be less surprising to someone seeing it
<!-- wp:block v="1358" /-->
With v1348
it looks like we have an attributes whose name is v1348
and whose value is true
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.
No reason and preference here
// as invalid, or future serialization attempt results in an error | ||
block.originalContent = originalContent; | ||
|
||
return block; |
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 kind of looks like this function is operating some kind of pipeline on the raw blocks: get a block from the parser, see if we have version stuff, see if we have fallback stuff, etc…
Since we're getting deep in the nesting do you think it might be time to consider splitting up that logic into isolated and more easily reasoned-about and testable pieces then turn this function into a composition of those steps?
I'm seeing the start of some kind of recursive operation with an accumulator passing things like [ block, arguments, shouldFallback ]
if {
if {
if {
} else {
}
} else {
// can you quickly tell me under what circumstances I will run?
}
} else {
}
^^^ that's starting to get hard to follow 😉
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.
Yes, I know, it's not easy to break actually. Just take a look at the flow chart linked above. The "reusable" pieces are already outside the function. Do you have a suggestion on how we can improve it? I'm all for it.
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 working on the parser! I've left some recommendations and I hope they don't seem too severe; I would strongly encourage some iteration on this, thinking first of the goal and then if we can accomplish that in different ways.
Also, this isn't specific here, but these fixtures are adding a lot of noise to the PR. Would it be reasonable to undo all fixture changes until the PR is ready for final review so that we can focus on the actual changes we want to enact?
Good stuff!
f27365e
to
545bf16
Compare
I can understand this, I feel the same in several PRs, but I have to admit the fixture test helped me a lot while working on this PR. |
@youknowriad I am reluctant about the approach of adding a version attribute to the comment for the purpose of supporting deprecated sourcing and migrating to current ones. This means eventually all blocks will have version number attributes, creating a convoluted presentation. For the current use cases we have, I'd rather we looked at just handling the old attributes better. Example: deprecatedAttributes: {
caption: {
type: 'array',
source: 'children',
selector: 'footer',
},
} Then the process would look like this:
Another way, suggested before by @mcsf could be: attributes: {
caption: {
type: 'array',
source: 'children',
selector: 'cite',
deprecated: [ { selector: 'footer' } ],
},
} This would allow a compact sequence of multiple deprecated values, used in order. Are there other use cases that would really need a structured versioning system? Is there another approach that doesn't have to include the version in the comment? |
I understand the concern but the proposed solution has flaws IMO:
The only other approach is to recommend to people to not change their blocks and create blocks with different names. It's not sustainable for core blocks though. As much as I understand the concerns, it also adds a bit of complexity to the parser, IMO a structured versioning system is the only reliable way to handle changes to existing blocks. |
I understand this, but it might be alright to do a secondary check for a deprecated attribute if something comes as undefined.
Yes, the example with the array shows how that might work.
What sort of complexity? I think the current state of this PR is also complex. :) What makes me pause is what versioning even means for something that is not yet a block until it is parsed. That is the nature of static blocks, we are basically defining an edit context that consumes from sourced attributes, where variations are really just other possible attributes to source from. The only problem there is the validation approach we have in place, where really just one Maybe we have a way of registering deprecated blocks like |
refs #2541
This PR does not solve all migrations paths. It only addresses the "automatic" migration on post edit strategy.
This is now ready for a first review. For testing purpose, I've upgraded the quote block to use
cite
instead offooter
with a migration.How does it work
This implements the parsing flow described here #2541 (comment)
1
by defaultversion
property and the version is persisted in the block starting commentmigrations
property.attributes
, the oldsave
function and the version to upgrade.Notes
I noticed that saving the quote block with a
cite
and reloading the page adds some undesired<p>
tags which causes the parsing to fail. I wonder if it's something related toautop
that was not causing an issue when it was afooter
tag instead.I added a "version" to the block starting comment, but this revealed an issue with the
Block_Attributes
parser.Todo:
autop
behavior breakingcite
Testing instructions
cite