Skip to content
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

Allow extensions to opt in to using the web-vitals attribution build via the od_use_web_vitals_attribution_build filter #1759

Merged
merged 45 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fc95f4a
Bundle web-vitals attribution build
swissspidy Dec 18, 2024
651577e
Allow loading web-vitals attribution build
swissspidy Dec 18, 2024
ff3f120
Collect some INP data for testing
swissspidy Dec 18, 2024
9dd8159
Add debugging helper for LCP elements
swissspidy Dec 18, 2024
12a3494
some lint fixes
swissspidy Dec 18, 2024
be0be49
Add INP elements with JS
swissspidy Dec 18, 2024
e9bbe99
Apply suggestions from code review
swissspidy Dec 19, 2024
ac4d153
Add missing return statement
swissspidy Dec 19, 2024
098191c
Lint fixes
swissspidy Dec 19, 2024
9288025
Clarify comment
swissspidy Dec 19, 2024
2f507a0
PHPStan fixes
swissspidy Dec 19, 2024
8ead9ae
Multiline comment
swissspidy Dec 19, 2024
ad09dea
Merge branch 'trunk' into try/1736-debug-detective
swissspidy Dec 19, 2024
3e4d648
Add empty line
swissspidy Dec 19, 2024
c8b490b
Merge branch 'trunk' into try/1736-debug-detective
swissspidy Jan 10, 2025
36430ad
Only load assets if admin bar is showing
swissspidy Jan 10, 2025
a229ba6
Extract `od_get_group_collection()` helper
swissspidy Jan 10, 2025
ba48bd9
Don't add visitor if not showing admin bar
swissspidy Jan 10, 2025
581ffbc
Add `additionalProperties`
swissspidy Jan 10, 2025
eabd8c8
Fix anchors and popovers
swissspidy Jan 10, 2025
ebfea3f
Re-enable css
swissspidy Jan 10, 2025
c2d1a9f
Fix test_get_json_schema
westonruter Jan 13, 2025
6d95ce1
Prevent warning if INP data is missing
swissspidy Jan 13, 2025
5de84c6
Remove admin bar condition in admin bar cb
swissspidy Jan 13, 2025
adf3ae5
Styling adjustments
swissspidy Jan 13, 2025
e7432ed
Remove dev mode condition
swissspidy Jan 13, 2025
d552366
Move INP logic to tag visitor too
swissspidy Jan 13, 2025
2aaad52
Undo `od_get_group_collection`
swissspidy Jan 13, 2025
39f8336
Lint fixes
swissspidy Jan 13, 2025
1d900d2
Reuse existing anchor name
swissspidy Jan 13, 2025
efc1aa6
phpstan fixes
swissspidy Jan 13, 2025
b63b9d2
lint fix
swissspidy Jan 13, 2025
6d848d5
Merge branch 'trunk' into try/1736-debug-detective
swissspidy Jan 13, 2025
2f162f2
Remove extracted code
swissspidy Jan 13, 2025
89b718a
Merge branch 'trunk' into try/1736-debug-detective
swissspidy Jan 13, 2025
d32ffc0
Undo tests change too
swissspidy Jan 13, 2025
2802b57
Remove JS parts now as well
swissspidy Jan 14, 2025
5fd6891
Undo type import
swissspidy Jan 14, 2025
8dd8af6
Don't conditionally load by default
swissspidy Jan 14, 2025
c6f53fa
Remove newline
swissspidy Jan 14, 2025
fd7fa9f
Add jsdoc description
westonruter Jan 14, 2025
3614c13
Add typing for attribution report functions
westonruter Jan 14, 2025
3a30027
Add docs for the od_use_web_vitals_attribution_build filter
westonruter Jan 14, 2025
a75dd53
Add test for od_use_web_vitals_attribution_build filter
westonruter Jan 14, 2025
9600078
fixup! Add docs for the od_use_web_vitals_attribution_build filter
westonruter Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions plugins/optimization-detective/detect.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
/**
* @typedef {import("web-vitals").LCPMetric} LCPMetric
* @typedef {import("web-vitals").LCPMetricWithAttribution} LCPMetricWithAttribution
* @typedef {import("./types.ts").ElementData} ElementData
* @typedef {import("./types.ts").OnTTFBFunction} OnTTFBFunction
* @typedef {import("./types.ts").OnFCPFunction} OnFCPFunction
* @typedef {import("./types.ts").OnLCPFunction} OnLCPFunction
* @typedef {import("./types.ts").OnINPFunction} OnINPFunction
* @typedef {import("./types.ts").OnCLSFunction} OnCLSFunction
* @typedef {import("./types.ts").OnTTFBWithAttributionFunction} OnTTFBWithAttributionFunction
* @typedef {import("./types.ts").OnFCPWithAttributionFunction} OnFCPWithAttributionFunction
* @typedef {import("./types.ts").OnLCPWithAttributionFunction} OnLCPWithAttributionFunction
* @typedef {import("./types.ts").OnINPWithAttributionFunction} OnINPWithAttributionFunction
* @typedef {import("./types.ts").OnCLSWithAttributionFunction} OnCLSWithAttributionFunction
* @typedef {import("./types.ts").URLMetric} URLMetric
* @typedef {import("./types.ts").URLMetricGroupStatus} URLMetricGroupStatus
* @typedef {import("./types.ts").Extension} Extension
Expand Down Expand Up @@ -360,11 +366,11 @@ export default async function detect( {
);

const {
/** @type OnTTFBFunction */ onTTFB,
/** @type OnFCPFunction */ onFCP,
/** @type OnLCPFunction */ onLCP,
/** @type OnINPFunction */ onINP,
/** @type OnCLSFunction */ onCLS,
/** @type {OnTTFBFunction|OnTTFBWithAttributionFunction} */ onTTFB,
/** @type {OnFCPFunction|OnFCPWithAttributionFunction} */ onFCP,
/** @type {OnLCPFunction|OnLCPWithAttributionFunction} */ onLCP,
/** @type {OnINPFunction|OnINPWithAttributionFunction} */ onINP,
/** @type {OnCLSFunction|OnCLSWithAttributionFunction} */ onCLS,
} = await import( webVitalsLibrarySrc );

