From b34f5b63a385cae4dba12b762d5757ff7978ef34 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jul 2024 18:55:13 +0100 Subject: [PATCH 01/26] fix: Add initial data population in interactivity router --- packages/interactivity-router/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 3bd44c7aebd71f..b647996bcee824 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -104,6 +104,7 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { } const title = dom.querySelector( 'title' )?.innerText; const initialData = parseServerData( dom ); + populateServerData( initialData ); return { regions, head, title, initialData }; }; From 894bc9af3937bf48bc4b670356467ccca271cf05 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Aug 2024 18:09:15 +0100 Subject: [PATCH 02/26] chore: Update element.innerText to element.textContent in head.ts file --- packages/interactivity-router/src/head.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 2bde7cea520404..052da365cdcc35 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -87,7 +87,8 @@ export const fetchHeadAssets = async ( const headElement = headElements.get( attributeValue ); const element = doc.createElement( tagName ); - element.innerText = headElement.text; + element.textContent = headElement.text; + for ( const attr of headElement.tag.attributes ) { element.setAttribute( attr.name, attr.value ); } From d132d26bfb6e810b4b63a9170420bd2d6276bcf1 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Aug 2024 18:09:56 +0100 Subject: [PATCH 03/26] feat: Exclude src and href attributes when copying head element attributes --- packages/interactivity-router/src/head.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 052da365cdcc35..f2177157fd712e 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -90,7 +90,10 @@ export const fetchHeadAssets = async ( element.textContent = headElement.text; for ( const attr of headElement.tag.attributes ) { - element.setAttribute( attr.name, attr.value ); + // don't copy the src or href attribute + if ( attr.name !== 'src' && attr.name !== 'href' ) { + element.setAttribute( attr.name, attr.value ); + } } headTags.push( element ); } ) From 2f8c93bde6bcd1b30f91914a6ffdc3cfec7a1018 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Aug 2024 18:10:25 +0100 Subject: [PATCH 04/26] chore: Populate initial data in interactivity router --- packages/interactivity-router/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index b647996bcee824..f29998d069d6a7 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -112,6 +112,7 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { const renderRegions = ( page: Page ) => { batch( () => { if ( globalThis.IS_GUTENBERG_PLUGIN ) { + populateInitialData( page.initialData ); if ( navigationMode === 'fullPage' ) { // Once this code is tested and more mature, the head should be updated for region based navigation as well. updateHead( page.head ); From d8500ec8cdd657febd6e38c08f388356b642bebf Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Aug 2024 18:10:59 +0100 Subject: [PATCH 05/26] chore: Move Populate initial data in interactivity router --- packages/interactivity-router/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index f29998d069d6a7..529890fdcccb1d 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -111,8 +111,8 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { // Render all interactive regions contained in the given page. const renderRegions = ( page: Page ) => { batch( () => { + populateInitialData( page.initialData ); if ( globalThis.IS_GUTENBERG_PLUGIN ) { - populateInitialData( page.initialData ); if ( navigationMode === 'fullPage' ) { // Once this code is tested and more mature, the head should be updated for region based navigation as well. updateHead( page.head ); From 804b7c19bdf9a0016e0de07e3c988cf526270f68 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Aug 2024 18:35:02 +0100 Subject: [PATCH 06/26] Wait for `load` event of script element before returning from `fetchHeadAssets()` function --- packages/interactivity-router/src/head.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index f2177157fd712e..f1e2601fafec44 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -95,7 +95,14 @@ export const fetchHeadAssets = async ( element.setAttribute( attr.name, attr.value ); } } + headTags.push( element ); + + // wait for the `load` event to fire before appending the element + return new Promise( ( resolve, reject ) => { + element.onload = resolve; + element.onerror = reject; + } ); } ) ); } From 0f2a51badbb07bb10921d3d953249a426d2d7ef9 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 21 Aug 2024 11:03:42 +0100 Subject: [PATCH 07/26] feat: Update head tags to improve prefetching of scripts and stylesheets This commit modifies the `updateHead` function in `head.ts` to improve support for lazy loading of scripts and stylesheets. It preloades the script modules using `modulepreload`, imports the necessary scripts using dynamic imports and adds the `preload` link elements for stylesheets. --- packages/interactivity-router/src/head.ts | 107 +++++++++++---------- packages/interactivity-router/src/index.ts | 27 +++--- 2 files changed, 70 insertions(+), 64 deletions(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index f1e2601fafec44..3ba04e0d5c4c1c 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { headElements } from '.'; + /** * Helper to update only the necessary tags in the head. * @@ -29,6 +34,14 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { } } + await Promise.all( + [ ...headElements.entries() ] + .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) + .map( async ( [ url ] ) => { + await import( /* webpackIgnore: true */ url ); + } ) + ); + // Prepare new assets. const toAppend = [ ...newHeadMap.values() ]; @@ -41,71 +54,59 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { * Fetches and processes head assets (stylesheets and scripts) from a specified document. * * @async - * @param doc The document from which to fetch head assets. It should support standard DOM querying methods. - * @param headElements A map of head elements to modify tracking the URLs of already processed assets to avoid duplicates. - * @param headElements.tag - * @param headElements.text + * @param doc The document from which to fetch head assets. It should support standard DOM querying methods. * * @return Returns an array of HTML elements representing the head assets. */ export const fetchHeadAssets = async ( - doc: Document, - headElements: Map< string, { tag: HTMLElement; text: string } > + doc: Document ): Promise< HTMLElement[] > => { const headTags = []; - const assets = [ - { - tagName: 'style', - selector: 'link[rel=stylesheet]', - attribute: 'href', - }, - { tagName: 'script', selector: 'script[src]', attribute: 'src' }, - ]; - for ( const asset of assets ) { - const { tagName, selector, attribute } = asset; - const tags = doc.querySelectorAll< - HTMLScriptElement | HTMLStyleElement - >( selector ); - // Use Promise.all to wait for fetch to complete - await Promise.all( - Array.from( tags ).map( async ( tag ) => { - const attributeValue = tag.getAttribute( attribute ); - if ( ! headElements.has( attributeValue ) ) { - try { - const response = await fetch( attributeValue ); - const text = await response.text(); - headElements.set( attributeValue, { - tag, - text, - } ); - } catch ( e ) { - // eslint-disable-next-line no-console - console.error( e ); - } - } + const scripts = doc.querySelectorAll< HTMLScriptElement >( + 'script[type="module"][src]' + ); + + Array.from( scripts ).forEach( ( script ) => { + const src = script.getAttribute( 'src' ); + if ( ! headElements.has( src ) ) { + // add the elements to prefetch the module scripts + const link = doc.createElement( 'link' ); + link.rel = 'modulepreload'; + link.href = src; + document.head.append( link ); + headElements.set( src, { tag: script } ); + } + } ); - const headElement = headElements.get( attributeValue ); - const element = doc.createElement( tagName ); - element.textContent = headElement.text; + const stylesheets = doc.querySelectorAll< HTMLScriptElement >( + 'link[rel=stylesheet]' + ); - for ( const attr of headElement.tag.attributes ) { - // don't copy the src or href attribute - if ( attr.name !== 'src' && attr.name !== 'href' ) { - element.setAttribute( attr.name, attr.value ); - } + await Promise.all( + Array.from( stylesheets ).map( async ( tag ) => { + const href = tag.getAttribute( 'href' ); + if ( ! headElements.has( href ) ) { + try { + const response = await fetch( href ); + const text = await response.text(); + headElements.set( href, { + tag, + text, + } ); + } catch ( e ) { + // eslint-disable-next-line no-console + console.error( e ); } + } - headTags.push( element ); + const headElement = headElements.get( href ); + const styleElement = doc.createElement( 'style' ); + styleElement.textContent = headElement.text; - // wait for the `load` event to fire before appending the element - return new Promise( ( resolve, reject ) => { - element.onload = resolve; - element.onerror = reject; - } ); - } ) - ); - } + headTags.push( styleElement ); + } ) + ); return [ doc.querySelector( 'title' ), diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 529890fdcccb1d..3b3e7ff64a7299 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -54,7 +54,10 @@ const navigationMode: 'regionBased' | 'fullPage' = // The cache of visited and prefetched pages, stylesheets and scripts. const pages = new Map< string, Promise< Page | false > >(); -const headElements = new Map< string, { tag: HTMLElement; text: string } >(); +export const headElements = new Map< + string, + { tag: HTMLElement; text?: string } +>(); // Helper to remove domain and hash from the URL. We are only interesting in // caching the path and the query. @@ -87,7 +90,7 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { let head: HTMLElement[]; if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { - head = await fetchHeadAssets( dom, headElements ); + head = await fetchHeadAssets( dom ); regions.body = vdom ? vdom.get( document.body ) : toVdom( dom.body ); @@ -110,12 +113,12 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { // Render all interactive regions contained in the given page. const renderRegions = ( page: Page ) => { - batch( () => { + batch( async () => { populateInitialData( page.initialData ); if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Once this code is tested and more mature, the head should be updated for region based navigation as well. - updateHead( page.head ); + await updateHead( page.head ); const fragment = getRegionRootFragment( document.body ); render( page.regions.body, fragment ); } @@ -172,13 +175,15 @@ window.addEventListener( 'popstate', async () => { if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Cache the scripts. Has to be called before fetching the assets. - [].map.call( document.querySelectorAll( 'script[src]' ), ( script ) => { - headElements.set( script.getAttribute( 'src' ), { - tag: script, - text: script.textContent, - } ); - } ); - await fetchHeadAssets( document, headElements ); + [].map.call( + document.querySelectorAll( 'script[type="module"][src]' ), + ( script ) => { + headElements.set( script.getAttribute( 'src' ), { + tag: script, + } ); + } + ); + await fetchHeadAssets( document ); } } pages.set( From eac0deb884aa9d64f9ee68eb707823a98c22287d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 28 Aug 2024 15:02:59 +0100 Subject: [PATCH 08/26] Do not load interactivity script modules in development mode when full page navigation is enabled --- lib/interactivity-api.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index c00d68bc70e8e2..5ed004eb56831e 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -14,11 +14,14 @@ function gutenberg_reregister_interactivity_script_modules() { wp_deregister_script_module( '@wordpress/interactivity' ); wp_deregister_script_module( '@wordpress/interactivity-router' ); + $experiments = get_option( 'gutenberg-experiments' ); + $full_page_navigation_enabled = isset( $experiments['gutenberg-full-page-client-side-navigation'] ); + wp_register_script_module( '@wordpress/interactivity', gutenberg_url( '/build-module/' . ( SCRIPT_DEBUG ? 'interactivity/debug.min.js' : 'interactivity/index.min.js' ) ), array(), - $default_version + $full_page_navigation_enabled ? null : $default_version ); wp_register_script_module( @@ -31,7 +34,7 @@ function gutenberg_reregister_interactivity_script_modules() { ), '@wordpress/interactivity', ), - $default_version + $full_page_navigation_enabled ? null : $default_version ); } add_action( 'init', 'gutenberg_reregister_interactivity_script_modules' ); From af136bc593b3e0174e3da39b8f2b1cea697100a5 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 28 Aug 2024 15:54:40 +0100 Subject: [PATCH 09/26] Format interactivity-api.php --- lib/interactivity-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index 5ed004eb56831e..c1d9f221a71687 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -14,7 +14,7 @@ function gutenberg_reregister_interactivity_script_modules() { wp_deregister_script_module( '@wordpress/interactivity' ); wp_deregister_script_module( '@wordpress/interactivity-router' ); - $experiments = get_option( 'gutenberg-experiments' ); + $experiments = get_option( 'gutenberg-experiments' ); $full_page_navigation_enabled = isset( $experiments['gutenberg-full-page-client-side-navigation'] ); wp_register_script_module( From 424da1ceef712dbe3c58fe67c993e1d05a3ce8eb Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 11 Sep 2024 17:58:12 +0100 Subject: [PATCH 10/26] Update interactivity script module registration to use version from asset files - Added logic to retrieve version information from `index.min.asset.php` and `router.min.asset.php` files. - Updated `wp_register_script_module` calls to use the retrieved version instead of the default version when full-page navigation is not enabled. --- lib/interactivity-api.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index c1d9f221a71687..71098b487c8b49 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -17,13 +17,21 @@ function gutenberg_reregister_interactivity_script_modules() { $experiments = get_option( 'gutenberg-experiments' ); $full_page_navigation_enabled = isset( $experiments['gutenberg-full-page-client-side-navigation'] ); + $index_path = gutenberg_dir_path() . 'build/interactivity/index.min.asset.php'; + $index_asset = file_exists( $index_path ) ? require $index_path : null; + $index_version = isset( $index_asset['version'] ) ? $index_asset['version'] : $default_version; + wp_register_script_module( '@wordpress/interactivity', gutenberg_url( '/build-module/' . ( SCRIPT_DEBUG ? 'interactivity/debug.min.js' : 'interactivity/index.min.js' ) ), array(), - $full_page_navigation_enabled ? null : $default_version + $full_page_navigation_enabled ? null : $index_version ); + $router_path = gutenberg_dir_path() . 'build/interactivity/router.min.asset.php'; + $router_asset = file_exists( $router_path ) ? require $router_path : null; + $router_version = isset( $router_asset['version'] ) ? $router_asset['version'] : $default_version; + wp_register_script_module( '@wordpress/interactivity-router', gutenberg_url( '/build-module/interactivity-router/index.min.js' ), From 56a9b082911df14b97f94db4c0a1470bd26eca89 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 11:56:10 +0100 Subject: [PATCH 11/26] empty commit From bc0c933c22e6c7fdcaaccc8c83814ea80c4d99d2 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 12:26:21 +0100 Subject: [PATCH 12/26] Rename populateInitialData to populateServerData --- packages/interactivity-router/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 3b3e7ff64a7299..99f40b41520411 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -114,7 +114,7 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { // Render all interactive regions contained in the given page. const renderRegions = ( page: Page ) => { batch( async () => { - populateInitialData( page.initialData ); + populateServerData( page.initialData ); if ( globalThis.IS_GUTENBERG_PLUGIN ) { if ( navigationMode === 'fullPage' ) { // Once this code is tested and more mature, the head should be updated for region based navigation as well. From 78d2d8893eb5451eb5a9950f3635929fe624bd1b Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 12:36:45 +0100 Subject: [PATCH 13/26] remove populateServerData --- packages/interactivity-router/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 99f40b41520411..0f4c301a16c057 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -107,7 +107,6 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { } const title = dom.querySelector( 'title' )?.innerText; const initialData = parseServerData( dom ); - populateServerData( initialData ); return { regions, head, title, initialData }; }; From a5a40cc53d4cc0a84676cdafe6d7ea50bcf5d926 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 13:37:29 +0100 Subject: [PATCH 14/26] try: remove the webpack comment --- packages/interactivity-router/src/head.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 3ba04e0d5c4c1c..9e2536d9c6c56a 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -38,7 +38,7 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { [ ...headElements.entries() ] .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) .map( async ( [ url ] ) => { - await import( /* webpackIgnore: true */ url ); + await import( url ); } ) ); From 3cef459945949f2a87f774406f64406e59188875 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 14:04:44 +0100 Subject: [PATCH 15/26] try: remove the await import() --- packages/interactivity-router/src/head.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 9e2536d9c6c56a..bd5fd25c28215e 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -38,7 +38,8 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { [ ...headElements.entries() ] .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) .map( async ( [ url ] ) => { - await import( url ); + // await import( url ); + return url; } ) ); From 2811a8cf17295e565061e95dd31df1681e423e09 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 15:56:27 +0100 Subject: [PATCH 16/26] bring back the async import --- packages/interactivity-router/src/head.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index bd5fd25c28215e..9e2536d9c6c56a 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -38,8 +38,7 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { [ ...headElements.entries() ] .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) .map( async ( [ url ] ) => { - // await import( url ); - return url; + await import( url ); } ) ); From 79bd1c94219685d7468db3d7cedd9be53b6f4fd1 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 12 Sep 2024 16:07:21 +0100 Subject: [PATCH 17/26] Move headElements to head.ts --- packages/interactivity-router/src/head.ts | 7 +++++-- packages/interactivity-router/src/index.ts | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 9e2536d9c6c56a..2f5edc86e67f76 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -1,7 +1,10 @@ /** - * Internal dependencies + * The cache of prefetched stylesheets and scripts. */ -import { headElements } from '.'; +export const headElements = new Map< + string, + { tag: HTMLElement; text?: string } +>(); /** * Helper to update only the necessary tags in the head. diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 0f4c301a16c057..6ca61f300bfe83 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -6,7 +6,7 @@ import { store, privateApis, getConfig } from '@wordpress/interactivity'; /** * Internal dependencies */ -import { fetchHeadAssets, updateHead } from './head'; +import { fetchHeadAssets, updateHead, headElements } from './head'; const { directivePrefix, @@ -54,10 +54,6 @@ const navigationMode: 'regionBased' | 'fullPage' = // The cache of visited and prefetched pages, stylesheets and scripts. const pages = new Map< string, Promise< Page | false > >(); -export const headElements = new Map< - string, - { tag: HTMLElement; text?: string } ->(); // Helper to remove domain and hash from the URL. We are only interesting in // caching the path and the query. From 06fd98d0cddcd22cfe8f6a5468d834ca40934f55 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 17 Sep 2024 11:54:51 +0100 Subject: [PATCH 18/26] Revert "try: remove the webpack comment" This reverts commit 62e527e1769162894881eba76fc33bd96e3fa7eb. --- packages/interactivity-router/src/head.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 2f5edc86e67f76..ad11cc8c8298ed 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -41,7 +41,7 @@ export const updateHead = async ( newHead: HTMLHeadElement[] ) => { [ ...headElements.entries() ] .filter( ( [ , { tag } ] ) => tag.nodeName === 'SCRIPT' ) .map( async ( [ url ] ) => { - await import( url ); + await import( /* webpackIgnore: true */ url ); } ) ); From 9983180211b0e6af4ca7a2816ed450cfebbbbec4 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 17 Sep 2024 19:58:21 +0100 Subject: [PATCH 19/26] default_version => router_version --- lib/interactivity-api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index 71098b487c8b49..cf74a926c96ded 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -42,7 +42,7 @@ function gutenberg_reregister_interactivity_script_modules() { ), '@wordpress/interactivity', ), - $full_page_navigation_enabled ? null : $default_version + $full_page_navigation_enabled ? null : $router_version ); } add_action( 'init', 'gutenberg_reregister_interactivity_script_modules' ); From 0186d2ae55cc98184489c033a2a40fae02c39142 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 24 Sep 2024 11:15:24 +0100 Subject: [PATCH 20/26] Remove the changes to interactivity-api.php --- lib/interactivity-api.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/interactivity-api.php b/lib/interactivity-api.php index cf74a926c96ded..c00d68bc70e8e2 100644 --- a/lib/interactivity-api.php +++ b/lib/interactivity-api.php @@ -14,24 +14,13 @@ function gutenberg_reregister_interactivity_script_modules() { wp_deregister_script_module( '@wordpress/interactivity' ); wp_deregister_script_module( '@wordpress/interactivity-router' ); - $experiments = get_option( 'gutenberg-experiments' ); - $full_page_navigation_enabled = isset( $experiments['gutenberg-full-page-client-side-navigation'] ); - - $index_path = gutenberg_dir_path() . 'build/interactivity/index.min.asset.php'; - $index_asset = file_exists( $index_path ) ? require $index_path : null; - $index_version = isset( $index_asset['version'] ) ? $index_asset['version'] : $default_version; - wp_register_script_module( '@wordpress/interactivity', gutenberg_url( '/build-module/' . ( SCRIPT_DEBUG ? 'interactivity/debug.min.js' : 'interactivity/index.min.js' ) ), array(), - $full_page_navigation_enabled ? null : $index_version + $default_version ); - $router_path = gutenberg_dir_path() . 'build/interactivity/router.min.asset.php'; - $router_asset = file_exists( $router_path ) ? require $router_path : null; - $router_version = isset( $router_asset['version'] ) ? $router_asset['version'] : $default_version; - wp_register_script_module( '@wordpress/interactivity-router', gutenberg_url( '/build-module/interactivity-router/index.min.js' ), @@ -42,7 +31,7 @@ function gutenberg_reregister_interactivity_script_modules() { ), '@wordpress/interactivity', ), - $full_page_navigation_enabled ? null : $router_version + $default_version ); } add_action( 'init', 'gutenberg_reregister_interactivity_script_modules' ); From a9469d0e19db5a639c6c0ab5580343282584983e Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 24 Sep 2024 15:54:20 +0200 Subject: [PATCH 21/26] Make `renderRegions` async --- packages/interactivity-router/src/index.ts | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/packages/interactivity-router/src/index.ts b/packages/interactivity-router/src/index.ts index 6ca61f300bfe83..5b4eb1cddbd15d 100644 --- a/packages/interactivity-router/src/index.ts +++ b/packages/interactivity-router/src/index.ts @@ -107,20 +107,22 @@ const regionsToVdom: RegionsToVdom = async ( dom, { vdom } = {} ) => { }; // Render all interactive regions contained in the given page. -const renderRegions = ( page: Page ) => { - batch( async () => { - populateServerData( page.initialData ); - if ( globalThis.IS_GUTENBERG_PLUGIN ) { - if ( navigationMode === 'fullPage' ) { - // Once this code is tested and more mature, the head should be updated for region based navigation as well. - await updateHead( page.head ); - const fragment = getRegionRootFragment( document.body ); +const renderRegions = async ( page: Page ) => { + if ( globalThis.IS_GUTENBERG_PLUGIN ) { + if ( navigationMode === 'fullPage' ) { + // Once this code is tested and more mature, the head should be updated for region based navigation as well. + await updateHead( page.head ); + const fragment = getRegionRootFragment( document.body ); + batch( () => { + populateServerData( page.initialData ); render( page.regions.body, fragment ); - } + } ); } - if ( navigationMode === 'regionBased' ) { + } + if ( navigationMode === 'regionBased' ) { + const attrName = `data-${ directivePrefix }-router-region`; + batch( () => { populateServerData( page.initialData ); - const attrName = `data-${ directivePrefix }-router-region`; document .querySelectorAll( `[${ attrName }]` ) .forEach( ( region ) => { @@ -128,11 +130,11 @@ const renderRegions = ( page: Page ) => { const fragment = getRegionRootFragment( region ); render( page.regions[ id ], fragment ); } ); - } - if ( page.title ) { - document.title = page.title; - } - } ); + } ); + } + if ( page.title ) { + document.title = page.title; + } }; /** @@ -156,7 +158,7 @@ window.addEventListener( 'popstate', async () => { const pagePath = getPagePath( window.location.href ); // Remove hash. const page = pages.has( pagePath ) && ( await pages.get( pagePath ) ); if ( page ) { - renderRegions( page ); + await renderRegions( page ); // Update the URL in the state. state.url = window.location.href; } else { From 13c881aee7bdc370a1453ff908484ae0c8097881 Mon Sep 17 00:00:00 2001 From: Michal Date: Wed, 2 Oct 2024 18:39:16 +0100 Subject: [PATCH 22/26] Update TS type of the stylesheets variable Co-authored-by: Jon Surrell --- packages/interactivity-router/src/head.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index ad11cc8c8298ed..fd6d931b02e7ec 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -82,7 +82,7 @@ export const fetchHeadAssets = async ( } } ); - const stylesheets = doc.querySelectorAll< HTMLScriptElement >( + const stylesheets = doc.querySelectorAll< HTMLLinkElement >( 'link[rel=stylesheet]' ); From 0b550f9bc5d1418034055d9f9bbca02409d5cf20 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 2 Oct 2024 18:54:01 +0100 Subject: [PATCH 23/26] Replace Array.from() with direct forEach() on NodeList in head.ts: --- packages/interactivity-router/src/head.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index fd6d931b02e7ec..843007108d23bc 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -70,7 +70,7 @@ export const fetchHeadAssets = async ( 'script[type="module"][src]' ); - Array.from( scripts ).forEach( ( script ) => { + scripts.forEach( ( script ) => { const src = script.getAttribute( 'src' ); if ( ! headElements.has( src ) ) { // add the elements to prefetch the module scripts From cf73a668fb5cae25511b51eaf45638164cb4f08e Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 2 Oct 2024 19:43:28 +0100 Subject: [PATCH 24/26] Check if href is null --- packages/interactivity-router/src/head.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 843007108d23bc..7903a76cd6c3f0 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -89,6 +89,10 @@ export const fetchHeadAssets = async ( await Promise.all( Array.from( stylesheets ).map( async ( tag ) => { const href = tag.getAttribute( 'href' ); + if ( ! href ) { + return; + } + if ( ! headElements.has( href ) ) { try { const response = await fetch( href ); From 375d4eea85cbdc382206c6b12c401ed22f6811a7 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 3 Oct 2024 16:05:14 +0100 Subject: [PATCH 25/26] Clarify why we only prefetch script modules --- packages/interactivity-router/src/head.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/interactivity-router/src/head.ts b/packages/interactivity-router/src/head.ts index 7903a76cd6c3f0..69139348b582ff 100644 --- a/packages/interactivity-router/src/head.ts +++ b/packages/interactivity-router/src/head.ts @@ -66,6 +66,9 @@ export const fetchHeadAssets = async ( ): Promise< HTMLElement[] > => { const headTags = []; + // We only want to fetch module scripts because regular scripts (without + // `async` or `defer` attributes) can depend on the execution of other scripts. + // Scripts found in the head are blocking and must be executed in order. const scripts = doc.querySelectorAll< HTMLScriptElement >( 'script[type="module"][src]' ); From a539b9ac7be8e5fc8abe965b6affecc7b3fbf8a8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 8 Oct 2024 14:03:52 +0100 Subject: [PATCH 26/26] Add changelog --- packages/interactivity-router/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/interactivity-router/CHANGELOG.md b/packages/interactivity-router/CHANGELOG.md index 1f8e91dec05474..43490c1cb2e5f9 100644 --- a/packages/interactivity-router/CHANGELOG.md +++ b/packages/interactivity-router/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Enhancements + +- Improvements to the experimental full-page navigation ([#64067](https://github.com/WordPress/gutenberg/pull/64067)): + - Remove the `src` attributes from prefetched script tags. + - Use `.textContent` instead of `.innerText` to set `