Skip to content

Commit

Permalink
Merge pull request #1563 from WordPress/add/wwo-gtag-offloading
Browse files Browse the repository at this point in the history
Integrate with WooCommerce to offload Google Analytics to Web Worker
  • Loading branch information
westonruter authored Sep 30, 2024
2 parents 76685b0 + 998d19f commit 282a068
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 124 deletions.
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"npm": ">=10.2.3"
},
"dependencies": {
"@builder.io/partytown": "^0.10.2",
"@builder.io/partytown": "github:westonruter/partytown#add/wp-i18n-workaround",
"web-vitals": "4.2.3"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"optimization-detective",
"performance-lab",
"speculation-rules",
"web-worker-offloading",
"webp-uploads"
]
}
70 changes: 70 additions & 0 deletions plugins/web-worker-offloading/helper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
/**
* Helpers for Web Worker Offloading.
*
* @since 0.1.0
* @package web-worker-offloading
*/

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Gets configuration for Web Worker Offloading.
*
* @since 0.1.0
* @link https://partytown.builder.io/configuration
* @link https://github.com/BuilderIO/partytown/blob/b292a14047a0c12ca05ba97df1833935d42fdb66/src/lib/types.ts#L393-L548
*
* @return array<string, mixed> Configuration for Partytown.
*/
function wwo_get_configuration(): array {
$config = array(
'lib' => wp_parse_url( plugin_dir_url( __FILE__ ), PHP_URL_PATH ) . 'build/',
);

if ( WP_DEBUG && SCRIPT_DEBUG ) {
$config['debug'] = true;
}

/**
* Add configuration for Web Worker Offloading.
*
* Many of the configuration options are not documented publicly, so refer to the TypeScript definitions.
* Additionally, not all of the configuration options (e.g. functions) can be serialized as JSON and must instead be
* defined in JavaScript instead. To do so, use the following PHP code instead of filtering `wwo_configuration`:
*
* add_action(
* 'wp_enqueue_scripts',
* function () {
* wp_add_inline_script(
* 'web-worker-offloading',
* <<<JS
* window.partytown = {
* ...(window.partytown || {}),
* resolveUrl: (url, location, type) => {
* if (type === 'script') {
* const proxyUrl = new URL('https://my-reverse-proxy.example.com/');
* proxyUrl.searchParams.append('url', url.href);
* return proxyUrl;
* }
* return url;
* },
* };
* JS,
* 'before'
* );
* }
* );
*
* There are also many configuration options which are not documented, so refer to the TypeScript definitions.
*
* @since 0.1.0
* @link https://partytown.builder.io/configuration
* @link https://github.com/BuilderIO/partytown/blob/b292a14047a0c12ca05ba97df1833935d42fdb66/src/lib/types.ts#L393-L548
*
* @param array<string, mixed> $config Configuration for Partytown.
*/
return (array) apply_filters( 'wwo_configuration', $config );
}
97 changes: 35 additions & 62 deletions plugins/web-worker-offloading/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,11 @@
exit; // Exit if accessed directly.
}

/**
* Gets configuration for Web Worker Offloading.
*
* @since 0.1.0
* @link https://partytown.builder.io/configuration
*
* @return array{ debug?: bool, forward?: non-empty-string[], lib: non-empty-string, loadScriptsOnMainThread?: non-empty-string[], nonce?: non-empty-string } Configuration for Partytown.
*/
function wwo_get_configuration(): array {
$config = array(
'lib' => wp_parse_url( plugin_dir_url( __FILE__ ), PHP_URL_PATH ) . 'build/',
'forward' => array(),
);

/**
* Add configuration for Web Worker Offloading.
*
* @since 0.1.0
* @link https://partytown.builder.io/configuration
*
* @param array{ debug?: bool, forward?: non-empty-string[], lib: non-empty-string, loadScriptsOnMainThread?: non-empty-string[], nonce?: non-empty-string } $config Configuration for Partytown.
*/
return apply_filters( 'wwo_configuration', $config );
}