// TODO: Does this make sense here?
Expand Down Expand Up @@ -490,13 +496,18 @@ export default async function detect( {
} );
}

/** @type {LCPMetric[]} */
/** @type {(LCPMetric|LCPMetricWithAttribution)[]} */
const lcpMetricCandidates = [];

// Obtain at least one LCP candidate. More may be reported before the page finishes loading.
await new Promise( ( resolve ) => {
onLCP(
( /** @type LCPMetric */ metric ) => {
/**
* Handles an LCP metric being reported.
*
* @param {LCPMetric|LCPMetricWithAttribution} metric
*/
( metric ) => {
lcpMetricCandidates.push( metric );
resolve();
},
Expand Down
26 changes: 25 additions & 1 deletion plugins/optimization-detective/detection.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,32 @@ function od_get_cache_purge_post_id(): ?int {
* @param OD_URL_Metric_Group_Collection $group_collection URL Metric group collection.
*/
function od_get_detection_script( string $slug, OD_URL_Metric_Group_Collection $group_collection ): string {

/**
* Filters whether to use the web-vitals.js build with attribution.
*
* When using the attribution build of web-vitals, the metric object passed to report callbacks registered via
* `onTTFB`, `onFCP`, `onLCP`, `onCLS`, and `onINP` will include an additional {@link https://github.com/GoogleChrome/web-vitals#attribution attribution property}.
* For details, please refer to the {@link https://github.com/GoogleChrome/web-vitals web-vitals documentation}.
*
* For example, to opt in to using the attribution build:
*
* add_filter( 'od_use_web_vitals_attribution_build', '__return_true' );
*
* Note that the attribution build is slightly larger than the standard build, so this is why it is not used by default.
* The additional attribution data is made available to client-side extension script modules registered via the `od_extension_module_urls` filter.
*
* @since n.e.x.t
*
* @param bool $use_attribution_build Whether to use the attribution build.
*/
$use_attribution_build = (bool) apply_filters( 'od_use_web_vitals_attribution_build', false );

$web_vitals_lib_data = require __DIR__ . '/build/web-vitals.asset.php';
$web_vitals_lib_src = plugins_url( add_query_arg( 'ver', $web_vitals_lib_data['version'], 'build/web-vitals.js' ), __FILE__ );
$web_vitals_lib_src = $use_attribution_build ?
plugins_url( 'build/web-vitals-attribution.js', __FILE__ ) :
plugins_url( 'build/web-vitals.js', __FILE__ );
$web_vitals_lib_src = add_query_arg( 'ver', $web_vitals_lib_data['version'], $web_vitals_lib_src );

/**
* Filters the list of extension script module URLs to import when performing detection.
Expand Down
17 changes: 17 additions & 0 deletions plugins/optimization-detective/docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ The supplied context object includes these properties:

## Filters

### Filter: `od_use_web_vitals_attribution_build` (default: `false`)

Filters whether to use the web-vitals.js build with attribution.

When using the attribution build of web-vitals, the metric object passed to report callbacks registered via
`onTTFB`, `onFCP`, `onLCP`, `onCLS`, and `onINP` will include an additional [attribution property](https://github.com/GoogleChrome/web-vitals#attribution).
For details, please refer to the [web-vitals documentation](https://github.com/GoogleChrome/web-vitals).

For example, to opt in to using the attribution build:

```php
add_filter( 'od_use_web_vitals_attribution_build', '__return_true' );
```

Note that the attribution build is slightly larger than the standard build, so this is why it is not used by default.
The additional attribution data is made available to client-side extension script modules registered via the `od_extension_module_urls` filter.

### Filter: `od_breakpoint_max_widths` (default: `array(480, 600, 782)`)

Filters the breakpoint max widths to group URL Metrics for various viewports. Each number represents the maximum width (inclusive) for a given breakpoint. So if there is one number, 480, then this means there will be two viewport groupings, one for 0\<=480, and another \>480. If instead there are the two breakpoints defined, 480 and 782, then this means there will be three viewport groups of URL Metrics, one for 0\<=480 (i.e. mobile), another 481\<=782 (i.e. phablet/tablet), and another \>782 (i.e. desktop).
Expand Down
26 changes: 18 additions & 8 deletions plugins/optimization-detective/tests/test-detection.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,19 +80,20 @@ public function test_od_get_cache_purge_post_id( Closure $set_up, bool $expected
/**
* Data provider.
*
* @return array<string, array{set_up: Closure, expected_exports: array<string, mixed>}>
* @return array<string, array{set_up: Closure, expected_exports: array<string, mixed>, expected_standard_build: bool}>
*/
public function data_provider_od_get_detection_script(): array {
return array(
'unfiltered' => array(
'set_up' => static function (): void {},
'expected_exports' => array(
'set_up' => static function (): void {},
'expected_exports' => array(
'storageLockTTL' => MINUTE_IN_SECONDS,
'extensionModuleUrls' => array(),
),
'expected_standard_build' => true,
),
'filtered' => array(
'set_up' => static function (): void {
'set_up' => static function (): void {
add_filter(
'od_url_metric_storage_lock_ttl',
static function (): int {
Expand All @@ -106,11 +107,13 @@ static function ( array $urls ): array {
return $urls;
}
);
add_filter( 'od_use_web_vitals_attribution_build', '__return_true' );
},
'expected_exports' => array(
'expected_exports' => array(
'storageLockTTL' => HOUR_IN_SECONDS,
'extensionModuleUrls' => array( home_url( '/my-extension.js', 'https' ) ),
),
'expected_standard_build' => false,
),
);
}
Expand All @@ -122,10 +125,11 @@ static function ( array $urls ): array {
*
* @dataProvider data_provider_od_get_detection_script
*
* @param Closure $set_up Set up callback.
* @param array<string, array{set_up: Closure, expected_exports: array<string, mixed>}> $expected_exports Expected exports.
* @param Closure $set_up Set up callback.
* @param array<string, string> $expected_exports Expected exports.
* @param bool $expected_standard_build Expected standard build.
*/
public function test_od_get_detection_script_returns_script( Closure $set_up, array $expected_exports ): void {
public function test_od_get_detection_script_returns_script( Closure $set_up, array $expected_exports, bool $expected_standard_build ): void {
$set_up();
$slug = od_get_url_metrics_slug( array( 'p' => '1' ) );
$current_etag = md5( '' );
Expand All @@ -140,6 +144,12 @@ public function test_od_get_detection_script_returns_script( Closure $set_up, ar
foreach ( $expected_exports as $key => $value ) {
$this->assertStringContainsString( sprintf( '%s:%s', wp_json_encode( $key ), wp_json_encode( $value ) ), $script );
}
$this->assertSame( 1, preg_match( '/"webVitalsLibrarySrc":("[^"]+?")/', $script, $matches ) );
$web_vitals_library_src = json_decode( $matches[1] );
$this->assertStringContainsString(
$expected_standard_build ? '/web-vitals.' : '/web-vitals-attribution.',
$web_vitals_library_src
);
$this->assertStringContainsString( '"minimumViewportWidth":0', $script );
$this->assertStringContainsString( '"minimumViewportWidth":481', $script );
$this->assertStringContainsString( '"minimumViewportWidth":601', $script );
Expand Down
22 changes: 17 additions & 5 deletions plugins/optimization-detective/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
type ExcludeProps< T > = { [ k: string ]: any } & { [ K in keyof T ]?: never };

import { onTTFB, onFCP, onLCP, onINP, onCLS } from 'web-vitals';
import {
onTTFB as onTTFBWithAttribution,
onFCP as onFCPWithAttribution,
onLCP as onLCPWithAttribution,
onINP as onINPWithAttribution,
onCLS as onCLSWithAttribution,
} from 'web-vitals/attribution';

export interface ElementData {
isLCP: boolean;
Expand Down Expand Up @@ -35,14 +42,19 @@ export type OnFCPFunction = typeof onFCP;
export type OnLCPFunction = typeof onLCP;
export type OnINPFunction = typeof onINP;
export type OnCLSFunction = typeof onCLS;
export type OnTTFBWithAttributionFunction = typeof onTTFBWithAttribution;
export type OnFCPWithAttributionFunction = typeof onFCPWithAttribution;
export type OnLCPWithAttributionFunction = typeof onLCPWithAttribution;
export type OnINPWithAttributionFunction = typeof onINPWithAttribution;
export type OnCLSWithAttributionFunction = typeof onCLSWithAttribution;

export type InitializeArgs = {
readonly isDebug: boolean;
readonly onTTFB: OnTTFBFunction;
readonly onFCP: OnFCPFunction;
readonly onLCP: OnLCPFunction;
readonly onINP: OnINPFunction;
readonly onCLS: OnCLSFunction;
readonly onTTFB: OnTTFBFunction | OnTTFBWithAttributionFunction;
readonly onFCP: OnFCPFunction | OnFCPWithAttributionFunction;
readonly onLCP: OnLCPFunction | OnLCPWithAttributionFunction;
readonly onINP: OnINPFunction | OnINPWithAttributionFunction;
readonly onCLS: OnCLSFunction | OnCLSWithAttributionFunction;
};

export type InitializeCallback = ( args: InitializeArgs ) => Promise< void >;
Expand Down
5 changes: 5 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ const optimizationDetective = ( env ) => {
to: `${ destination }/build/web-vitals.js`,
info: { minimized: true },
},
{
from: `${ source }/dist/web-vitals.attribution.js`,
to: `${ destination }/build/web-vitals-attribution.js`,
info: { minimized: true },
},
{
from: `${ source }/package.json`,
to: `${ destination }/build/web-vitals.asset.php`,
Expand Down
Loading