Skip to content

Commit

Permalink
MWPW-158071: Optimize LCP loading times (#2914)
Browse files Browse the repository at this point in the history
* Optimize LCP loading times

* preload blocks and preloadLinks

* Remove console.log

* Retain current loading order

* Add test for preloading blocks and a marquee dependency

* Rename method
  • Loading branch information
mokimo committed Sep 24, 2024
1 parent 3f86947 commit 75fa60c
Show file tree
Hide file tree
Showing 3 changed files with 503 additions and 36 deletions.
84 changes: 48 additions & 36 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -447,27 +447,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 {
Expand Down Expand Up @@ -733,6 +733,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');
Expand All @@ -746,7 +748,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;
Expand Down Expand Up @@ -1223,41 +1225,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;
}

Expand Down
Loading

0 comments on commit 75fa60c

Please sign in to comment.