From ba8660428c98791e479c3395024fd8e315cc4176 Mon Sep 17 00:00:00 2001 From: Maarten Van Hoof Date: Mon, 12 Oct 2020 21:39:40 +0200 Subject: [PATCH 1/3] WIP - preload CSS Inspired by https://github.com/webpack-contrib/mini-css-extract-plugin/pull/344 --- src/index.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index fe1b2f92..6d711ad7 100644 --- a/src/index.js +++ b/src/index.js @@ -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]) {', @@ -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++) {', @@ -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([ @@ -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);', + ]), + '}', + ]), '}));', ]), '}', From b08a525b300bf0949874d57c50e57dd0c9dff0c5 Mon Sep 17 00:00:00 2001 From: Maarten Van Hoof Date: Wed, 14 Oct 2020 22:19:46 +0200 Subject: [PATCH 2/3] feat: preload css --- test/HMR.test.js | 3 ++- test/TestCases.test.js | 16 ++++++------- test/__snapshots__/HMR.test.js.snap | 12 +++++----- test/cases/insert-function/expected/main.js | 23 +++++++++--------- .../insert-function/webpack.config.e2e.js | 4 +++- test/cases/insert-string/expected/main.js | 19 ++++++++++----- .../cases/insert-string/webpack.config.e2e.js | 2 +- .../publicpath-emptystring/expected/main.css | 2 +- .../expected/nested/again/style.css | 2 +- .../expected/nested/style.css | 2 +- .../expected/main.css | 2 +- .../cases/simple-publicpath/expected/main.css | 2 +- test/cases/split-chunks/index.js | 2 +- test/inject-option.test.js | 24 ++++++++++--------- 14 files changed, 64 insertions(+), 51 deletions(-) diff --git a/test/HMR.test.js b/test/HMR.test.js index 2a597ddc..39947d77 100644 --- a/test/HMR.test.js +++ b/test/HMR.test.js @@ -30,7 +30,8 @@ describe('HMR', () => { jest.spyOn(Date, 'now').mockImplementation(() => 1479427200000); - document.head.innerHTML = ''; + document.head.innerHTML = + ''; document.body.innerHTML = ''; }); diff --git a/test/TestCases.test.js b/test/TestCases.test.js index 0f98df47..0b2a4f6e 100644 --- a/test/TestCases.test.js +++ b/test/TestCases.test.js @@ -78,14 +78,14 @@ describe('TestCases', () => { done(); // eslint-disable-next-line no-console - console.log( - stats.toString({ - context: path.resolve(__dirname, '..'), - chunks: true, - chunkModules: true, - modules: false, - }) - ); + // console.log( + // stats.toString({ + // context: path.resolve(__dirname, '..'), + // chunks: true, + // chunkModules: true, + // modules: false, + // }) + // ); if (stats.hasErrors()) { done( diff --git a/test/__snapshots__/HMR.test.js.snap b/test/__snapshots__/HMR.test.js.snap index 269260d0..7d1f41a1 100644 --- a/test/__snapshots__/HMR.test.js.snap +++ b/test/__snapshots__/HMR.test.js.snap @@ -2,7 +2,7 @@ exports[`HMR should handle error event 1`] = `"[HMR] css reload %s"`; -exports[`HMR should handle error event 2`] = `""`; +exports[`HMR should handle error event 2`] = `""`; exports[`HMR should reloads with # link href 1`] = `"[HMR] css reload %s"`; @@ -18,7 +18,7 @@ exports[`HMR should reloads with link without href 2`] = `""`; +exports[`HMR should reloads with locals 2`] = `""`; exports[`HMR should reloads with non http/https link href 1`] = `"[HMR] css reload %s"`; @@ -26,14 +26,14 @@ exports[`HMR should reloads with non http/https link href 2`] = `""`; +exports[`HMR should reloads with reloadAll option 2`] = `""`; exports[`HMR should works 1`] = `"[HMR] css reload %s"`; -exports[`HMR should works 2`] = `""`; +exports[`HMR should works 2`] = `""`; exports[`HMR should works with multiple updates 1`] = `"[HMR] css reload %s"`; -exports[`HMR should works with multiple updates 2`] = `""`; +exports[`HMR should works with multiple updates 2`] = `""`; -exports[`HMR should works with multiple updates 3`] = `""`; +exports[`HMR should works with multiple updates 3`] = `""`; diff --git a/test/cases/insert-function/expected/main.js b/test/cases/insert-function/expected/main.js index 507701ba..7c4ef54a 100644 --- a/test/cases/insert-function/expected/main.js +++ b/test/cases/insert-function/expected/main.js @@ -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]) { @@ -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++) { @@ -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; @@ -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); +/******/ } /******/ })); /******/ } /******/ diff --git a/test/cases/insert-function/webpack.config.e2e.js b/test/cases/insert-function/webpack.config.e2e.js index f35a1c97..636f8479 100644 --- a/test/cases/insert-function/webpack.config.e2e.js +++ b/test/cases/insert-function/webpack.config.e2e.js @@ -64,7 +64,9 @@ module.exports = { new Self({ filename: '[name].css', chunkFilename: '[id].css', - insert: 'body', + insert: (linkTag) => { + document.head.appendChild(linkTag) + }, }), ], devServer: { diff --git a/test/cases/insert-string/expected/main.js b/test/cases/insert-string/expected/main.js index 78886571..003729a2 100644 --- a/test/cases/insert-string/expected/main.js +++ b/test/cases/insert-string/expected/main.js @@ -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]) { @@ -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++) { @@ -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; @@ -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); +/******/ } /******/ })); /******/ } /******/ diff --git a/test/cases/insert-string/webpack.config.e2e.js b/test/cases/insert-string/webpack.config.e2e.js index c18b3591..504c5bf1 100644 --- a/test/cases/insert-string/webpack.config.e2e.js +++ b/test/cases/insert-string/webpack.config.e2e.js @@ -64,7 +64,7 @@ module.exports = { new Self({ filename: '[name].css', chunkFilename: '[id].css', - insert: 'body', + insert: '(linkTag) => { document.head.appendChild(linkTag) }', }), ], devServer: { diff --git a/test/cases/publicpath-emptystring/expected/main.css b/test/cases/publicpath-emptystring/expected/main.css index b6282c08..339ed982 100644 --- a/test/cases/publicpath-emptystring/expected/main.css +++ b/test/cases/publicpath-emptystring/expected/main.css @@ -1,5 +1,5 @@ body { background: red; - background-image: url(cd0bb358c45b584743d8ce4991777c42.svg); + background-image: url(c9e192c015437a21dea1faa1d30f4941.svg); } diff --git a/test/cases/publicpath-function/expected/nested/again/style.css b/test/cases/publicpath-function/expected/nested/again/style.css index 3e223c60..c2846f3d 100644 --- a/test/cases/publicpath-function/expected/nested/again/style.css +++ b/test/cases/publicpath-function/expected/nested/again/style.css @@ -1,5 +1,5 @@ body { background: green; - background-image: url(../../cd0bb358c45b584743d8ce4991777c42.svg); + background-image: url(../../c9e192c015437a21dea1faa1d30f4941.svg); } diff --git a/test/cases/publicpath-function/expected/nested/style.css b/test/cases/publicpath-function/expected/nested/style.css index 147ac268..a6f3d9ec 100644 --- a/test/cases/publicpath-function/expected/nested/style.css +++ b/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); } diff --git a/test/cases/publicpath-trailing-slash/expected/main.css b/test/cases/publicpath-trailing-slash/expected/main.css index 0e1e2d07..6d14c42a 100644 --- a/test/cases/publicpath-trailing-slash/expected/main.css +++ b/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); } diff --git a/test/cases/simple-publicpath/expected/main.css b/test/cases/simple-publicpath/expected/main.css index 0e1e2d07..6d14c42a 100644 --- a/test/cases/simple-publicpath/expected/main.css +++ b/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); } diff --git a/test/cases/split-chunks/index.js b/test/cases/split-chunks/index.js index b90fdc81..83d40d8e 100644 --- a/test/cases/split-chunks/index.js +++ b/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'; diff --git a/test/inject-option.test.js b/test/inject-option.test.js index ac8bf1ac..e66dfe45 100644 --- a/test/inject-option.test.js +++ b/test/inject-option.test.js @@ -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') ); @@ -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') @@ -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)`); From 53b531c6d72c59617b33235ea37681279e5f21fc Mon Sep 17 00:00:00 2001 From: Maarten Van Hoof Date: Wed, 14 Oct 2020 22:30:15 +0200 Subject: [PATCH 3/3] test(TestCases): re-enable logging --- test/TestCases.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/TestCases.test.js b/test/TestCases.test.js index 0b2a4f6e..0f98df47 100644 --- a/test/TestCases.test.js +++ b/test/TestCases.test.js @@ -78,14 +78,14 @@ describe('TestCases', () => { done(); // eslint-disable-next-line no-console - // console.log( - // stats.toString({ - // context: path.resolve(__dirname, '..'), - // chunks: true, - // chunkModules: true, - // modules: false, - // }) - // ); + console.log( + stats.toString({ + context: path.resolve(__dirname, '..'), + chunks: true, + chunkModules: true, + modules: false, + }) + ); if (stats.hasErrors()) { done(