Skip to content

Commit fdc5e7b

Browse files
authored
feat: preload css (#300)
* WIP - preload CSS Inspired by webpack-contrib/mini-css-extract-plugin#344 * feat: preload css * test(TestCases): re-enable logging
1 parent c640252 commit fdc5e7b

File tree

14 files changed

+74
-48
lines changed

14 files changed

+74
-48
lines changed

src/index.js

+18-5
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,13 @@ class ExtractCssChunksPlugin {
323323
}
324324
);
325325
const { insert } = this.options;
326+
const supportsPreload =
327+
'(function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());';
326328
return Template.asString([
327329
source,
328330
'',
329331
`// ${pluginName} CSS loading`,
332+
`var supportsPreload = ${supportsPreload}`,
330333
`var cssChunks = ${JSON.stringify(chunkMap)};`,
331334
'if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);',
332335
'else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {',
@@ -340,8 +343,7 @@ class ExtractCssChunksPlugin {
340343
Template.indent([
341344
'var tag = existingLinkTags[i];',
342345
'var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");',
343-
'if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();',
344-
]),
346+
'if((tag.rel === "stylesheet" || tag.rel === "preload") && (dataHref === href || dataHref === fullhref)) return resolve();', ]),
345347
'}',
346348
'var existingStyleTags = document.getElementsByTagName("style");',
347349
'for(var i = 0; i < existingStyleTags.length; i++) {',
@@ -352,8 +354,8 @@ class ExtractCssChunksPlugin {
352354
]),
353355
'}',
354356
'var linkTag = document.createElement("link");',
355-
'linkTag.rel = "stylesheet";',
356-
'linkTag.type = "text/css";',
357+
'linkTag.rel = supportsPreload ? "preload": "stylesheet";',
358+
'supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";',
357359
'linkTag.onload = resolve;',
358360
'linkTag.onerror = function(event) {',
359361
Template.indent([
@@ -383,7 +385,18 @@ class ExtractCssChunksPlugin {
383385
: 'var head = document.getElementsByTagName("head")[0]; head.appendChild(linkTag)',
384386
]),
385387
'}).then(function() {',
386-
Template.indent(['installedCssChunks[chunkId] = 0;']),
388+
Template.indent([
389+
'installedCssChunks[chunkId] = 0;',
390+
'if(supportsPreload) {',
391+
Template.indent([
392+
'var execLinkTag = document.createElement("link");',
393+
`execLinkTag.href = ${mainTemplate.requireFn}.p + ${linkHrefPath};`,
394+
'execLinkTag.rel = "stylesheet";',
395+
'execLinkTag.type = "text/css";',
396+
'document.body.appendChild(execLinkTag);',
397+
]),
398+
'}',
399+
]),
387400
'}));',
388401
]),
389402
'}',

test/HMR.test.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ describe('HMR', () => {
3030

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

33-
document.head.innerHTML = '<link rel="stylesheet" href="/dist/main.css" />';
33+
document.head.innerHTML =
34+
'<link rel="preload" as="style" href="/dist/main.css" />';
3435
document.body.innerHTML = '<script src="/dist/main.js"></script>';
3536
});
3637

test/__snapshots__/HMR.test.js.snap

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

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

