diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 07fee41492..d8102b6121 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -446,27 +446,27 @@ export async function loadTemplate() { await Promise.all([styleLoaded, scriptLoaded]); } -export async function loadBlock(block) { - if (block.classList.contains('hide-block')) { - block.remove(); - return null; - } - +function getBlockData(block) { const name = block.classList[0]; - const hasStyles = AUTO_BLOCKS.find((ab) => Object.keys(ab).includes(name))?.styles ?? true; const { miloLibs, codeRoot, mep } = getConfig(); - const base = miloLibs && MILO_BLOCKS.includes(name) ? miloLibs : codeRoot; let path = `${base}/blocks/${name}`; - if (mep?.blocks?.[name]) path = mep.blocks[name]; - const blockPath = `${path}/${name}`; + const hasStyles = AUTO_BLOCKS.find((ab) => Object.keys(ab).includes(name))?.styles ?? true; + + return { blockPath, name, hasStyles }; +} +export async function loadBlock(block) { + if (block.classList.contains('hide-block')) { + block.remove(); + return null; + } + const { name, blockPath, hasStyles } = getBlockData(block); const styleLoaded = hasStyles && new Promise((resolve) => { loadStyle(`${blockPath}.css`, resolve); }); - const scriptLoaded = new Promise((resolve) => { (async () => { try { @@ -731,6 +731,8 @@ function decorateDefaults(el) { } function decorateHeader() { + const breadcrumbs = document.querySelector('.breadcrumbs'); + breadcrumbs?.remove(); const header = document.querySelector('header'); if (!header) return; const headerMeta = getMetadata('header'); @@ -744,7 +746,7 @@ function decorateHeader() { || getConfig().breadcrumbs; if (metadataConfig === 'off') return; const baseBreadcrumbs = getMetadata('breadcrumbs-base')?.length; - const breadcrumbs = document.querySelector('.breadcrumbs'); + const autoBreadcrumbs = getMetadata('breadcrumbs-from-url') === 'on'; const dynamicNavActive = getMetadata('dynamic-nav') === 'on' && window.sessionStorage.getItem('gnavSource') !== null; @@ -1221,41 +1223,51 @@ export function partition(arr, fn) { ); } -async function processSection(section, config, isDoc) { - 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; +const preloadBlockResources = (blocks = []) => blocks.map((block) => { + if (block.classList.contains('hide-block')) return null; + const { blockPath, hasStyles, name } = getBlockData(block); + if (['marquee', 'hero-marquee'].includes(name)) { + loadLink(`${getConfig().base}/utils/decorate.js`, { rel: 'preload', as: 'script', crossorigin: 'anonymous' }); } - await decoratePlaceholders(section.el, config); + loadLink(`${blockPath}.js`, { rel: 'preload', as: 'script', crossorigin: 'anonymous' }); + return hasStyles && new Promise((resolve) => { loadStyle(`${blockPath}.css`, resolve); }); +}).filter(Boolean); + +async function resolveInlineFrags(section) { + const inlineFrags = [...section.el.querySelectorAll('a[href*="#_inline"]')]; + 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 resolveInlineFrags(section); + const firstSection = section.el.dataset.idx === '0'; + const stylePromises = firstSection ? preloadBlockResources(section.blocks) : []; + preloadBlockResources(section.preloadLinks); + await Promise.all([ + decoratePlaceholders(section.el, config), + decorateIcons(section.el, config), + ]); + const loadBlocks = [...stylePromises]; 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); + const [modals, blocks] = partition(section.preloadLinks, (block) => block.classList.contains('modal')); + await Promise.all(blocks.map((block) => loadBlock(block))); modals.forEach((block) => loadBlock(block)); } - const loaded = section.blocks.map((block) => loadBlock(block)); - - await decorateIcons(section.el, config); + section.blocks.forEach((block) => loadBlocks.push(loadBlock(block))); // Only move on to the next section when all blocks are loaded. - await Promise.all(loaded); + await Promise.all(loadBlocks); - // 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 && firstSection) await loadPostLCP(config); delete section.el.dataset.idx; - return section.blocks; } diff --git a/test/utils/mocks/marquee.html b/test/utils/mocks/marquee.html new file mode 100644 index 0000000000..c066a7cb55 --- /dev/null +++ b/test/utils/mocks/marquee.html @@ -0,0 +1,441 @@ +
+
+

Marquee (small)

+
+
+
+ + + mock + +
+
+
+
+

DETAIL M BOLD 12/15 OPTIONAL

+

Heading XL 28/35 small light left

+

Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore + et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Lorem ipsum

+
+
+ + + mock + +
+
+
+ +
+
+
+ + + mock + +
+
+
+
+ + + mock + + Link +
+
+

+

Heading XL 28/35 small dark right

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Call to action

+
+
+
+
+

Marquee (medium) - 3 viewport images

+
+
+
+ + + mock + +
+
+ + + mock + +
+
+ + + mock + +
+
+
+
+

+ + + mock + +

+

DETAIL M BOLD 12/15 OPTIONAL

+

Heading XL 28/35 medium light left

+

Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed + do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud + exercitation.

+

Learn more Text + link asdfasd

+
+
+ + + mock + +
+
+
+
+
+
+ + + mock + +
+
+
+
+ + + mock + +
+
+

+ + + mock + +

+

DETAIL M BOLD 12/15 OPTIONAL

+

Heading XL 28/35 medium dark right

+

Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed + do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud + exercitation.

+

Learn more Text + link asdfasd

+
+
+
+

Marquee (large)

+
+
+
+ + + mock + +
+
+
+
+

Heading XXL 36/45 large light left

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Secondary action Call + to action

+

Duis aute irure dolor in reprehenderit.

+
+
+
+
+
+
+
+ + + mock + +
+
+
+
+
+

Heading XXL 36/45 large dark right

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Lorem ipsum Call to + action

+
+
+
+

Marquee (quiet)

+
+
+
+
Detail
+

Marquee quiet

+

Marquee’s variants are small, medium, large (default), quiet, and inline - This is example of body copy that + should fit well here. It is recommended to keep text below 230 characters in this body aera.

+

Watch the video

+
+
+
+

Marquee (inline)

+
+
+
+
Detail
+

Marquee inline

+

Marquee’s variants are small, medium, large (default), quiet, and inline - This is example of body copy that + should fit well here. It is recommended to keep text below 230 characters in this body aera.

+
+
+
+
+ +

Marquee (split)

+

small

+
+
+
#000000
+
+
+
+

Marquee Split ½ dark

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Secondary action Call + to action

+
+
+ + + mock + + Photo by First Name Last Name +
+
+
+

Medium split medium light

+
+
+
#fafafa
+
+
+
+

Marquee Split ½ light

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Secondary action Call + to action

+
+
+ + + mock + + Photo by First Name Last Name +
+
+
+

Marquee split large

+
+
+
#000000
+
+
+
+

Marquee Split ½ dark

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore + magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Secondary action Call + to action

+
+
+ + + mock + + Photo by First Name Last Name +
+
+
+ + +

Marquee per-viewport background images

+

2 viewport images

+
+
+
+ + + + + + +
+
+ + + + + + +
+
+
+
+

Standing out amid a sea of labels.

+

Wine industry leader E. & J. Gallo leans on design to keep its 130+ brands competitive.

+

Watch + Now

+
+
+
+

3 viewport images, no body copy

+
+
+
+ + + + + + +
+
+ + + + + + +
+
+ + + + + + +
+
+
+
+

Standing out amid a sea of labels.

+

Watch + Now

+
+
+
+

Marquee background image focal point

+
+
+
+

+ + + + + + +

+

right, bottom

+
+
+
+
+

+ + + + + + +

+

This is my detail

+

Heading XL Marquee standard medium left

+

Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Lorem ipsum Learn + more Text link

+
+
+ + + + + + +
+
+
+

Catalog Marquee

+
+
+
linear-gradient(90deg, rgba(226,106,96,1) 0%, rgba(228,170,166,1) 51%, + rgba(163,243,120,1) 100%)
+
+
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+

Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.

+


Includes:

+

+ Acrobat Pro + Acrobat +

+
+
+
+
diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index 669817fe6b..d82a7123f0 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -60,6 +60,20 @@ describe('Utils', () => { expect(resp.json()).to.be.true; }); + describe('core-functionality', () => { + it('preloads blocks for performance reasons', async () => { + document.head.innerHTML = head; + document.body.innerHTML = await readFile({ path: './mocks/marquee.html' }); + await utils.loadArea(); + const scriptPreload = document.head.querySelector('link[href*="/libs/blocks/marquee/marquee.js"]'); + const marqueeDecoratePreload = document.head.querySelector('link[href*="/libs/utils/decorate.js"]'); + const stylePreload = document.head.querySelector('link[href*="/libs/blocks/marquee/marquee.css"]'); + expect(marqueeDecoratePreload).to.exist; + expect(scriptPreload).to.exist; + expect(stylePreload).to.exist; + }); + }); + describe('with body', () => { beforeEach(async () => { window.fetch = mockFetch({ payload: { data: '' } });