Skip to content

Commit

Permalink
feat: preload css (#300)
Browse files Browse the repository at this point in the history
* WIP - preload CSS

Inspired by webpack-contrib/mini-css-extract-plugin#344

* feat: preload css

* test(TestCases): re-enable logging
  • Loading branch information
mrtnvh committed Oct 15, 2020
1 parent c640252 commit fdc5e7b
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 48 deletions.
23 changes: 18 additions & 5 deletions src/index.js
Expand Up @@ -323,10 +323,13 @@ class ExtractCssChunksPlugin {
}
);
const { insert } = this.options;
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 @@ -340,8 +343,7 @@ class ExtractCssChunksPlugin {
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");',
'for(var i = 0; i < existingStyleTags.length; i++) {',
Expand All @@ -352,8 +354,8 @@ class ExtractCssChunksPlugin {
]),
'}',
'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 @@ -383,7 +385,18 @@ class ExtractCssChunksPlugin {
: 'var head = document.getElementsByTagName("head")[0]; 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);',

This comment has been minimized.

Copy link
@SuperOleg39

SuperOleg39 Dec 29, 2020

Hi!

It's look like breaking change, because at line#346 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#357 already append preload link?

This comment has been minimized.

Copy link
@SuperOleg39

SuperOleg39 Dec 29, 2020

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)

This comment has been minimized.

Copy link
@chinaso2018

chinaso2018 Jan 12, 2021

Hi!
we had a problem in nuxtjs which Integrated this plugin. we got a page with A.css ->B.css in head,but because of line#396 got B.css -> A.css in body