5-
exports[`HMR should handle error event 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
5+
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\\">"`;
66
77
exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`;
88
@@ -18,22 +18,22 @@ exports[`HMR should reloads with link without href 2`] = `"<link rel=\\"styleshe
1818
1919
exports[`HMR should reloads with locals 1`] = `"[HMR] Detected local css modules. Reload all css"`;
2020
21-
exports[`HMR should reloads with locals 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
21+
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\\">"`;
2222
2323
exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`;
2424
2525
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,=\\">"`;
2626
2727
exports[`HMR should reloads with reloadAll option 1`] = `"[HMR] Reload all css"`;
2828
29-
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\\">"`;
29+
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\\">"`;
3030
3131
exports[`HMR should works 1`] = `"[HMR] css reload %s"`;
3232
33-
exports[`HMR should works 2`] = `"<link rel=\\"stylesheet\\" href=\\"/dist/main.css\\"><link rel=\\"stylesheet\\" href=\\"http://localhost/dist/main.css?1479427200000\\">"`;
33+
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\\">"`;
3434
3535
exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`;
3636
37-
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\\">"`;
37+
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\\">"`;
3838
39-
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\\">"`;
39+
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\\">"`;

test/cases/insert-function/expected/main.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
/******/
8383
/******/
8484
/******/ // extract-css-chunks-webpack-plugin CSS loading
85+
/******/ var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());
8586
/******/ var cssChunks = {"1":1};
8687
/******/ if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
8788
/******/ else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
@@ -92,7 +93,7 @@
9293
/******/ for(var i = 0; i < existingLinkTags.length; i++) {
9394
/******/ var tag = existingLinkTags[i];
9495
/******/ var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");
95-
/******/ if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();
96+
/******/ if((tag.rel === "stylesheet" || tag.rel === "preload") && (dataHref === href || dataHref === fullhref)) return resolve();
9697
/******/ }
9798
/******/ var existingStyleTags = document.getElementsByTagName("style");
9899
/******/ for(var i = 0; i < existingStyleTags.length; i++) {
@@ -101,8 +102,8 @@
101102
/******/ if(dataHref === href || dataHref === fullhref) return resolve();
102103
/******/ }
103104
/******/ var linkTag = document.createElement("link");
104-
/******/ linkTag.rel = "stylesheet";
105-
/******/ linkTag.type = "text/css";
105+
/******/ linkTag.rel = supportsPreload ? "preload": "stylesheet";
106+
/******/ supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";
106107
/******/ linkTag.onload = resolve;
107108
/******/ linkTag.onerror = function(event) {
108109
/******/ var request = event && event.target && event.target.src || fullhref;
@@ -122,16 +123,16 @@
122123
/******/ reference.parentNode.insertBefore(linkTag, reference);
123124
/******/ }
124125
/******/ };
125-
/******/ if (typeof insert === 'function') { insert(linkTag); }
126-
/******/ else { var target = document.querySelector(function insert(linkTag) {
127-
/******/ const reference = document.querySelector('.hot-reload');
128-
/******/
129-
/******/ if (reference) {
130-
/******/ reference.parentNode.insertBefore(linkTag, reference);
131-
/******/ }
132-
/******/ }); target && insert === 'body' ? target && target.insertBefore(linkTag,target.firstChild) : target.appendChild(linkTag); }
126+
/******/ insert(linkTag);
133127
/******/ }).then(function() {
134128
/******/ installedCssChunks[chunkId] = 0;
129+
/******/ if(supportsPreload) {
130+
/******/ var execLinkTag = document.createElement("link");
131+
/******/ execLinkTag.href = __webpack_require__.p + "" + chunkId + ".css";
132+
/******/ execLinkTag.rel = "stylesheet";
133+
/******/ execLinkTag.type = "text/css";
134+
/******/ document.body.appendChild(execLinkTag);
135+
/******/ }
135136
/******/ }));
136137
/******/ }
137138
/******/

test/cases/insert-function/webpack.config.e2e.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ module.exports = {
6464
new Self({
6565
filename: '[name].css',
6666
chunkFilename: '[id].css',
67-
insert: 'body',
67+
insert: (linkTag) => {
68+
document.head.appendChild(linkTag)
69+
},
6870
}),
6971
],
7072
devServer: {

test/cases/insert-string/expected/main.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
/******/
8383
/******/
8484
/******/ // extract-css-chunks-webpack-plugin CSS loading
85+
/******/ var supportsPreload = (function() { try { return document.createElement("link").relList.supports("preload"); } catch(e) { return false; }}());
8586
/******/ var cssChunks = {"1":1};
8687
/******/ if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
8788
/******/ else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
@@ -92,7 +93,7 @@
9293
/******/ for(var i = 0; i < existingLinkTags.length; i++) {
9394
/******/ var tag = existingLinkTags[i];
9495
/******/ var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");
95-
/******/ if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return resolve();
96+
/******/ if((tag.rel === "stylesheet" || tag.rel === "preload") && (dataHref === href || dataHref === fullhref)) return resolve();
9697
/******/ }
9798
/******/ var existingStyleTags = document.getElementsByTagName("style");
9899
/******/ for(var i = 0; i < existingStyleTags.length; i++) {
@@ -101,8 +102,8 @@
101102
/******/ if(dataHref === href || dataHref === fullhref) return resolve();
102103
/******/ }
103104
/******/ var linkTag = document.createElement("link");
104-
/******/ linkTag.rel = "stylesheet";
105-
/******/ linkTag.type = "text/css";
105+
/******/ linkTag.rel = supportsPreload ? "preload": "stylesheet";
106+
/******/ supportsPreload ? linkTag.as = "style" : linkTag.type = "text/css";
106107
/******/ linkTag.onload = resolve;
107108
/******/ linkTag.onerror = function(event) {
108109
/******/ var request = event && event.target && event.target.src || fullhref;
@@ -115,11 +116,17 @@
115116
/******/ };
116117
/******/ linkTag.href = fullhref;
117118
/******/
118-
/******/ var insert = "body";
119-
/******/ if (typeof insert === 'function') { insert(linkTag); }
120-
/******/ else { var target = document.querySelector("body"); target && insert === 'body' ? target && target.insertBefore(linkTag,target.firstChild) : target.appendChild(linkTag); }
119+
/******/ var insert = body;
120+
/******/ insert(linkTag);
121121
/******/ }).then(function() {
122122
/******/ installedCssChunks[chunkId] = 0;
123+
/******/ if(supportsPreload) {
124+
/******/ var execLinkTag = document.createElement("link");
125+
/******/ execLinkTag.href = __webpack_require__.p + "" + chunkId + ".css";
126+
/******/ execLinkTag.rel = "stylesheet";
127+
/******/ execLinkTag.type = "text/css";
128+
/******/ document.body.appendChild(execLinkTag);
129+
/******/ }
123130
/******/ }));
124131
/******/ }
125132
/******/

