-
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
Prototype: Server-side block attributes sourcing #18414
Conversation
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.
Impressive!
Is it performant enough?
Are DOMDocument and the PHP Selector utilities resilient enough, and do they account for modern HTML and CSS?
Performance is the first thing one wonders about. To answer both those questions, there's probably nothing like getting this in the hands of testers. To that effect: we've been relatively liberal at pushing experimental client-side interfaces and know how to approach things there; how could we do the same here?
Resurfacing #7342, as it's in the same domain and something we could easily tackle now. |
I think a safe route (albeit perhaps more labor-intensive) would be to first consider extracting some of the "additional" prerequisite work that I had to bundle into this pull request:
The first and last of these would complement ongoing work in Trac#47620 (cc @gziolo, @spacedmonkey), since while the endpoint proposed there would expose registered blocks, it's currently the case that very few core blocks are actually registered on the server. It would be good to have some feedback from REST API folks as well, at least so far as how we expose this data through that interface. It might be a task worth considering separate from how the attributes actually become populated during the parse. I can plan to bring it up in their weekly meeting this upcoming Thursday. Lastly, while the plugin was a nice venue for quick prototyping, and we may be able to merge it in some experimental form, moving forward we might want to develop in Trac, or at least develop there some additional hooks necessary for a more solid solution. That this implementation replaces the default parser may not be how we ultimately want to go about doing this (e.g. perhaps we want a more "pure" HTML parser result from One specific task which otherwise limits the effectiveness of this in the plugin is a means to filter block registration settings on the server. Currently this is not possible, and in this implementation I manually apply this by re-registering the core set of blocks. |
@$document->loadHTML( '<html><body>' . $block['innerHTML'] . '</body></html>' ); | ||
} catch ( Exception $e ) { | ||
return null; | ||
} |
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.
when I have worked with DOMDocument
I found it extremely helpful to extract the setup code into a separate function that abstracts away the noisy quirks.
$document = parseHTML( $block['innerHTML'] );
if ( null === $document ) {
return null;
}
it's a small thing to abstract but I find in my experience it worth it especially as we learn about the settings we need to activate with whitespace and with parse-handling.
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.
when I have worked with
DOMDocument
I found it extremely helpful to extract the setup code into a separate function that abstracts away the noisy quirks.
That seems like a reasonable revision, for sure! I expect it would also make writing the tests a little nicer, since I'd not need to lump all the error cases for this function otherwise intended specifically at sourcing values.
function gutenberg_replace_block_parser_class() { | ||
return 'WP_Sourced_Attributes_Block_Parser'; | ||
} | ||
add_filter( 'block_parser_class', 'gutenberg_replace_block_parser_class' ); |
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 how the system was designed to work, though I believe that you should be able to skip creating the helper function.
add_filter( 'block_parser_class', 'WP_Sourced_Attributes_Block_Parser' );
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 helper is useful, as it allows for this filter to be unhooked.
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 how the system was designed to work, though I believe that you should be able to skip creating the helper function.
I'd tend to agree with @spacedmonkey here, though it certainly piques my interest that this syntax can work 🤔
Thanks for getting back to server-side parsing of attributes @aduth. This change seems to mitigate the problem of having attributes sourced from HTML and I think it's good to get it in Core. If we end up thinking this is the way to go I'd only want to consider eventually merging it into the default parser, though I'd prefer to find a way to do that which doesn't mash all that code into the default parser. From our discussions I know that you are aware of the fact that this only addresses one of a few concerns with sourced attributes, that the desire to have all attributes available to a parser with no knowledge of the block implementations is still unresolved by this change. It seems though that we have been pushing the limit of what sourced attributes are offering and some people are really wishing we didn't have as much of them; I think that in some ways the biggest problem we're addressing is one of quantity and not of quality. If we can surface the attributes for the Core blocks then most people will be happy. This patch gets those attributes available to the PHP on the running server while leaving them inaccessible to any other parser. That's better than leaving them unavailable to every parser. 🙂 |
function gutenberg_register_block_types() { | ||
$registry = WP_Block_Type_Registry::get_instance(); | ||
|
||
$block_manifests = glob( dirname( dirname( __FILE__ ) ) . '/packages/block-library/src/*/block.json' ); |
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 think we have block.json
for all core blocks, those which are dynamic don't have this metadata file provided because we still didn't resolve the following issues:
supports
is implemented only on the client-side- translations aren't tackled for fields defined in the JSON file, there is the proposal for the client-side Babel macro: Add new Babel macro which handles block.json file transformation #16088, but we don't have anything for the server-side
Those aren't blockers for this proposal if we were to use only attributes
though. So maybe it would be a good idea to move attributes to the block.json
file to better promote this format.
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 still didn't resolve the following issues:
You'll have to forgive me since it's been a while that I've revisited some of those specific details of the JSON manifest. Working through this prototype forced me to consider how we would implement at least some of those supports on the server (className
, align
, anchor
are implemented in this pull request).
For translations, I seem to recall something about how we considered to wrap the translateable fields via __
et. al., automatically? I'm not sure exactly how we determine the domain in that case.
As a prototype, I'm also fine to start splitting those off into their own individual tasks. It was at least interesting to explore the feasibility of pulling them in and highlighting some of these shortcomings (notably supports
).
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.
You'll have to forgive me since it's been a while that I've revisited some of those specific details of the JSON manifest. Working through this prototype forced me to consider how we would implement at least some of those supports on the server (
className
,align
,anchor
are implemented in this pull request).
Nice, I missed that, sorry about it :(
For translations, I seem to recall something about how we considered to wrap the translateable fields via
__
et. al., automatically? I'm not sure exactly how we determine the domain in that case.
There needs to be the textDomain
field declared in the block.json
file. You can check my prototype for JS side as a reference: #16088.
I don't think it is a concern though in the context of attributes
. I just wanted to raise awareness of that. The general agreement was that attributes
shouldn't be translatable.
As a prototype, I'm also fine to start splitting those off into their own individual tasks. It was at least interesting to explore the feasibility of pulling them in and highlighting some of these shortcomings (notably
supports
).
Yes, supports
seems like the only place which can cause issues for the proposed code.
Please consider this ticket in core. I believe this has to land before we can continue this work. I also believe how |
$registry = WP_Block_Type_Registry::get_instance(); | ||
|
||
$block_manifests = glob( dirname( dirname( __FILE__ ) ) . '/packages/block-library/src/*/block.json' ); | ||
foreach ( $block_manifests as $block_manifest ) { |
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 some level of validation be happening 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.
Should some level of validation be happening here?
Do you mean that it has necessary properties to consider it a valid block manifest?
There are some simple checks below, both to account that the file could be parsed as JSON, and that it has a name
. We could expand on this.
Line 41 in 1e359a8
if ( is_null( $block_settings ) || ! isset( $block_settings['name'] ) ) { |
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.
Validation of types, like it string, int, array etc?
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.
Validation of types, like it string, int, array etc?
If I understand you correctly, we probably should want something like what exists in WP_Block_Type#prepare_attributes_for_render
, though I expect this would be applied at the time that $attributes
are being sourced.
* be parsed. | ||
* @return mixed Sourced attribute value. | ||
*/ | ||
function get_html_sourced_attribute( $block, $attribute_schema ) { |
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 call is going to be expensive from a compute level. Is there anyway we can cache the result?
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 call is going to be expensive from a compute level. Is there anyway we can cache the result?
I'd thought a bit about what might make sense to cache. Since the HTML of each block will likely be unique, I don't know that we would want to cache either the loaded HTML or the queried results, as the cache hit ratio would be very low.
What might make sense, depending on whether it makes a measurable difference:
- Caching the
$document
itself.- If constructing
DOMDocument
is expensive (I don't know that it is)
- If constructing
- Caching the converted XPath selectors
- The conversion may or may not be expensive (might also depend which implementation we choose), but there's a higher likelihood we would reuse those on a a per-block-type basis (e.g. every paragraph will be running the
p
selector).
- The conversion may or may not be expensive (might also depend which implementation we choose), but there's a higher likelihood we would reuse those on a a per-block-type basis (e.g. every paragraph will be running the
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.
How about cache the attributes, in say post meta?
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.
How about cache the attributes, in say post meta?
Hm, I'd have to think about it more, but that does seem like a good idea. In fact, it might then make sense to run this sourcing logic when a post is saved, rather than at parse-time (the parse would just read the cached result).
* @return mixed Sourced attribute value. | ||
*/ | ||
function get_html_sourced_attribute( $block, $attribute_schema ) { | ||
$document = new DOMDocument(); |
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 seems like this class has some requirements. Can we confirm that the libxml package is currently a required one for WP Core?
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 seems like this class has some requirements. Can we confirm that the libxml package is currently a required one for WP Core?
From the page you link, it says "libxml is enabled by default".
We could still have some graceful fallback here for environments where it's explicitly disabled, although it would be unable to populate $attributes
, yes.
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 may need to change the requires for WP core.
* @return string Equivalent XPath selector. | ||
*/ | ||
function _wp_css_selector_to_xpath( $selector ) { | ||
/* |
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 would add a filter here, to high jack this bahviour
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 would add a filter here, to high jack this bahviour
Seems reasonable, sure 👍
* @param string $selector CSS selector. | ||
* @return string Equivalent XPath selector. | ||
*/ | ||
function _wp_css_selector_to_xpath( $selector ) { |
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 function needs PHP unit tests.
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 function needs PHP unit tests.
Yep, I have no plans to merge anything without sufficient tests.
* | ||
* @since 6.9.0 | ||
*/ | ||
class WP_Sourced_Attributes_Block_Parser extends WP_Block_Parser { |
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.
Unit tests?
Thanks for the pointer! Those changes seem much-needed, I agree. For how it impacts this pull request, I think
Before this pull request is merged? Or the Trac ticket? I'm not really sure how those fields relate to the effort here. |
In the RFC, editor_script, script, editor_style and style are defines as URLs. However PHP registered block use script / style handles. See wp_enqueue_registered_block_scripts_and_styles. TL:DR If you pass a URL in editor_script, script, editor_style and style fields, is not going to work and may break things.
I think that #48529 has to land and we need to decide how the fields are handled in #47620 before we merge anything here. |
Those files updated in the patch are replaced with the files from packages installed from npm. Your changes would be erased on the next run of
We also didn't move translatable fields to
We don't have a code in place which would do it with PHP code when registering blocks from |
I'd like to propose exposing this block structure as a subresource, Edit: this was discussed a while back in slack, too. |
This was always meant to be a prototype exploration, so I'm going to close this as it's not in a mergeable state. It can be useful for future reference implementation. As far as its current status, there were pending architectural revisions that ought to be explored in any future implementation:
This pull request also included additional changes required by—but not directly related to—the addition of server-side attribute sourcing. From the original comment:
Respective status of each:
|
Yes, the plan is to introduce new helper method that allows registering block using a new utility function that works with |
|
||
/** | ||
* Given a registered block type settings array, assigns default attributes. | ||
* This must be called manually, as there is currently no way to hook to 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.
Now, that register_block_type_args
filter was introduced with https://core.trac.wordpress.org/ticket/49615, we can add this functionality in WordPress core. I will extract this function and propose it as an enhancement to the registration process on the server.
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 proposal is ready at WordPress/wordpress-develop#383.
This pull request seeks to explore an approach to block attributes sourcing on the server. In other words, it seeks to account for attributes whose values are derived from HTML (or post meta). It should be considered a prototype, but it currently implements most all of the current source supports.
Example:
Implementation Notes:
The parsing relies on
DOMDocument
, querying usingDOMXPath
by first converting the block type attribute selector to an equivalent XPath selector using a bundled, modified version of a third-party library PHP Selector.In an effort to best demonstrate its usage, additional changes include:
block.json
manifestscontent.blocks
field on the REST API posts responsesclassName
,align
,anchor
)Open Questions:
For me, the main questions and concerns surrounding this approach include:
DOMDocument
and the PHP Selector utilities resilient enough, and do they account for modern HTML and CSS?symfony/css-selector