-> means order

]),
'}',
]),
'}));',
]),
'}',
Expand Down
3 changes: 2 additions & 1 deletion test/HMR.test.js
Expand Up @@ -30,7 +30,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
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\\">"`;
23 changes: 12 additions & 11 deletions test/cases/insert-function/expected/main.js
Expand Up @@ -82,6 +82,7 @@
/******/
/******/
/******/ // extract-css-chunks-webpack-plugin CSS loading
/******/ var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());
/******/ var cssChunks = {"1":1};
/******/ if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
/******/ else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
Expand All @@ -92,7 +93,7 @@
/******/ for(var i = 0; i < existingLinkTags.length; i++) {
/******/ 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");
/******/ for(var i = 0; i < existingStyleTags.length; i++) {
Expand All @@ -101,8 +102,8 @@
/******/ if(dataHref === href || dataHref === fullhref) return resolve();
/******/ }
/******/ 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) {
/******/ var request = event && event.target && event.target.src || fullhref;
Expand All @@ -122,16 +123,16 @@
/******/ reference.parentNode.insertBefore(linkTag, reference);
/******/ }
/******/ };
/******/ if (typeof insert === 'function') { insert(linkTag); }
/******/ else { var target = document.querySelector(function insert(linkTag) {
/******/ const reference = document.querySelector('.hot-reload');
/******/
/******/ if (reference) {
/******/ reference.parentNode.insertBefore(linkTag, reference);
/******/ }
/******/ }); target && insert === 'body' ? target && target.insertBefore(linkTag,target.firstChild) : target.appendChild(linkTag); }
/******/ insert(linkTag);
/******/ }).then(function() {
/******/ installedCssChunks[chunkId] = 0;
/******/ if(supportsPreload) {
/******/ var execLinkTag = document.createElement("link");
/******/ execLinkTag.href = __webpack_require__.p + "" + chunkId + ".css";
/******/ execLinkTag.rel = "stylesheet";
/******/ execLinkTag.type = "text/css";
/******/ document.body.appendChild(execLinkTag);
/******/ }
/******/ }));
/******/ }
/******/
Expand Down
4 changes: 3 additions & 1 deletion test/cases/insert-function/webpack.config.e2e.js
Expand Up @@ -64,7 +64,9 @@ module.exports = {
new Self({
filename: '[name].css',
chunkFilename: '[id].css',
insert: 'body',
insert: (linkTag) => {
document.head.appendChild(linkTag)
},
}),
],
devServer: {
Expand Down
19 changes: 13 additions & 6 deletions test/cases/insert-string/expected/main.js
Expand Up @@ -82,6 +82,7 @@
/******/
/******/
/******/ // extract-css-chunks-webpack-plugin CSS loading
/******/ var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());
/******/ var cssChunks = {"1":1};
/******/ if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
/******/ else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
Expand All @@ -92,7 +93,7 @@
/******/ for(var i = 0; i < existingLinkTags.length; i++) {
/******/ 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");
/******/ for(var i = 0; i < existingStyleTags.length; i++) {
Expand All @@ -101,8 +102,8 @@
/******/ if(dataHref === href || dataHref === fullhref) return resolve();
/******/ }
/******/ 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) {
/******/ var request = event && event.target && event.target.src || fullhref;
Expand All @@ -115,11 +116,17 @@
/******/ };
/******/ linkTag.href = fullhref;
/******/
/******/ var insert = "body";
/******/ if (typeof insert === 'function') { insert(linkTag); }
/******/ else { var target = document.querySelector("body"); target && insert === 'body' ? target && target.insertBefore(linkTag,target.firstChild) : target.appendChild(linkTag); }
/******/ var insert = body;
/******/ insert(linkTag);
/******/ }).then(function() {
/******/ installedCssChunks[chunkId] = 0;
/******/ if(supportsPreload) {
/******/ var execLinkTag = document.createElement("link");
/******/ execLinkTag.href = __webpack_require__.p + "" + chunkId + ".css";
/******/ execLinkTag.rel = "stylesheet";
/******/ execLinkTag.type = "text/css";
/******/ document.body.appendChild(execLinkTag);
/******/ }
/******/ }));
/******/ }
/******/
Expand Down
2 changes: 1 addition & 1 deletion test/cases/insert-string/webpack.config.e2e.js
Expand Up @@ -64,7 +64,7 @@ module.exports = {
new Self({
filename: '[name].css',
chunkFilename: '[id].css',
insert: 'body',
insert: '(linkTag) => { document.head.appendChild(linkTag) }',
}),
],
devServer: {
Expand Down
2 changes: 1 addition & 1 deletion test/cases/publicpath-emptystring/expected/main.css
@@ -1,5 +1,5 @@
body {
background: red;
background-image: url(cd0bb358c45b584743d8ce4991777c42.svg);
background-image: url(c9e192c015437a21dea1faa1d30f4941.svg);
}

@@ -1,5 +1,5 @@
body {
background: green;
background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg);
background-image: url(../../c9e192c015437a21dea1faa1d30f4941.svg);
}

2 changes: 1 addition & 1 deletion test/cases/publicpath-function/expected/nested/style.css
@@ -1,5 +1,5 @@
body {
background: red;
background-image: url(../cd0bb358c45b584743d8ce4991777c42.svg);
background-image: url(../c9e192c015437a21dea1faa1d30f4941.svg);
}

2 changes: 1 addition & 1 deletion test/cases/publicpath-trailing-slash/expected/main.css
@@ -1,5 +1,5 @@
body {
background: red;
background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg);
background-image: url(/static/img/c9e192c015437a21dea1faa1d30f4941.svg);
}

2 changes: 1 addition & 1 deletion test/cases/simple-publicpath/expected/main.css
@@ -1,5 +1,5 @@
body {
background: red;
background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg);
background-image: url(/static/img/c9e192c015437a21dea1faa1d30f4941.svg);
}

2 changes: 1 addition & 1 deletion test/cases/split-chunks/index.js
@@ -1,3 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
// eslint-disable-next-line
import 'bootstrap.css';
import './style.css';
24 changes: 13 additions & 11 deletions test/inject-option.test.js
Expand Up @@ -13,14 +13,14 @@ describe('insert-options', () => {
});
await page.goto('http://localhost:5000/');
});
it('stylesheet was injected into body', async () => {
await page.waitFor(3000);
const bodyHTML = await page.evaluate(() => document.body.innerHTML);

await expect(bodyHTML.indexOf('type="text/css"') > 0).toBe(true);
it('style preload was injected into body', async () => {
// preloaded1 + main + inject
await expect(await page.$$eval('[type="text/css"]', links => links.length)).toEqual(3);
// inject
await expect(await page.$$eval('[rel="preload"]', preloads => preloads.length)).toEqual(1);
});

it('body background style set correctly', async () => {
it('body background style was not set', async () => {
const bodyStyle = await page.evaluate(() =>
getComputedStyle(document.body).getPropertyValue('background-color')
);
Expand All @@ -38,13 +38,14 @@ describe('insert-options', () => {
});
await page.goto('http://localhost:3001/');
});
it('stylesheet was injected into body', async () => {
const bodyHTML = await page.evaluate(() => document.body.innerHTML);

await expect(bodyHTML.indexOf('type="text/css"') > 0).toBe(true);
it('style preload was injected into body', async () => {
// preloaded1 + main + inject
await expect(await page.$$eval('[type="text/css"]', links => links.length)).toEqual(3);
// inject
await expect(await page.$$eval('[rel="preload"]', preloads => preloads.length)).toEqual(1);
});

it('body background style set correctly', async () => {
it('body background style was not set', async () => {
await page.waitFor(4000);
const bodyStyle = await page.evaluate(() =>
getComputedStyle(document.body).getPropertyValue('background-color')
Expand All @@ -55,6 +56,7 @@ describe('insert-options', () => {
});

afterAll(() => {
// eslint-disable-next-line
const childProcess = require('child_process').exec;
childProcess(`kill $(lsof -t -i:3001)`);
childProcess(`kill $(lsof -t -i:5000)`);
Expand Down

0 comments on commit fdc5e7b

Please sign in to comment.