/**
* Registers defaults scripts for Web Worker Offloading.
*
* @since 0.1.0
* @access private
*
* @param WP_Scripts $scripts WP_Scripts instance.
*/
Expand All @@ -59,7 +35,7 @@ function wwo_register_default_scripts( WP_Scripts $scripts ): void {
$scripts->add_inline_script(
'web-worker-offloading',
sprintf(
'window.partytown = %s;',
'window.partytown = {...(window.partytown || {}), ...%s};',
wp_json_encode( wwo_get_configuration() )
),
'before'
Expand All @@ -72,12 +48,8 @@ function wwo_register_default_scripts( WP_Scripts $scripts ): void {
/**
* Prepends web-worker-offloading to the list of scripts to print if one of the queued scripts is offloaded to a worker.
*
* This also marks their strategy as `async`. This is needed because scripts offloaded to a worker thread can be
* considered async. However, they may include `before` and `after` inline scripts that need sequential execution. Once
* marked as async, `filter_eligible_strategies()` determines if the script is eligible for async execution. If so, it
* will be offloaded to the worker thread.
*
* @since 0.1.0
* @access private
*
* @param string[]|mixed $script_handles An array of enqueued script dependency handles.
* @return string[] Script handles.
Expand All @@ -88,61 +60,62 @@ function wwo_filter_print_scripts_array( $script_handles ): array {
if ( true === (bool) $scripts->get_data( $handle, 'worker' ) ) {
$scripts->set_group( 'web-worker-offloading', false, 0 ); // Try to print in the head.
array_unshift( $script_handles, 'web-worker-offloading' );

// TODO: This should be reconsidered because scripts needing to be offloaded will often have after scripts. See <https://github.com/WordPress/performance/pull/1497/files#r1733538721>.
if ( false === wp_scripts()->get_data( $handle, 'strategy' ) ) {
wp_script_add_data( $handle, 'strategy', 'async' ); // The 'defer' strategy would work as well.
wp_script_add_data( $handle, 'wwo_strategy_added', true );
}
break;
}
}
return $script_handles;
}
add_filter( 'print_scripts_array', 'wwo_filter_print_scripts_array' );
add_filter( 'print_scripts_array', 'wwo_filter_print_scripts_array', PHP_INT_MAX );

/**
* Updates script type for handles having `web-worker-offloading` as dependency.
*
* @since 0.1.0
* @access private
*
* @param string|mixed $tag Script tag.
* @param string $handle Script handle.
* @return string|mixed Script tag with type="text/partytown" for eligible scripts.
*/
function wwo_update_script_type( $tag, string $handle ) {
if (
is_string( $tag ) &&
is_string( $tag )
&&
(bool) wp_scripts()->get_data( $handle, 'worker' )
) {
$html_processor = new WP_HTML_Tag_Processor( $tag );

while ( $html_processor->next_tag( array( 'tag_name' => 'SCRIPT' ) ) ) {
if ( $html_processor->get_attribute( 'id' ) !== "{$handle}-js" ) {
continue;
}
if ( null === $html_processor->get_attribute( 'async' ) && null === $html_processor->get_attribute( 'defer' ) ) {
_doing_it_wrong(
'wwo_update_script_type',
esc_html(
sprintf(
/* translators: %s: script handle */
__( 'Unable to offload "%s" script to a worker. Script will continue to load in the main thread.', 'web-worker-offloading' ),
$handle
)
),
'Web Worker Offloading 0.1.0'
);
} else {
if ( $html_processor->get_attribute( 'id' ) === "{$handle}-js" ) {
$html_processor->set_attribute( 'type', 'text/partytown' );
$tag = $html_processor->get_updated_html();
break;
}
if ( true === wp_scripts()->get_data( $handle, 'wwo_strategy_added' ) ) {
$html_processor->remove_attribute( 'async' );
$html_processor->remove_attribute( 'data-wp-strategy' );
}
$tag = $html_processor->get_updated_html();
}
}

return $tag;
}
add_filter( 'script_loader_tag', 'wwo_update_script_type', 10, 2 );

/**
* Filters inline script attributes to offload to a worker if the script has been opted-in.
*
* @since 0.1.0
* @access private
*
* @param array<string, mixed>|mixed $attributes Attributes.
* @return array<string, mixed> Attributes.
*/
function wwo_filter_inline_script_attributes( $attributes ): array {
$attributes = (array) $attributes;
if (
isset( $attributes['id'] )
&&
1 === preg_match( '/^(?P<handle>.+)-js-(?:before|after)$/', $attributes['id'], $matches )
&&
(bool) wp_scripts()->get_data( $matches['handle'], 'worker' )
) {
$attributes['type'] = 'text/partytown';
}
return $attributes;
}
add_filter( 'wp_inline_script_attributes', 'wwo_filter_inline_script_attributes' );
3 changes: 2 additions & 1 deletion plugins/web-worker-offloading/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@

define( 'WEB_WORKER_OFFLOADING_VERSION', '0.1.0' );

// Load the hooks.
require_once __DIR__ . '/helper.php';
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/third-party.php';
78 changes: 75 additions & 3 deletions plugins/web-worker-offloading/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,95 @@ Tested up to: 6.6
Stable tag: 0.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: performance, JavaScript, web worker, partytown
Tags: performance, JavaScript, web worker, partytown, analytics

Offload JavaScript execution to a Web Worker.

== Description ==

This plugin offloads JavaScript execution to a Web Worker, improving performance by freeing up the main thread.
This plugin offloads JavaScript execution to a Web Worker, improving performance by freeing up the main thread. This should translate into improved [Interaction to Next Paint](https://web.dev/articles/inp) (INP) scores.

In order to opt-in a script to be loaded in a worker, simply add `worker` script data to a registered script. For example,
⚠ _This functionality is experimental._ ⚠

In order to opt in a script to be loaded in a worker, simply add `worker` script data to a registered script. For example,
if you have a script registered with the handle of `foo`, opt-in to offload it to a web worker by doing:

`
wp_script_add_data( 'foo', 'worker', true );
`

Unlike with the script loading strategies (async/defer), any inline before/after scripts associated with the worker-offloaded registered script will also be offloaded to the worker, whereas with the script strategies an inline after script would block the script from being delayed.

Otherwise, the plugin currently ships with built-in integrations to offload Google Analytics to a web worker for the following plugin:

* [WooCommerce](https://wordpress.org/plugins/woocommerce/)

Support for [Site Kit by Google](https://wordpress.org/plugins/google-site-kit/) and [Rank Math SEO](https://wordpress.org/plugins/seo-by-rank-math/) are [planned](https://github.com/WordPress/performance/issues/1455).

Please monitor your analytics once activating to ensure all the expected events are being logged. At the same time, monitor your INP scores to check for improvement.

This plugin relies on the [Partytown 🎉](https://partytown.builder.io/) library by Builder.io, released under the MIT license. This library is in beta and there are quite a few [open bugs](https://github.com/BuilderIO/partytown/issues?q=is%3Aopen+is%3Aissue+label%3Abug).

The [Partytown configuration](https://partytown.builder.io/configuration) can be modified via the `wwo_configuration` filter. For example:

`
<?php
add_filter( 'wwo_configuration', function ( $config ) {
$config['mainWindowAccessors'][] = 'wp'; // Make the wp global available in the worker (e.g. wp.i18n and wp.hooks).
return $config;
} );
`

However, not all of the configuration options can be serialized to JSON in this way, for example the `resolveUrl` configuration is a function. To specify this, you can add an inline script as follows.

`
<?php
add_action(
'wp_enqueue_scripts',
function () {
wp_add_inline_script(
'web-worker-offloading',
<<<JS
window.partytown = {
...(window.partytown || {}),
resolveUrl: (url, location, type) => {
if (type === 'script') {
const proxyUrl = new URL('https://my-reverse-proxy.example.com/');
proxyUrl.searchParams.append('url', url.href);
return proxyUrl;
}
return url;
},
};
JS,
'before'
);
}
);
`

There are also many configuration options which are not documented, so refer to the [TypeScript definitions](https://github.com/BuilderIO/partytown/blob/b292a14047a0c12ca05ba97df1833935d42fdb66/src/lib/types.ts#L393-L548).

== Frequently Asked Questions ==

= Why are my offloaded scripts not working and I see a 404 error in the console for `partytown-sandbox-sw.html`? =

If you find that your offloaded scripts aren't working while also seeing a 404 error in the console for a file at `/wp-content/plugins/web-worker-offloading/build/partytown-sandbox-sw.html?1727389399791` then it's likely you have Chrome DevTools open with the "Bypass for Network" toggle enabled in the Application panel.

= Where can I report security bugs? =

The Performance team and WordPress community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, please visit the [WordPress HackerOne](https://hackerone.com/wordpress) program.

= How can I contribute to the plugin? =

Contributions are always welcome! Learn more about how to get involved in the [Core Performance Team Handbook](https://make.wordpress.org/performance/handbook/get-involved/).

The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plugins/web-worker-offloading) is located in the [WordPress/performance](https://github.com/WordPress/performance) repo on GitHub.

== Changelog ==

= 0.1.0 =

* Initial release.
Loading

0 comments on commit 282a068

Please sign in to comment.