-
Notifications
You must be signed in to change notification settings - Fork 110
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
Fire actions before and after Optimization Detective processes a document #1919
Conversation
wp
action instead of late in the output buffer callback
8e31a06
to
f973719
Compare
1e8db6d
to
a57391d
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## trunk #1919 +/- ##
==========================================
+ Coverage 71.25% 71.39% +0.13%
==========================================
Files 85 86 +1
Lines 7006 7043 +37
==========================================
+ Hits 4992 5028 +36
- Misses 2014 2015 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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. |
wp
action instead of late in the output buffer callbackThere 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.
@westonruter Overall this looks good to me. A few questions and notes:
- I don't fully understand what part of the conversation starting in REST API logic in Optimization Detective can be encapsulated into a ~static~ class #1859 (comment) this is supposed to address. Can you clarify?
- To confirm, the main purpose of these actions is to only allow reading relevant data for the OD optimization process right? In other words, they are not meant to modify/perform any actual optimizations?
- I'm not sure about the
append_head_html
andappend_body_html
methods in their current implementation. Other than appending body comments, what are real use-cases for the two methods? - I know they're documented to not validate and that one needs to be careful for that reason, but (partly dependent on your response to my previous point) we may want to come up with something better. For example, there could be an
append_body_comment
method that expects a string that is HTML escaped and wrapped in the HTML comment markup. This would be much safer than allowing arbitrary HTML.
$query_vars = od_get_normalized_query_vars(); | ||
$slug = od_get_url_metrics_slug( $query_vars ); | ||
$post = OD_URL_Metrics_Post_Type::get_post( $slug ); | ||
$post_id = $post instanceof WP_Post && $post->ID > 0 ? $post->ID : 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.
I find this a bit strange - I assume without it PHPStan complains. But in reality a WP_Post
should never have a non-positive ID
. Obviously an additional check for it is not a real problem, but if we then set the $post_id
to null
if not, we should also set the $post
to null
in that same situation.
In other words: If somehow the WP_Post
is invalid, we shouldn't allow the post object to get passed through either, if we don't accept its ID
value.
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, it's for the sake of PHPStan. The condition was present before when constructing OD_Tag_Visitor_Context
but just moved up here.
Technically WP_Post
can have a negative integer sometimes. It was a hack I used in the Customizer to denote nav_menu_item
posts which were in a changeset but not yet published to the database 😅
Otherwise, the $post
is only used in OD_URL_Metrics_Post_Type::get_url_metrics_from_post()
below, which only looks at the post_content
of the object, so in that case it doesn't matter if the ID
is not valid.
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.
But if a post doesn't have an ID, wouldn't it be considered irrelevant for Optimization Detective too? I think it would make more sense to then nullify it too for a consistent experience to avoid a weird edge-case.
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.
Since in practice it will never not be a positive-int
, I've opted for phpdoc to declare it as such: 0d8cb65.
It addresses it by eliminating the need for global variables or singletons, where the objects are exposed as part of actions instead.
Correct, although they can be used to add new preload/preconnect links or append HTML to the
See usage in westonruter/od-admin-ui#11. It is used to grab URL Metric data for the plugin to add to the admin bar. You can see it is used to add a stylesheet to the
See the previous point. Validation could be added later via fragments in the HTML Processor, but these methods are exposed now already for tag visitors to append arbitrary HTML to the |
Co-authored-by: Felix Arntz <[email protected]>
Makes sense, thanks 👍
But that doesn't seem to be the purpose of this action? Otherwise I would expect the other plugins like Image Prioritizer to use it for example. I would advise caution against allowing to do things in an action just because they're possible when that's not the main intent of the action.
Why don't we instead add specific methods for adding script tags, style tags, and HTML comments instead? I think that would be a better solution. I don't like the idea of now allowing any HTML and later adding validation, as that would effectively become a breaking change. Since this is going to be a public API, I think we should go for more specific intents and have validation from the start. Alternatively, we leave those methods out for now and focus only on the "read" / "access" aspect of this action. Then we can discuss in a follow up issue which write-actions would make sense via this action. This discussion would also go back to my above point - if it's possible via another action, why should it also be possible with this action? |
Image Prioritizer and Embed Optimizer could indeed use these actions, but they haven't been available. Allowing insertion of HTML once via the This could, for example, allow tag visitors to better optimize stylesheets they insert into the document. Instead of Embed Optimizer inserting a separate <style>
@media (width <= 480px) { #embed-optimizer-6040306707fb51ccaafa915c2da8f412 { min-height: 500px; } }
@media (480px < width <= 600px) { #embed-optimizer-6040306707fb51ccaafa915c2da8f412 { min-height: 500px; } }
@media (600px < width <= 782px) { #embed-optimizer-6040306707fb51ccaafa915c2da8f412 { min-height: 500px; } }
@media (782px < width) { #embed-optimizer-6040306707fb51ccaafa915c2da8f412 { min-height: 500px; } }
</style> With the
That could be done, but note that The current use cases for
The current use cases for
So there would need to be new methods like:
I think it's useful to have methods that allow inserting arbitrary raw HTML in the
Because not all document modifications are directly related to a tag visitor. For example, with the OD Admin UI plugin, it inserts markup based on the current collection of URL Metrics, regardless of any tags in the page. |
@@ -143,6 +143,7 @@ private function get_common_lcp_element_external_background_image( OD_URL_Metric | |||
private function maybe_preload_external_lcp_background_image( OD_Tag_Visitor_Context $context ): void { | |||
// Gather the tuples of URL Metric group and the common LCP element external background image. | |||
// Note the groups of URL Metrics do not change across invocations, we just need to compute this once for all. | |||
// TODO: Instead of populating this here, it could be done once per invocation during the od_start_template_optimization action since the page's OD_URL_Metric_Group_Collection is available there. |
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.
See #1921
@westonruter Thanks for outlining some more use-cases for this. As far as I understand though, several of them are already possible via the tag visitors today - which goes back to my concern about also allowing it here. But my main question is: Why are none of these use-cases being implemented here? That would provide a better foundation to discuss the implementation of the "write" methods. I'm still not convinced about the openness of these methods and whether a "context" object should have them. At the end of the day, this needs more discussion with real examples to consider how these methods would be used. For example, based on your comment that the extension plugins could use these actions to append HTML once instead of doing it in the tag visitor, maybe this should rather go hand in hand with another change to disallow doing so in the tag visitor. Given this is milestoned for Monday's release, we have almost no time left. I think it would be best to downscope this PR to focus only on introducing the actions and the template context class for context awareness, but leave out any write methods. Since there is no usage of them in neither of the plugins, I don't see a reason these have to be added in now. We could discuss them in a follow-up issue with more time on our hands. |
The reason is that since Image Prioritizer and Embed Optimizer depend on Optimization Detective. If they were part of this release, then all plugins would have to be updated at the same time. I'd rather get the new fundamental API out as part of this 1.0.0-beta3 release so that for the 1.0.0 final we can safely update Image Prioritizer and Embed Optimizer to make use of them. You can see there are use cases being implemented:
This seems better than introducing a new fundamental API in 1.0.0 without a beta first. If the due date is the problem we can just move it back, as we discussed these releases being more ad hoc anyway. |
I don't think that's a good reason to not make the change at the same time. If they need to be updated at the same time, the problem would be backward compatibility, not timing.
The fundamental API would be introduced here - the hooks and the context object. Adding more methods to it would be an enhancement, and that is totally reasonable for post-1.0.0 IMO.
Per the above, I don't see why that's necessary. There are sufficient changes in the plugins already to do a release now as we had planned. If you think we are not ready for Optimization Detective 1.0.0 yet as the next release, we could also have another 1.0.0-beta4 release. |
Right, it's not a back-compat problem. Image Prioritizer and Embed Optimizer will still work as expected without updating to make use of this new API. I was confused.
Still, since there is a clear need for these hooks I want to include them prior to the 1.0.0 release. It address something that always nagged at me about the API but I didn't have a solution for it until now. |
Then how about this? We could
|
Oh nevermind, I wasn't confused. The problem is that if someone were to update Image Prioritizer and Embed Optimizer which used the new hooks, but didn't update Optimization Detective. Then they would stop working.
Given the above, I think we'll need to be sure to release the hooks earlier so ensure users update OD so they are available. I don't see the need to omit |
But then this is a BC break, which shouldn't happen in the first place. It should be possible to have (at least temporarily) conditionals in Image Prioritizer and Embed Optimizer so that they use the most suitable way that's available via the Optimization Detective version used.
See above, I don't think this is a good idea. We can't rely on timing, there should be BC measures in place instead.
I disagree. It's not just about whether direct HTML modification is generally the right way, it's also about whether it should happen in this hook vs in the other hook. So it requires a more nuanced conversation where we consider the whole API, not just this new action hook.
That would work for me. |
This is why I'm not including the changes to Embed Optimizer and Image Prioritizer in this PR.
I think we've had a pretty nuanced conversation here! |
But that doesn't make it a non-breaking change then. If we need to break BC, there should always be at least 1-2 releases where both the old and new way are supported, with the old one triggering deprecation warnings, or (in this case possibly more likely) add a warning to update to the newer OD version. Alternatively, we would need to bump the minimum requirement for the OD version in each extension right away, which means there's no need for temporary workarounds, but the feature would be disabled (temporarily) if a site only updates the extensions but not OD.
True, but we haven't discussed yet for example why injecting head and/or body HTML should be possible both via the tag visitor context and the template context - to me that's confusing from an API perspective. I think we should continue discussing that, but the more time-sensitive decision is what to do about this upcoming release. I still don't understand what is the rush in including the write methods in this releases, rather than figuring this out "calmly" without the time pressure and implement what we decide on for a 1.0.0-beta4 (ideally also accompanied by the usage of it in the extension plugins). |
Yes, Image Prioritizer and Embed Optimizer can be updated to short-circuit at the
There are three scenarios for inserting markup:
I was hoping to be able to avoid a beta4. If this is included in beta3, then extension plugins can make use of the new APIs at the time of 1.0.0. Even if we do have a beta4, then this being included now will allow us to have more time to make sure the it is able to be leveraged by extensions effectively, but I think this has been already demonstrated with the above PRs. |
👍 So I think this would mean we can implement the API enhancements and their usage at the same time, and they can be released at the same time.
Thanks for outlining those. Particularly the 2nd one I find fuzzy. Some thoughts:
Not sure why, the reasons you provide seem a bit like a moot point. I think it's totally fine to have a beta4. WordPress Core has had beta4s before too, and FWIW I don't think there needs to be any arbitrary limit on the number of betas a product has. Technically speaking, we've been still refining the API throughout the betas, which isn't something a regular beta would allow for anyway (beyond bug fixes). I'd rather have a 1.0.0 people can trust than introducing methods in the last 1.0.0 beta that may be deprecated again already in a 1.1.0 shortly after. I think I'm aligned on allowing write methods on the template context, but per my above and previous feedback this requires more thought on how it should be implemented exactly (I'm still not a fan of allowing arbitrary HTML in those methods until there is a realistic use-case for requiring that) and what it means for the tag visitor context (we shouldn't allow to do the same thing on either). Based on that, I still think the best decision would be to only include the read methods in this release, and continue discussing the above in a follow-up issue which we'll milestone for 1.0.0-beta4. |
Yes, but if someone updates Image Prioritizer or Embed Optimizer without Optimization Detective then the functionality won't work and they'll get an admin notice. That's fine I guess.
The intended design of the tag visitors is to be able to allow one to be written as simply as possible, just by registering a single tag visitor callback. This is possible if there is a way to write to the document from the tag visitor. For example: add_action( 'od_register_tag_visitors', static function ( OD_Tag_Visitor_Registry $registry ) {
$registry->register( 'video-lazy-load', static function ( OD_Tag_Visitor_Context $context ) {
static $added_lazy_load_script = false;
$processor = $context->processor;
if ( $processor->get_tag() !== 'VIDEO' ) {
return;
}
$context->track_tag();
// Abort if URL Metrics aren't collected for mobile and desktop yet.
if (
$context->url_metric_group_collection->get_first_group()->count() === 0
&&
$context->url_metric_group_collection->get_last_group()->count() === 0
) {
return;
}
if ( $context->url_metric_group_collection->get_element_max_intersection_ratio( $processor->get_xpath() ) < PHP_FLOAT_EPSILON ) {
$processor->set_attribute( 'preload', 'none' );
if ( ! $added_lazy_load_script ) {
$processor->append_body_html( wp_get_script_tag( array( 'src' => plugins_url( 'lazy-load.js', __FILE__ ) ) ) );
$added_lazy_load_script = true;
}
}
} );
} ); If writing to the document isn't possible from the tag visitor, then you have to add higher level abstractions, like a class or else a global variable, and then to also add a
This goes back to the chat during the previous release, where there was confusion about having betas. So I had intended to get away from beta releases, but since this next release is a beta which I hadn't intended either, I guess my goals here are awash. We can add more betas. But perhaps the next beta can be in two weeks rather than waiting a whole month, as we discussed.
I'd rather not but I will if you insist. |
…late_Optimization_Context for now Co-authored-by: Felix Arntz <[email protected]>
Thanks, it makes sense to be able to do it in both ways then. Still, I think there may be a cleaner implementation for that than to expose the same methods on multiple API classes. Maybe we can have those shared write methods on one of the classes that is used across both actions, or on a new class that is then used across both actions, e.g. as a child property of the respective main context class, like My other remaining concern is about allowing to inject raw HTML. This is of course already available via the tag visitor today (which I'm not sure we should allow either), but since this PR would be adding it to another class, I think we should revisit before committing to it for 1.0.0.
Yeah, I would prefer to continue the above discussion over the next week and for now merge this without the append methods for beta3. And definitely, no need to wait a month for the following beta4 based on our new approach. |
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 @westonruter, LGTM!
See #1931 for the follow-up. |
This is to address the discussion starting at #1859 (comment).
Two new actions are introduced with this PR:
od_start_template_optimization
od_finish_template_optimization
The first is fired before walking over the document and invoking all of the tag visitors. The second is fired after the processing of the document is complete. Both actions are passed a new
OD_Template_Optimization_Context
object which contains the following properties:Additionally, two methods are exposed which are wrappers for the corresponding methods inOD_HTML_Tag_Processor
:::append_head_html( string $html )
::append_body_html( string $html )
Note that the underlyingOD_HTML_Tag_Processor
is not exposed in this context because atod_start_template_optimization
the iteration over the document has not started and atod_finish_template_optimization
the iteration is completed. Mutations of specific tags are expected to be done via tag visitors, with the exception of being able to insert links via the suppliedOD_Link_Collection
instance and these two methods which append raw HTML to theHEAD
andBODY
.These actions will allow plugins to do more things with the URL Metrics, like extend the admin bar with URL Metric information without having to re-construct the
OD_URL_Metric_Group_Collection
. For example, the OD Admin UI plugin can use this to populate the Admin Bar item for URL Metrics without having to re-construct theOD_URL_Metric_Group_Collection
: westonruter/od-admin-ui#11Additionally, the
od_start_template_optimization
action allows for tag visitors to do some initialization logic based on the currentOD_URL_Metric_Group_collection
prior to iterating over the document, which otherwise requires this initialization logic to be handled in the tag visitor callback itself, which is awkward. See #1921 for how this improves the tag visitor for background images in Image Prioritizer.This also allows plugins to conditionally opt-out of caching a pre-optimized page, in particular by allowing them to access the constructed
OD_URL_Metric_Group_Collection
. For example, WP Super Cache allows plugins to prevent storing a page in cache by defining aDONOTCACHEPAGE
constant. So if you wanted to prevent caching a page until all viewport groups are populated, you could do:Or assuming another caching layer like Varnish, you could send the
Cache-Control: private
header if all groups are not complete, and you want to :These essentially implement #1912.
Originally I tried to move the initialization of the Optimization Detective objects just before the template was being rendered, but there was a tradeoff here. On one hand, it would allow all of the
OD_URL_Metric_Group_Collection
to be available earlier so the template could be rendered with information from the collection (e.g. to populate the admin bar). However, the downside here is that this would prevent the ability to register tag visitors conditionally when the page is rendered. For example, it doesn't make a lot of sense to register a tag visitor for a block that doesn't exist in the page. So you could do something like this to conditionally register a tag visitor to setfetchpriority
on aSCRIPT
tag for embeds, with a value ofhigh
if it appears in the initial viewport andlow
if it does not: