From 645c945a36f19374e7aa8ffd0f06c6506b643543 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 24 Jan 2024 16:29:44 -0800 Subject: [PATCH 1/7] Break long hydration task in interactivity init --- packages/interactivity/src/init.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js index d749003b86f49..fd3bf272737b6 100644 --- a/packages/interactivity/src/init.js +++ b/packages/interactivity/src/init.js @@ -21,15 +21,25 @@ export const getRegionRootFragment = ( region ) => { return regionRootFragments.get( region ); }; +function yieldToMain() { + return new Promise( ( resolve ) => { + // TODO: Use scheduler.yield() when available. + setTimeout( resolve, 0 ); + } ); +} + // Initialize the router with the initial DOM. export const init = async () => { - document - .querySelectorAll( `[data-${ directivePrefix }-interactive]` ) - .forEach( ( node ) => { - if ( ! hydratedIslands.has( node ) ) { - const fragment = getRegionRootFragment( node ); - const vdom = toVdom( node ); - hydrate( vdom, fragment ); - } - } ); + const nodes = document.querySelectorAll( + `[data-${ directivePrefix }-interactive]` + ); + + for ( const node of nodes ) { + if ( ! hydratedIslands.has( node ) ) { + await yieldToMain(); + const fragment = getRegionRootFragment( node ); + const vdom = toVdom( node ); + hydrate( vdom, fragment ); + } + } }; From 6ac9d62d41e9f32971b5bf8df37ae73a000f3a6a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 25 Jan 2024 09:01:00 -0800 Subject: [PATCH 2/7] Yield to main between toVdom() and hydrate() Co-authored-by: Luis Herranz --- packages/interactivity/src/init.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js index fd3bf272737b6..839302c4f8c6b 100644 --- a/packages/interactivity/src/init.js +++ b/packages/interactivity/src/init.js @@ -39,6 +39,7 @@ export const init = async () => { await yieldToMain(); const fragment = getRegionRootFragment( node ); const vdom = toVdom( node ); + await yieldToMain(); hydrate( vdom, fragment ); } } From cffdb4dd93d962957e7c01e3e236eff7505f6f1a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 25 Jan 2024 09:12:19 -0800 Subject: [PATCH 3/7] Update changelog --- packages/interactivity/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index 0c627c9f640c5..49b6b28b91a96 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Enhancements + +- Break up init with yielding to main to prevent long task from hydration. ([#58227](https://github.com/WordPress/gutenberg/pull/58227)) + ## 4.0.0 (2024-01-24) ### Enhancements From 4cc10a7712521cef1aeba407a39788b44ec31262 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 25 Jan 2024 13:09:33 -0800 Subject: [PATCH 4/7] Delay hydration of nodes until they near the viewport --- packages/interactivity/src/init.js | 41 +++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js index 839302c4f8c6b..79695a3adddab 100644 --- a/packages/interactivity/src/init.js +++ b/packages/interactivity/src/init.js @@ -30,17 +30,44 @@ function yieldToMain() { // Initialize the router with the initial DOM. export const init = async () => { + const pendingNodes = new Set(); + + const intersectionObserver = new window.IntersectionObserver( + async ( entries ) => { + for ( const entry of entries ) { + if ( ! entry.isIntersecting ) { + continue; + } + + const node = entry.target; + intersectionObserver.unobserve( node ); + pendingNodes.delete( node ); + if ( pendingNodes.size === 0 ) { + intersectionObserver.disconnect(); + } + + if ( ! hydratedIslands.has( node ) ) { + await yieldToMain(); + const fragment = getRegionRootFragment( node ); + const vdom = toVdom( node ); + await yieldToMain(); + hydrate( vdom, fragment ); + } + } + }, + { + root: null, // To watch for intersection relative to the device's viewport. + rootMargin: '100% 0% 100% 0%', // Intersect when within 1 viewport approaching from top or bottom. + threshold: 0.0, // As soon as even one pixel is visible. + } + ); + const nodes = document.querySelectorAll( `[data-${ directivePrefix }-interactive]` ); for ( const node of nodes ) { - if ( ! hydratedIslands.has( node ) ) { - await yieldToMain(); - const fragment = getRegionRootFragment( node ); - const vdom = toVdom( node ); - await yieldToMain(); - hydrate( vdom, fragment ); - } + pendingNodes.add( node ); + intersectionObserver.observe( node ); } }; From 3770575e95b88703ddc0fc10019d1b73980485ad Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 25 Jan 2024 13:10:03 -0800 Subject: [PATCH 5/7] Yield to main after hydration --- packages/interactivity/src/init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js index 79695a3adddab..8bf2de80196bc 100644 --- a/packages/interactivity/src/init.js +++ b/packages/interactivity/src/init.js @@ -47,11 +47,11 @@ export const init = async () => { } if ( ! hydratedIslands.has( node ) ) { - await yieldToMain(); const fragment = getRegionRootFragment( node ); const vdom = toVdom( node ); await yieldToMain(); hydrate( vdom, fragment ); + await yieldToMain(); } } }, From 1f845c9970693e659f197e80ff755ff97745ec87 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 25 Jan 2024 13:15:21 -0800 Subject: [PATCH 6/7] Check if isInputPending prior to hydration --- packages/interactivity/src/init.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/interactivity/src/init.js b/packages/interactivity/src/init.js index 8bf2de80196bc..d6398da849e84 100644 --- a/packages/interactivity/src/init.js +++ b/packages/interactivity/src/init.js @@ -47,6 +47,9 @@ export const init = async () => { } if ( ! hydratedIslands.has( node ) ) { + while ( window.navigator.scheduling?.isInputPending() ) { + await yieldToMain(); + } const fragment = getRegionRootFragment( node ); const vdom = toVdom( node ); await yieldToMain(); From fb155653f59220afc15432f9fd5b3f331f911827 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 23 Jul 2024 16:11:52 -0700 Subject: [PATCH 7/7] Remove now-discouraged use of isInputPending See https://web.dev/articles/optimize-long-tasks#isinputpending --- packages/interactivity/src/init.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/interactivity/src/init.ts b/packages/interactivity/src/init.ts index 722876a1695d6..43450539cb121 100644 --- a/packages/interactivity/src/init.ts +++ b/packages/interactivity/src/init.ts @@ -46,9 +46,6 @@ export const init = async () => { } if ( ! hydratedIslands.has( node ) ) { - while ( window.navigator.scheduling?.isInputPending() ) { - await splitTask(); - } const fragment = getRegionRootFragment( node ); const vdom = toVdom( node ); await splitTask();