Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MWPW-158071: Optimize LCP loading times #2914

Merged
merged 6 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 48 additions & 36 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -731,6 +731,8 @@ function decorateDefaults(el) {
}

function decorateHeader() {
const breadcrumbs = document.querySelector('.breadcrumbs');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that if a breadcrumbs is authored as first block, we immediately load it. The global-navigation should take care of this and on L752 we move the breadcrumbs into the header if (breadcrumbs) header.append(breadcrumbs);

Copy link
Contributor Author

@mokimo mokimo Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mokimo where are you seeing that we immediately load this? For me it looks like it was already working properly before any change here. 😅

Copy link
Contributor Author

@mokimo mokimo Sep 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a test experience @vhargrave https://main--milo--adobecom.aem.page/drafts/osahin/education

When you turn the header off, it treats the breadcrumbs as block, which 404s. PS: I noticed my comment might have been a bit misleading, the issue is that we try to load it as a block, but if there is no header - there is no breadcrumbs, so we should remove it to safeguard authoring mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, i see thanks 👍

breadcrumbs?.remove();
const header = document.querySelector('header');
if (!header) return;
const headerMeta = getMetadata('header');
Expand All @@ -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;
Expand Down Expand Up @@ -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),
mokimo marked this conversation as resolved.
Show resolved Hide resolved
]);
const loadBlocks = [...stylePromises];
mokimo marked this conversation as resolved.
Show resolved Hide resolved
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
Loading