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

Add link=preload support #344

Closed
wants to merge 17 commits into from
Closed
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,13 @@ module.exports = {

For long term caching use `filename: "[contenthash].css"`. Optionally add `[name]`.

#### rel="preload" support

On browsers that support `<link rel="preload" as="style">`, then CSS links will be preloaded by default.
This is both to improve page load performance, and addresess Chrome Lighthouse SEO performance audits requiring the use of rel="preload" with asynchronous chunks.

-[Lighthouse article](https://developers.google.com/web/tools/lighthouse/audits/preload)

**webpack.config.js**

```js
Expand Down
22 changes: 18 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,13 @@ class MiniCssExtractPlugin {
}
);

const supportsPreload =
'(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());';
return Template.asString([
source,
'',
`// ${pluginName} CSS loading`,
`var supportsPreload = ${supportsPreload}`,
`var cssChunks = ${JSON.stringify(chunkMap)};`,
'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);',
'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {',
Expand All @@ -338,7 +341,7 @@ class MiniCssExtractPlugin {
Template.indent([
'var tag = existingLinkTags[i];',
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();',
'if((tag.rel === "stylesheet" || tag.rel === "preload") && (dataHref === href || dataHref === fullhref)) return resolve();',
]),
'}',
'var existingStyleTags = document.getElementsByTagName("style");',
Expand All @@ -350,8 +353,8 @@ class MiniCssExtractPlugin {
]),
'}',
'var linkTag = document.createElement("link");',
'linkTag.rel = "stylesheet";',
'linkTag.type = "text/css";',
'linkTag.rel = supportsPreload ? "preload": "stylesheet";',
'supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";',
'linkTag.onload = resolve;',
'linkTag.onerror = function(event) {',
Template.indent([
Expand Down Expand Up @@ -380,7 +383,18 @@ class MiniCssExtractPlugin {
'head.appendChild(linkTag);',
]),
'}).then(function() {',
Template.indent(['installedCssChunks[chunkId] = 0;']),
Template.indent([
'installedCssChunks[chunkId] = 0;',
'if(supportsPreload) {',
Template.indent([
'var execLinkTag = document.createElement("link");',
`execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`,
'execLinkTag.rel = "stylesheet";',
'execLinkTag.type = "text/css";',
'document.body.appendChild(execLinkTag);',

Choose a reason for hiding this comment

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

Hi!

It's look like breaking change, because at line#344 is checking an existing link, and prevent append new link at this case.

But now, even if the link exists on the page, we get an additional unnecessary link.

Maybe better to add extra flag, checking that line#356 already append preload link?

Choose a reason for hiding this comment

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

Also, we had a problem in out internal framework with this changes, because we append preload and stylesheet links directly in document.head, with some custom onload logic, and after package updating, i see a broken integration test with new link tag inside body)

]),
'}',
]),
'}));',
]),
'}',
Expand Down
3 changes: 2 additions & 1 deletion test/HMR.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ describe('HMR', () => {

jest.spyOn(Date, 'now').mockImplementation(() => 1479427200000);

document.head.innerHTML = '<link rel="stylesheet" href="/dist/main.css" />';
document.head.innerHTML =
'<link rel="preload" as="style" href="/dist/main.css" />';
document.body.innerHTML = '<script src="/dist/main.js"></script>';
});

Expand Down
12 changes: 6 additions & 6 deletions test/__snapshots__/HMR.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`HMR should handle error event 1`] = `"[HMR] css reload %s"`;

exports[`HMR should handle error event 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should handle error event 2`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"/dist/main.css\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;

exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`;

Expand All @@ -18,22 +18,22 @@ exports[`HMR should reloads with link without href 2`] = `"<link rel=\\"styleshe

exports[`HMR should reloads with locals 1`] = `"[HMR] Detected local css modules. Reload all css"`;

exports[`HMR should reloads with locals 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should reloads with locals 2`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"/dist/main.css\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;

exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`;

exports[`HMR should reloads with non http/https link href 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"shortcut icon\\" href=\\"data:;base64,=\\">"`;

exports[`HMR should reloads with reloadAll option 1`] = `"[HMR] Reload all css"`;

exports[`HMR should reloads with reloadAll option 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should reloads with reloadAll option 2`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"/dist/main.css\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;

exports[`HMR should works 1`] = `"[HMR] css reload %s"`;

exports[`HMR should works 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should works 2`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"/dist/main.css\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;

exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`;

exports[`HMR should works with multiple updates 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
exports[`HMR should works with multiple updates 2`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"/dist/main.css\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;

exports[`HMR should works with multiple updates 3`] = `"<link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200001\\">"`;
exports[`HMR should works with multiple updates 3`] = `"<link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200000\\"><link rel=\\"preload\\" as=\\"style\\" href=\\"http://localhost/dist/main.css?1479427200001\\">"`;