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

Integrate Web Worker Offloading with Rank Math SEO #1685

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from

Conversation

westonruter
Copy link
Member

@westonruter westonruter commented Nov 21, 2024

Summary

Previously #1563 to integrate Web Worker Offloading with WooCommerce.

This integrates Rank Math SEO's Google Analytics integration with Partytown to offload gtag() to a worker. Inline scripts now no longer block a registered script from being offloaded to a worker; any associated inline scripts are also offloaded to a worker.

This is part of #1455.

Diff of Prettier-formatted page with plugin active
--- wwo-disabled.html	2024-11-20 16:55:26.821253220 -0800
+++ wwo-enabled.html	2024-11-20 17:02:26.304266209 -0800
@@ -1070,9 +1070,10 @@
 		<meta name="generator" content="dominant-color-images 1.1.2" />
 		<meta
 			name="generator"
-			content="performance-lab 3.6.1; plugins: auto-sizes, dominant-color-images, embed-optimizer, image-prioritizer, performant-translations, speculation-rules, webp-uploads"
+			content="performance-lab 3.6.1; plugins: auto-sizes, dominant-color-images, embed-optimizer, image-prioritizer, performant-translations, speculation-rules, web-worker-offloading, webp-uploads"
 		/>
 		<meta name="generator" content="performant-translations 1.2.0" />
+		<meta name="generator" content="web-worker-offloading 0.1.1" />
 		<meta name="generator" content="webp-uploads 2.3.0" />
 		<meta name="generator" content="speculation-rules 1.3.1" />
 		<meta name="generator" content="optimization-detective 0.9.0-alpha" />
@@ -1082,8 +1083,9 @@
 			id="google_gtagjs"
 			src="https://www.googletagmanager.com/gtag/js?id=G-RSERXH3Y5M"
 			async
+			type="text/partytown"
 		></script>
