<aside> ๐งโ๐ The critical request chain is necessary to learn what chunks must be preload. Learn more how to identify it:
How to identify critical request chain?
</aside>
To preload critical chunks, means to add preload links for chunks necessary to render each page. Consider the following request chain (unoptimized):2
Preloading chunks will have the following effect:
To achieve this effect, following actions are required:
<aside> ๐ง Webpack comes pre-loaded with Magic Comments feature. It allows hinting Webpack how to name the chunk group and what strategy use to load it. It looks as follows:
import(
/* webpackMode: "lazy", webpackChunkName: "cms" */
'./CmsPage.component'
);
</aside>
Lookup the critical request chains (i.e. for homepage, PLP, PDP), note the chunks loading for each page. For example, these could be: cms
, product
, category
chunks (based on optimized critical request chains).
Create a next extension, declare a build configuration plugin inside. It is possible to use an exiting extension too. Follow these instructions:
How to build configuration plugins?
Create a new file preload.js
along the build configuration plugin, with following content (do not forget cacheGroupWhitelist
with desired names from step 1):
<aside>
๐ง By adding new entries to cacheGroupWhitelist
the named chunk group will be added to a list of pre-loaded ones. Add chunk groups
</aside>
const path = require('path');
class PreloadPlugin {
addPreloadConfig(compilation, htmlPluginData) {
const stats = compilation.getStats().toJson({ all: false, chunkGroups: true });
// vvv Update with desired cache groups from step 1
const cacheGroupWhitelist = ['cms', 'product', 'category'];
const localeCacheGroup = Object.keys(stats.namedChunkGroups).filter((cacheGroup) => /[a-z]{2}_[A-Z]{2}/.test(cacheGroup));
const preloadData = [
...cacheGroupWhitelist,
...localeCacheGroup
].reduce((acc, cacheGroup) => {
return {
...acc,
[cacheGroup]: stats.namedChunkGroups[cacheGroup]?.assets.map(
(asset) => process.env.PUBLIC_URL
? path.join(process.env.PUBLIC_URL, asset)
: `/${asset}`
)
};
}, {});
const scriptHtml = `<script>window.preloadData = ${JSON.stringify(preloadData)};</script>`;
htmlPluginData.html = htmlPluginData.html.replace('<head>', `<head>${scriptHtml}`);
return htmlPluginData;
}
apply(compiler) {
compiler.hooks.compilation.tap(
this.constructor.name,
compilation => {
// This is set in html-webpack-plugin pre-v4.
let hook = compilation.hooks.htmlWebpackPluginAfterHtmlProcessing;
if (!hook) {
const [HtmlWebpackPlugin] = compiler.options.plugins.filter(
(plugin) => plugin.constructor.name === 'HtmlWebpackPlugin');
console.log('Unable to find an instance of HtmlWebpackPlugin in the current compilation.');
hook = HtmlWebpackPlugin.constructor.getHooks(compilation).beforeEmit;
}
hook.tapAsync(
this.constructor.name,
(htmlPluginData, callback) => {
try {
callback(null, this.addPreloadConfig(compilation, htmlPluginData));
} catch (error) {
callback(error);
}
}
);
}
);
}
}
module.exports = { PreloadPlugin };
<aside>
๐ง This plugin uses Webpack stats to retrieve information about files included in specified named chunk groups. It then inlines this information right after the head
element into the HTML document template.
</aside>
Inject the plugin into Webpack configuration, inside of a build configuration plugin:
webpackConfig.plugins.push(new PreloadPlugin());
The template used to generate HTML document on server side, is located in public
folder, in Magento theme mode, the public/index.php
template is used. Append the following code to HTML document after the script tag that declares window.actionName
(we need itโs value to determine current page):
<aside>
๐ง By appending new entries to chunkValidator
the control over preloaded chunks can be obtained. Use it to define conditions for chunk groups to preload.
</aside>
<script>
const chunkValidator = {
// vvv Preload pages conditionally
category: window.actionName.type === 'CATEGORY',
cms: window.actionName.type === 'CMS_PAGE',
product: window.actionName.type === 'PRODUCT',
// vvv Always preload current locale
[window.defaultLocale]: true
};
const appendPreloadLink = (chunk) => {
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'script';
link.href = chunk;
document.head.appendChild(link);
}
if (window.preloadData) {
Object.entries(window.preloadData).forEach(([key, chunks]) => {
if (chunkValidator[key]) {
chunks.forEach((c) => appendPreloadLink(c));
}
});
}
</script>
<aside> ๐ Thatโs how you preload chunks!
</aside>