From abf351f119d589c6f7da9ae29ff21f48bdf71b37 Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Mon, 16 Sep 2024 15:43:30 +0200 Subject: [PATCH 1/6] Parallelize loading within processing the sections --- libs/utils/utils.js | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 07fee41492..c1d9bdb248 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1221,38 +1221,39 @@ export function partition(arr, fn) { ); } -async function processSection(section, config, isDoc) { +const decorateInlineFragments = async (section) => { const inlineFrags = [...section.el.querySelectorAll('a[href*="#_inline"]')]; - if (inlineFrags.length) { - const { default: loadInlineFrags } = await import('../blocks/fragment/fragment.js'); - const fragPromises = inlineFrags.map((link) => loadInlineFrags(link)); - await Promise.all(fragPromises); - const newlyDecoratedSection = decorateSection(section.el, section.idx); - section.blocks = newlyDecoratedSection.blocks; - section.preloadLinks = newlyDecoratedSection.preloadLinks; - } - await decoratePlaceholders(section.el, config); + if (!inlineFrags.length) return; + const { default: loadInlineFrags } = await import('../blocks/fragment/fragment.js'); + const fragPromises = inlineFrags.map((link) => loadInlineFrags(link)); + await Promise.all(fragPromises); + const newlyDecoratedSection = decorateSection(section.el, section.idx); + section.blocks = newlyDecoratedSection.blocks; + section.preloadLinks = newlyDecoratedSection.preloadLinks; +}; +async function processSection(section, config, isDoc) { + await decorateInlineFragments(section); + const tasks = [ + decoratePlaceholders(section.el, config), + decorateIcons(section.el, config), + ]; + if (section.classList.contains('marquee') || section.classList.contains('hero-marquee')) { + loadLink('./utils/decorate.js', { rel: 'preload', as: 'script', crossorigin: 'anonymous' }); + } if (section.preloadLinks.length) { const [modals, nonModals] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); - const preloads = nonModals.map((block) => loadBlock(block)); - await Promise.all(preloads); + nonModals.forEach((block) => tasks.push(loadBlock(block))); modals.forEach((block) => loadBlock(block)); } + section.blocks.forEach((block) => tasks.push(loadBlock(block))); - const loaded = section.blocks.map((block) => loadBlock(block)); - - await decorateIcons(section.el, config); - - // Only move on to the next section when all blocks are loaded. - await Promise.all(loaded); + await Promise.all(tasks); // Show the section when all blocks inside are done. delete section.el.dataset.status; - if (isDoc && section.el.dataset.idx === '0') { - await loadPostLCP(config); - } + if (isDoc && section.el.dataset.idx === '0') await loadPostLCP(config); delete section.el.dataset.idx; From a555edaacba69d760e9dcedded73cf6a212f7a88 Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Mon, 16 Sep 2024 17:00:29 +0200 Subject: [PATCH 2/6] Preload decorate.js --- libs/utils/utils.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index c1d9bdb248..62f2b4f958 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -459,6 +459,9 @@ export async function loadBlock(block) { const base = miloLibs && MILO_BLOCKS.includes(name) ? miloLibs : codeRoot; let path = `${base}/blocks/${name}`; + if (name === 'marquee' || name === 'hero-marquee') { + loadLink(`${base}/utils/decorate.js`, { rel: 'preload', as: 'script', crossorigin: 'anonymous' }); + } if (mep?.blocks?.[name]) path = mep.blocks[name]; const blockPath = `${path}/${name}`; @@ -466,7 +469,6 @@ export async function loadBlock(block) { const styleLoaded = hasStyles && new Promise((resolve) => { loadStyle(`${blockPath}.css`, resolve); }); - const scriptLoaded = new Promise((resolve) => { (async () => { try { @@ -1238,9 +1240,6 @@ async function processSection(section, config, isDoc) { decoratePlaceholders(section.el, config), decorateIcons(section.el, config), ]; - if (section.classList.contains('marquee') || section.classList.contains('hero-marquee')) { - loadLink('./utils/decorate.js', { rel: 'preload', as: 'script', crossorigin: 'anonymous' }); - } if (section.preloadLinks.length) { const [modals, nonModals] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); nonModals.forEach((block) => tasks.push(loadBlock(block))); From c7675aa1daea03bb3f08839d4e9e424ca424be80 Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Mon, 16 Sep 2024 21:05:40 +0200 Subject: [PATCH 3/6] Split placeholder decoration --- libs/utils/utils.js | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 62f2b4f958..a9e0a2d78c 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -776,6 +776,7 @@ export async function customFetch({ resource, withCacheRules }) { } const findReplaceableNodes = (area) => { + if (!area) return []; const regex = /{{(.*?)}}|%7B%7B(.*?)%7D%7D/g; const walker = document.createTreeWalker(area, NodeFilter.SHOW_ALL); const nodes = []; @@ -798,16 +799,21 @@ const findReplaceableNodes = (area) => { }; let placeholderRequest; -async function decoratePlaceholders(area, config) { - if (!area) return; +let decoratePlaceholderArea; +let placeholderPath; +async function fetchPlaceholders(area, config) { + if (!area) return null; const nodes = findReplaceableNodes(area); - if (!nodes.length) return; - const placeholderPath = `${config.locale?.contentRoot}/placeholders.json`; + if (!nodes.length) return null; + placeholderPath = `${config.locale?.contentRoot}/placeholders.json`; placeholderRequest = placeholderRequest - || customFetch({ resource: placeholderPath, withCacheRules: true }) - .catch(() => ({})); - const { decoratePlaceholderArea } = await import('../features/placeholders.js'); - await decoratePlaceholderArea({ placeholderPath, placeholderRequest, nodes }); + || customFetch({ resource: placeholderPath, withCacheRules: true }) + .catch(() => ({})); + + return import('../features/placeholders.js') + .then((placeholderModule) => { + decoratePlaceholderArea = placeholderModule.decoratePlaceholderArea; + }); } async function loadFooter() { @@ -1041,7 +1047,14 @@ async function checkForPageMods() { } async function loadPostLCP(config) { - await decoratePlaceholders(document.body.querySelector('header'), config); + await fetchPlaceholders(document.body.querySelector('header'), config); + if (decoratePlaceholderArea) { + await decoratePlaceholderArea({ + placeholderPath, + placeholderRequest, + nodes: findReplaceableNodes(document.body.querySelector('header')), + }); + } if (config.mep?.targetEnabled === 'gnav') { /* c8 ignore next 2 */ const { init } = await import('../features/personalization/personalization.js'); @@ -1237,7 +1250,7 @@ const decorateInlineFragments = async (section) => { async function processSection(section, config, isDoc) { await decorateInlineFragments(section); const tasks = [ - decoratePlaceholders(section.el, config), + fetchPlaceholders(section.el, config), decorateIcons(section.el, config), ]; if (section.preloadLinks.length) { @@ -1249,6 +1262,13 @@ async function processSection(section, config, isDoc) { await Promise.all(tasks); + if (decoratePlaceholderArea) { + await decoratePlaceholderArea({ + placeholderPath, + placeholderRequest, + nodes: findReplaceableNodes(section.el), + }); + } // Show the section when all blocks inside are done. delete section.el.dataset.status; From d9246e2edce1f9ac1f3f468197f68f59e158e56a Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Mon, 16 Sep 2024 22:14:00 +0200 Subject: [PATCH 4/6] Split rendering to before and after a block loads --- libs/utils/utils.js | 53 ++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index a9e0a2d78c..69e3ecfc24 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -756,14 +756,14 @@ function decorateHeader() { if (promo?.length) header.classList.add('has-promo'); } -async function decorateIcons(area, config) { - const icons = area.querySelectorAll('span.icon'); - if (icons.length === 0) return; +let loadIcons; +async function decorateIcons(config) { + if (loadIcons) return loadIcons; const { base } = config; loadStyle(`${base}/features/icons/icons.css`); loadLink(`${base}/img/icons/icons.svg`, { rel: 'preload', as: 'fetch', crossorigin: 'anonymous' }); - const { default: loadIcons } = await import('../features/icons/icons.js'); - await loadIcons(icons, config); + loadIcons = await import('../features/icons/icons.js').default; + return loadIcons; } export async function customFetch({ resource, withCacheRules }) { @@ -801,15 +801,11 @@ const findReplaceableNodes = (area) => { let placeholderRequest; let decoratePlaceholderArea; let placeholderPath; -async function fetchPlaceholders(area, config) { - if (!area) return null; - const nodes = findReplaceableNodes(area); - if (!nodes.length) return null; +async function fetchPlaceholders(config) { placeholderPath = `${config.locale?.contentRoot}/placeholders.json`; placeholderRequest = placeholderRequest || customFetch({ resource: placeholderPath, withCacheRules: true }) .catch(() => ({})); - return import('../features/placeholders.js') .then((placeholderModule) => { decoratePlaceholderArea = placeholderModule.decoratePlaceholderArea; @@ -1047,12 +1043,13 @@ async function checkForPageMods() { } async function loadPostLCP(config) { - await fetchPlaceholders(document.body.querySelector('header'), config); - if (decoratePlaceholderArea) { + const nodes = findReplaceableNodes(document.body.querySelector('header')); + if (nodes.length) await fetchPlaceholders(document.body.querySelector('header'), config); + if (nodes.length && decoratePlaceholderArea) { await decoratePlaceholderArea({ - placeholderPath, placeholderRequest, - nodes: findReplaceableNodes(document.body.querySelector('header')), + nodes, + placeholderPath, }); } if (config.mep?.targetEnabled === 'gnav') { @@ -1249,10 +1246,11 @@ const decorateInlineFragments = async (section) => { async function processSection(section, config, isDoc) { await decorateInlineFragments(section); - const tasks = [ - fetchPlaceholders(section.el, config), - decorateIcons(section.el, config), - ]; + const tasks = []; + const icons = section.el.querySelectorAll('span.icon'); + if (icons.length > 0) tasks.push(decorateIcons(config)); + if (findReplaceableNodes(section.el).length) tasks.push(fetchPlaceholders(section.el, config)); + if (section.preloadLinks.length) { const [modals, nonModals] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); nonModals.forEach((block) => tasks.push(loadBlock(block))); @@ -1262,14 +1260,19 @@ async function processSection(section, config, isDoc) { await Promise.all(tasks); - if (decoratePlaceholderArea) { - await decoratePlaceholderArea({ - placeholderPath, - placeholderRequest, - nodes: findReplaceableNodes(section.el), - }); + const postDecorationTasks = []; + + if (icons.length > 0 && loadIcons) { + postDecorationTasks.push(loadIcons(icons, config)); + } + const nodes = findReplaceableNodes(section.el); + if (nodes.length && decoratePlaceholderArea) { + postDecorationTasks.push( + decoratePlaceholderArea({ placeholderRequest, nodes, placeholderPath }), + ); } - // Show the section when all blocks inside are done. + + await Promise.all(postDecorationTasks); delete section.el.dataset.status; if (isDoc && section.el.dataset.idx === '0') await loadPostLCP(config); From bb2458fdd9ac516b16355bcaa8089960ad3ee4b2 Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Tue, 17 Sep 2024 08:08:23 +0200 Subject: [PATCH 5/6] Restructure and rename a few things --- libs/utils/utils.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 69e3ecfc24..ef8102fc24 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -756,14 +756,14 @@ function decorateHeader() { if (promo?.length) header.classList.add('has-promo'); } -let loadIcons; -async function decorateIcons(config) { - if (loadIcons) return loadIcons; +let decorateIcons; +async function fetchIcons(config) { + if (decorateIcons) return decorateIcons; const { base } = config; loadStyle(`${base}/features/icons/icons.css`); loadLink(`${base}/img/icons/icons.svg`, { rel: 'preload', as: 'fetch', crossorigin: 'anonymous' }); - loadIcons = await import('../features/icons/icons.js').default; - return loadIcons; + return import('../features/icons/icons.js') + .then((mod) => { decorateIcons = mod.default; }); } export async function customFetch({ resource, withCacheRules }) { @@ -1246,25 +1246,21 @@ const decorateInlineFragments = async (section) => { async function processSection(section, config, isDoc) { await decorateInlineFragments(section); + const tasks = []; const icons = section.el.querySelectorAll('span.icon'); - if (icons.length > 0) tasks.push(decorateIcons(config)); + if (icons.length > 0) tasks.push(fetchIcons(config)); if (findReplaceableNodes(section.el).length) tasks.push(fetchPlaceholders(section.el, config)); - if (section.preloadLinks.length) { const [modals, nonModals] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); nonModals.forEach((block) => tasks.push(loadBlock(block))); modals.forEach((block) => loadBlock(block)); } section.blocks.forEach((block) => tasks.push(loadBlock(block))); - await Promise.all(tasks); const postDecorationTasks = []; - - if (icons.length > 0 && loadIcons) { - postDecorationTasks.push(loadIcons(icons, config)); - } + if (icons.length > 0 && decorateIcons) postDecorationTasks.push(decorateIcons(icons, config)); const nodes = findReplaceableNodes(section.el); if (nodes.length && decoratePlaceholderArea) { postDecorationTasks.push( From 7205d99ba7b63a9b3460dd011aa7ae573a96ce2b Mon Sep 17 00:00:00 2001 From: Okan Sahin Date: Tue, 17 Sep 2024 09:50:34 +0200 Subject: [PATCH 6/6] Correctly pass on the config to the placeholders --- libs/utils/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/utils/utils.js b/libs/utils/utils.js index ef8102fc24..57aacc6e73 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -1044,7 +1044,7 @@ async function checkForPageMods() { async function loadPostLCP(config) { const nodes = findReplaceableNodes(document.body.querySelector('header')); - if (nodes.length) await fetchPlaceholders(document.body.querySelector('header'), config); + if (nodes.length) await fetchPlaceholders(config); if (nodes.length && decoratePlaceholderArea) { await decoratePlaceholderArea({ placeholderRequest, @@ -1250,7 +1250,7 @@ async function processSection(section, config, isDoc) { const tasks = []; const icons = section.el.querySelectorAll('span.icon'); if (icons.length > 0) tasks.push(fetchIcons(config)); - if (findReplaceableNodes(section.el).length) tasks.push(fetchPlaceholders(section.el, config)); + if (findReplaceableNodes(section.el).length) tasks.push(fetchPlaceholders(config)); if (section.preloadLinks.length) { const [modals, nonModals] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); nonModals.forEach((block) => tasks.push(loadBlock(block)));