-		<script id="google_gtagjs-inline">
+		<script id="google_gtagjs-inline" type="text/partytown">
 			window.dataLayer = window.dataLayer || [];
 			function gtag() {
 				dataLayer.push( arguments );
@@ -1888,6 +1890,288 @@
 			src="http://localhost:8888/wp-content/themes/twentytwentyone/assets/js/responsive-embeds.js?ver=2.4"
 			id="twenty-twenty-one-responsive-embeds-script-js"
 		></script>
+		<script id="web-worker-offloading-js-before">
+			window.partytown = {
+				...( window.partytown || {} ),
+				...{
+					lib: '\/wp-content\/plugins\/web-worker-offloading\/build\/',
+					debug: true,
+					globalFns: [ 'gtag' ],
+					forward: [ 'dataLayer.push' ],
+				},
+			};
+		</script>
+		<script id="web-worker-offloading-js-after">
+			/* Partytown 0.10.2-dev1727590485751 - MIT builder.io */
+			const defaultPartytownForwardPropertySettings = {
+				preserveBehavior: false,
+			};
+
+			const resolvePartytownForwardProperty = (
+				propertyOrPropertyWithSettings
+			) => {
+				if ( 'string' == typeof propertyOrPropertyWithSettings ) {
+					return [
+						propertyOrPropertyWithSettings,
+						defaultPartytownForwardPropertySettings,
+					];
+				}
+				const [
+					property,
+					settings = defaultPartytownForwardPropertySettings,
+				] = propertyOrPropertyWithSettings;
+				return [
+					property,
+					{
+						...defaultPartytownForwardPropertySettings,
+						...settings,
+					},
+				];
+			};
+
+			const arrayMethods = Object.freeze(
+				( ( obj ) => {
+					const properties = new Set();
+					let currentObj = obj;
+					do {
+						Object.getOwnPropertyNames( currentObj ).forEach(
+							( item ) => {
+								'function' == typeof currentObj[ item ] &&
+									properties.add( item );
+							}
+						);
+					} while (
+						( currentObj = Object.getPrototypeOf( currentObj ) ) !==
+						Object.prototype
+					);
+					return Array.from( properties );
+				} )( [] )
+			);
+
+			! ( function (
+				win,
+				doc,
+				nav,
+				top,
+				useAtomics,
+				config,
+				libPath,
+				timeout,
+				scripts,
+				sandbox,
+				mainForwardFn = win,
+				isReady
+			) {
+				function ready() {
+					if ( ! isReady ) {
+						isReady = 1;
+						libPath =
+							( config.lib || '/~partytown/' ) +
+							( false !== config.debug ? 'debug/' : '' );
+						if ( '/' == libPath[ 0 ] ) {
+							scripts = doc.querySelectorAll(
+								'script[type="text/partytown"]'
+							);
+							if ( top != win ) {
+								top.dispatchEvent(
+									new CustomEvent( 'pt1', {
+										detail: win,
+									} )
+								);
+							} else {
+								timeout = setTimeout( fallback, 999999999 );
+								doc.addEventListener( 'pt0', clearFallback );
+								useAtomics
+									? loadSandbox( 1 )
+									: nav.serviceWorker
+									? nav.serviceWorker
+											.register(
+												libPath +
+													( config.swPath ||
+														'partytown-sw.js' ),
+												{
+													scope: libPath,
+												}
+											)
+											.then( function ( swRegistration ) {
+												if ( swRegistration.active ) {
+													loadSandbox();
+												} else if (
+													swRegistration.installing
+												) {
+													swRegistration.installing.addEventListener(
+														'statechange',
+														function ( ev ) {
+															'activated' ==
+																ev.target
+																	.state &&
+																loadSandbox();
+														}
+													);
+												} else {
+													console.warn(
+														swRegistration
+													);
+												}
+											}, console.error )
+									: fallback();
+							}
+						} else {
+							console.warn(
+								'Partytown config.lib url must start with "/"'
+							);
+						}
+					}
+				}
+				function loadSandbox( isAtomics ) {
+					sandbox = doc.createElement(
+						isAtomics ? 'script' : 'iframe'
+					);
+					win._pttab = Date.now();
+					if ( ! isAtomics ) {
+						sandbox.style.display = 'block';
+						sandbox.style.width = '0';
+						sandbox.style.height = '0';
+						sandbox.style.border = '0';
+						sandbox.style.visibility = 'hidden';
+						sandbox.setAttribute( 'aria-hidden', ! 0 );
+					}
+					sandbox.src =
+						libPath +
+						'partytown-' +
+						( isAtomics
+							? 'atomics.js?v=0.10.2-dev1727590485751'
+							: 'sandbox-sw.html?' + win._pttab );
+					doc.querySelector(
+						config.sandboxParent || 'body'
+					).appendChild( sandbox );
+				}
+				function fallback( i, script ) {
+					console.warn( 'Partytown script fallback' );
+					clearFallback();
+					top == win &&
+						( config.forward || [] ).map(
+							function ( forwardProps ) {
+								const [ property ] =
+									resolvePartytownForwardProperty(
+										forwardProps
+									);
+								delete win[ property.split( '.' )[ 0 ] ];
+							}
+						);
+					for ( i = 0; i < scripts.length; i++ ) {
+						script = doc.createElement( 'script' );
+						script.innerHTML = scripts[ i ].innerHTML;
+						script.nonce = config.nonce;
+						doc.head.appendChild( script );
+					}
+					sandbox && sandbox.parentNode.removeChild( sandbox );
+				}
+				function clearFallback() {
+					clearTimeout( timeout );
+				}
+				config = win.partytown || {};
+				top == win &&
+					( config.forward || [] ).map( function ( forwardProps ) {
+						const [
+							property,
+							{ preserveBehavior: preserveBehavior },
+						] = resolvePartytownForwardProperty( forwardProps );
+						mainForwardFn = win;
+						property
+							.split( '.' )
+							.map( function ( _, i, forwardPropsArr ) {
+								mainForwardFn = mainForwardFn[
+									forwardPropsArr[ i ]
+								] =
+									i + 1 < forwardPropsArr.length
+										? mainForwardFn[
+												forwardPropsArr[ i ]
+										  ] ||
+										  ( ( propertyName ) =>
+												arrayMethods.includes(
+													propertyName
+												)
+													? []
+													: {} )(
+												forwardPropsArr[ i + 1 ]
+										  )
+										: ( () => {
+												let originalFunction = null;
+												if ( preserveBehavior ) {
+													const {
+														methodOrProperty:
+															methodOrProperty,
+														thisObject: thisObject,
+													} = ( (
+														window,
+														properties
+													) => {
+														let thisObject = window;
+														for (
+															let i = 0;
+															i <
+															properties.length -
+																1;
+															i += 1
+														) {
+															thisObject =
+																thisObject[
+																	properties[
+																		i
+																	]
+																];
+														}
+														return {
+															thisObject:
+																thisObject,
+															methodOrProperty:
+																properties.length >
+																0
+																	? thisObject[
+																			properties[
+																				properties.length -
+																					1
+																			]
+																	  ]
+																	: void 0,
+														};
+													} )( win, forwardPropsArr );
+													'function' ==
+														typeof methodOrProperty &&
+														( originalFunction = (
+															...args
+														) =>
+															methodOrProperty.apply(
+																thisObject,
+																...args
+															) );
+												}
+												return function () {
+													let returnValue;
+													originalFunction &&
+														( returnValue =
+															originalFunction(
+																arguments
+															) );
+													( win._ptf =
+														win._ptf || [] ).push(
+														forwardPropsArr,
+														arguments
+													);
+													return returnValue;
+												};
+										  } )();
+							} );
+					} );
+				if ( 'complete' == doc.readyState ) {
+					ready();
+				} else {
+					win.addEventListener( 'DOMContentLoaded', ready );
+					win.addEventListener( 'load', ready );
+				}
+			} )( window, document, navigator, top, window.crossOriginIsolated );
+		</script>
 		<script type="module">
 			console.log(
 				'[Optimization Detective] Inspect URL Metrics: ' + '

Looking at the network logs:

Without plugin active:

image

With plugin active:

image

Relevant technical choices

The integration is not targeting the \RankMath\Analytics\GTag::enqueue_gtag_js() code which is only used for WP<5.7. In WP 5.7, the wp_script_attributes and wp_inline_script_attributes filters were introduced, and Rank Math then deemed it preferable to use wp_print_script_tag() and wp_print_inline_script_tag() rather than wp_enqueue_script() and wp_add_inline_script(), respectively. Since Web Worker Offloading requires WP 6.5+, there is no point to integrate with the pre-5.7 code in Rank Math.

@westonruter westonruter added [Type] Enhancement A suggestion for improvement of an existing feature [Plugin] Web Worker Offloading Issues for the Web Worker Offloading plugin. labels Nov 21, 2024
Copy link

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Web Worker Offloading Issues for the Web Worker Offloading plugin. [Type] Enhancement A suggestion for improvement of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant