Skip to content

Commit

Permalink
Merge pull request #1869 from WordPress/update/od-docs
Browse files Browse the repository at this point in the history
Further update Optimization Detective documentation
  • Loading branch information
westonruter authored Feb 20, 2025
2 parents d04e270 + 7a5bfad commit 2c3e1ca
Show file tree
Hide file tree
Showing 23 changed files with 669 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public function get_freshness_ttl(): int {
}

/**
* Gets the first URL Metric group.
* Gets the first URL Metric group (with the lowest minimum viewport width, e.g. for mobile).
*
* This group normally represents viewports for mobile devices. This group always has a minimum viewport width of 0
* and the maximum viewport width corresponds to the smallest defined breakpoint returned by
Expand All @@ -248,7 +248,7 @@ public function get_first_group(): OD_URL_Metric_Group {
}

/**
* Gets the last URL Metric group.
* Gets the last URL Metric group (with the highest minimum viewport width, e.g. for desktop).
*
* This group normally represents viewports for desktop devices. This group always has a minimum viewport width
* defined as one greater than the largest breakpoint returned by {@see od_get_breakpoint_max_widths()}.
Expand All @@ -266,6 +266,7 @@ public function get_last_group(): OD_URL_Metric_Group {
* Clears result cache.
*
* @since 0.3.0
* @access private
*/
public function clear_cache(): void {
$this->result_cache = array();
Expand Down Expand Up @@ -298,6 +299,7 @@ private function create_groups(): array {
*
* @since 0.1.0
* @throws InvalidArgumentException If there is no group available to add a URL Metric to.
* @access private
*
* @param OD_URL_Metric $new_url_metric New URL Metric.
*/
Expand All @@ -317,7 +319,7 @@ public function add_url_metric( OD_URL_Metric $new_url_metric ): void {
}

/**
* Gets group for viewport width.
* Gets the group for the provided viewport width.
*
* @since 0.1.0
* @throws InvalidArgumentException When there is no group for the provided viewport width. This would only happen if a negative width is provided.
Expand Down Expand Up @@ -411,7 +413,11 @@ public function is_every_group_populated(): bool {
}

/**
* Checks whether every group is complete.
* Checks whether every group is complete (full sample of non-stale URL Metrics).
*
* Completeness means the full sample size of URL Metrics has been collected,
* none of the collected URL Metrics are stale (with a mismatching ETag or a
* timestamp older than the freshness TTL).
*
* @since 0.1.0
* @see OD_URL_Metric_Group::is_complete()
Expand All @@ -438,7 +444,7 @@ public function is_every_group_complete(): bool {
}

/**
* Gets the groups with the provided LCP element XPath.
* Gets the groups which have an LCP element with the provided XPath.
*
* @since 0.3.0
* @see OD_URL_Metric_Group::get_lcp_element()
Expand Down Expand Up @@ -468,7 +474,7 @@ public function get_groups_by_lcp_element( string $xpath ): array {
}

/**
* Gets common LCP element.
* Gets the LCP element which is shared by all groups, or at least the first group (mobile) and last group (desktop) if the intermediary groups are not populated.
*
* @since 0.3.0
* @since 0.9.0 An LCP element is also considered common if it is the same in the narrowest and widest viewport groups, and all intermediate groups are empty.
Expand Down Expand Up @@ -585,7 +591,7 @@ public function get_all_element_max_intersection_ratios(): array {
}

/**
* Gets all elements' status for whether they are positioned in any initial viewport.
* Gets the status for whether each element is positioned in any initial viewport.
*
* An element is positioned in the initial viewport if its `boundingClientRect.top` is less than the
* `viewport.height` for any of its recorded URL Metrics. Note that even though the element may be positioned in the
Expand Down
5 changes: 4 additions & 1 deletion plugins/optimization-detective/class-od-url-metric-group.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ public function is_viewport_width_in_range( int $viewport_width ): bool {
* Adds a URL Metric to the group.
*
* @since 0.1.0
* @access private
*
* @throws InvalidArgumentException If the viewport width of the URL Metric is not within the min/max bounds of the group.
*
Expand Down Expand Up @@ -279,7 +280,8 @@ static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int {
* Determines whether the URL Metric group is complete.
*
* A group is complete if it has the full sample size of URL Metrics
* and all of these URL Metrics are fresh.
* and all of these URL Metrics are fresh (with a current ETag and a
* timestamp that is not older than the freshness TTL).
*
* @since 0.1.0
* @since 0.9.0 If the current environment's generated ETag does not match the URL Metric's ETag, the URL Metric is considered stale.
Expand Down Expand Up @@ -486,6 +488,7 @@ public function count(): int {
* Clears result cache.
*
* @since 0.9.0
* @access private
*/
public function clear_cache(): void {
$this->result_cache = array();
Expand Down
6 changes: 4 additions & 2 deletions plugins/optimization-detective/class-od-url-metric.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,11 @@ private function prepare_data( array $data ): array {
}

/**
* Gets the group that this URL Metric is a part of (which may not be any).
* Gets the group that this URL Metric is a part of.
*
* @since 0.7.0
*
* @return OD_URL_Metric_Group|null Group.
* @return OD_URL_Metric_Group|null Group. Null will never occur in the context of a tag visitor.
*/
public function get_group(): ?OD_URL_Metric_Group {
return $this->group;
Expand All @@ -159,6 +159,7 @@ public function get_group(): ?OD_URL_Metric_Group {
* Sets the group that this URL Metric is a part of.
*
* @since 0.7.0
* @access private
*
* @param OD_URL_Metric_Group $group Group.
*
Expand All @@ -177,6 +178,7 @@ public function set_group( OD_URL_Metric_Group $group ): void {
* @since 0.1.0
* @since 0.9.0 Added the 'etag' property to the schema.
* @since 1.0.0 The 'etag' property is now required.
* @access private
*
* @todo Cache the return value?
*
Expand Down
12 changes: 8 additions & 4 deletions plugins/optimization-detective/docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,20 @@ As mentioned above, this plugin is a dependency that doesn't provide features on

## Extension Plugins

For production, from the WordPress.org Plugin Directory:
Stable plugins for use in production from the WordPress.org Plugin Directory:

* [Image Prioritizer](https://wordpress.org/plugins/image-prioritizer/): Prioritizes the loading of images and videos based on how visible they are to actual visitors; adds fetchpriority and applies lazy loading.
* [Embed Optimizer](https://wordpress.org/plugins/embed-optimizer/): Optimizes the performance of embeds through lazy-loading, preconnecting, and reserving space to reduce layout shifts.

For development and debugging, from repositories on GitHub:
Experimental plugins being explored exploration, from repositories on GitHub:

* [Optimization Detective Content Visibility](https://github.com/westonruter/od-content-visibility): Applies content-visibility to posts in The Loop to improve rendering performance.
* [Optimization Detective Intrinsic Dimensions](https://github.com/westonruter/od-intrinsic-dimensions): Supplies width and height attributes to IMG and VIDEO tags that lack them according to their intrinsic dimensions. This reduces Cumulative Layout Shift (CLS).

For development and debugging, also on GitHub:

* [Optimization Detective Admin UI](https://github.com/westonruter/od-admin-ui): Provides an admin UI to inspect URL Metrics from the Optimization Detective plugin.
* [Optimization Detective Debug Helper](https://github.com/swissspidy/od-debug-helper/): Makes data from Optimization Detective visible on the front end through the admin bar.
* [Optimization Detective Store Query Vars](https://github.com/westonruter/od-store-query-vars): Stores the Query Vars with a URL Metric in the Optimization Detective plugin. This is useful for debugging URL Metrics, in particular what the slug was computed from.
* [Optimization Detective Store User Agent](https://github.com/westonruter/od-store-user-agent): Stores the User Agent with a URL Metric in the Optimization Detective plugin. This is useful for debugging URL Metrics, in particular to understand what device has a given viewport dimensions.
* [Optimization Detective Dev Mode](https://github.com/westonruter/od-dev-mode): Adds filters to facilitate development of the Optimization Detective plugin.
* [Optimization Detective Content Visibility](https://github.com/westonruter/od-content-visibility): Applies content-visibility to posts in The Loop to improve rendering performance.
* [Optimization Detective Intrinsic Dimensions](https://github.com/westonruter/od-intrinsic-dimensions): Supplies width and height attributes to IMG and VIDEO tags that lack them according to their intrinsic dimensions. This reduces Cumulative Layout Shift (CLS).
108 changes: 87 additions & 21 deletions plugins/optimization-detective/docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,104 @@

### Action: `od_init` (argument: plugin version)

Fires when the Optimization Detective is initializing. This action is useful for loading extension code that depends on Optimization Detective to be running. The version of the plugin is passed as the sole argument so that if the required version is not present, the callback can short circuit.
Fires when the Optimization Detective is initializing.

This action is useful for loading extension code that depends on Optimization Detective to be running. The version
of the plugin is passed as the sole argument so that if the required version is not present, the callback can short circuit.

Example:

```php
add_action( 'od_init', function ( string $version ) {
if ( version_compare( $version, '1.0', '<' ) ) {
add_action( 'admin_notices', 'my_plugin_warn_optimization_plugin_outdated' );
return;
}

// Bootstrap the Optimization Detective extension.
require_once __DIR__ . '/functions.php';
// ...
} );
```

### Action: `od_register_tag_visitors` (argument: `OD_Tag_Visitor_Registry`)

Fires to register tag visitors before walking over the document to perform optimizations.

For example, to register a new tag visitor that targets `H1` elements:
Once a page has finished rendering and the output buffer is processed, the page contents are loaded into
an HTML Tag Processor instance. It then iterates over each tag in the document, and at each open tag it will
invoke all registered tag visitors. A tag visitor is simply a callable (such as a regular function, closure,
or even a class with an `__invoke` method defined). The tag visitor callback is invoked by passing an instance
of the `OD_Tag_Visitor_Context` object which includes the following read-only properties:

- `$processor` (`OD_HTML_Tag_Processor`): The processor with the cursor at the current open tag.
- `$url_metric_group_collection` (`OD_URL_Metric_Group_Collection`): The URL Metrics which may include information about the current tag to inform what optimizations the callback performs.
- `$link_collection` (`OD_Link_Collection`): Collection of links which will be added to the `HEAD` when the page is served. This allows you to add preload links and preconnect links as needed.
- `$url_metrics_id` (`positive-int|null`): The post ID for the `od_url_metrics` post from which the URL Metrics were loaded (if any). For advanced usage.

Note that you are free to call `$processor->next_tag()` in the callback (such as to walk over any child elements)
since the tag processor's cursor will be reset to the tag after the callback finishes.

When a tag visitor sees it is at a relevant open tag (e.g. by checking `$processor->get_tag()`), it can call the
`$context->track_tag()` method to indicate that the tag should be measured during detection. This will cause the
tag to be included among the `elements` in the stored URL Metrics. The element data includes properties such
as `intersectionRatio`, `intersectionRect`, and `boundingClientRect` (provided by an `IntersectionObserver`) as
well as whether the tag is the LCP element (`isLCP`) or LCP element candidate (`isLCPCandidate`). This method
should not be called if the current tag is not relevant for the tag visitor or if the tag visitor callback does
not need to query the provided `OD_URL_Metric_Group_Collection` instance to apply the desired optimizations. (In
addition to calling the `$context->track_tag()`, a callback may also return `true` to indicate the tag should be
tracked.)

Here's an example tag visitor that depends on URL Metrics data:

```php
add_action(
'od_register_tag_visitors',
static function ( OD_Tag_Visitor_Registry $registry ) {
$registry->register(
'my-plugin/h1',
static function ( OD_Tag_Visitor_Context $context ): bool {
if ( $context->processor->get_tag() !== 'H1' ) {
return false;
}
// Now optimize based on stored URL Metrics in $context->url_metric_group_collection.
// ...

// Returning true causes the tag to be tracked in URL Metrics. If there is no need
// for this, as in there is no reference to $context->url_metric_group_collection
// in a tag visitor, then this can instead return false.
return true;
}
);
$tag_visitor_registry->register(
'lcp-img-fetchpriority-high',
static function ( OD_Tag_Visitor_Context $context ): void {
if ( $context->processor->get_tag() !== 'IMG' ) {
return; // Tag is not relevant for this tag visitor.
}

// Mark the tag for measurement during detection so it is included among the elements stored in URL Metrics.
$context->track_tag();

// Make sure fetchpriority=high is added to LCP IMG elements based on the captured URL Metrics.
$common_lcp_element = $context->url_metric_group_collection->get_common_lcp_element();
if (
null !== $common_lcp_element
&&
$common_lcp_element->get_xpath() === $context->processor->get_xpath()
) {
$context->processor->set_attribute( 'fetchpriority', 'high' );
}
}
);
````

Please note this implementation of setting `fetchpriority=high` on the LCP `IMG` element is simplified. Please
see the Image Prioritizer extension for a more robust implementation.

Here's an example tag visitor that does not depend on any URL Metrics data:

```php
$tag_visitor_registry->register(
'img-decoding-async',
static function ( OD_Tag_Visitor_Context $context ): bool {
if ( $context->processor->get_tag() !== 'IMG' ) {
return; // Tag is not relevant for this tag visitor.
}

// Set the decoding attribute if it is absent.
if ( null === $context->processor->get_attribute( 'decoding' ) ) {
$context->processor->set_attribute( 'decoding', 'async' );
}
}
);
```

Refer to [Image Prioritizer](https://github.com/WordPress/performance/tree/trunk/plugins/image-prioritizer) and [Embed Optimizer](https://github.com/WordPress/performance/tree/trunk/plugins/embed-optimizer) for real world examples of how tag visitors are used. Registered tag visitors need only be callables, so in addition to providing a closure you may provide a `callable-string` or even a class which has an `__invoke()` method.
Refer to [Image Prioritizer](https://github.com/WordPress/performance/tree/trunk/plugins/image-prioritizer) and
[Embed Optimizer](https://github.com/WordPress/performance/tree/trunk/plugins/embed-optimizer) for additional
examples of how tag visitors are used.

### Action: `od_url_metric_stored` (argument: `OD_URL_Metric_Store_Request_Context`)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2c3e1ca

Please sign in to comment.