test/cases/insert-string/webpack.config.e2e.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ module.exports = {
6464
new Self({
6565
filename: '[name].css',
6666
chunkFilename: '[id].css',
67-
insert: 'body',
67+
insert: '(linkTag) => { document.head.appendChild(linkTag) }',
6868
}),
6969
],
7070
devServer: {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
22
background: red;
3-
background-image: url(cd0bb358c45b584743d8ce4991777c42.svg);
3+
background-image: url(c9e192c015437a21dea1faa1d30f4941.svg);
44
}
55

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
22
background: green;
3-
background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg);
3+
background-image: url(../../c9e192c015437a21dea1faa1d30f4941.svg);
44
}
55

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
22
background: red;
3-
background-image: url(../cd0bb358c45b584743d8ce4991777c42.svg);
3+
background-image: url(../c9e192c015437a21dea1faa1d30f4941.svg);
44
}
55

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
22
background: red;
3-
background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg);
3+
background-image: url(/static/img/c9e192c015437a21dea1faa1d30f4941.svg);
44
}
55

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
body {
22
background: red;
3-
background-image: url(/static/img/cd0bb358c45b584743d8ce4991777c42.svg);
3+
background-image: url(/static/img/c9e192c015437a21dea1faa1d30f4941.svg);
44
}
55

test/cases/split-chunks/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
// eslint-disable-next-line import/no-extraneous-dependencies
1+
// eslint-disable-next-line
22
import 'bootstrap.css';
33
import './style.css';

test/inject-option.test.js

+13-11
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ describe('insert-options', () => {
1313
});
1414
await page.goto('http://localhost:5000/');
1515
});
16-
it('stylesheet was injected into body', async () => {
17-
await page.waitFor(3000);
18-
const bodyHTML = await page.evaluate(() => document.body.innerHTML);
19-
20-
await expect(bodyHTML.indexOf('type="text/css"') > 0).toBe(true);
16+
it('style preload was injected into body', async () => {
17+
// preloaded1 + main + inject
18+
await expect(await page.$$eval('[type="text/css"]', links => links.length)).toEqual(3);
19+
// inject
20+
await expect(await page.$$eval('[rel="preload"]', preloads => preloads.length)).toEqual(1);
2121
});
2222

23-
it('body background style set correctly', async () => {
23+
it('body background style was not set', async () => {
2424
const bodyStyle = await page.evaluate(() =>
2525
getComputedStyle(document.body).getPropertyValue('background-color')
2626
);
@@ -38,13 +38,14 @@ describe('insert-options', () => {
3838
});
3939
await page.goto('http://localhost:3001/');
4040
});
41-
it('stylesheet was injected into body', async () => {
42-
const bodyHTML = await page.evaluate(() => document.body.innerHTML);
43-
44-
await expect(bodyHTML.indexOf('type="text/css"') > 0).toBe(true);
41+
it('style preload was injected into body', async () => {
42+
// preloaded1 + main + inject
43+
await expect(await page.$$eval('[type="text/css"]', links => links.length)).toEqual(3);
44+
// inject
45+
await expect(await page.$$eval('[rel="preload"]', preloads => preloads.length)).toEqual(1);
4546
});
4647

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

5758
afterAll(() => {
59+
// eslint-disable-next-line
5860
const childProcess = require('child_process').exec;
5961
childProcess(`kill $(lsof -t -i:3001)`);
6062
childProcess(`kill $(lsof -t -i:5000)`);

0 commit comments

Comments
 (0)