From a0ab666e1baeb881c1b398a08a83bcacb394fee4 Mon Sep 17 00:00:00 2001 From: Patrik Sletmo Date: Mon, 12 Apr 2021 11:51:47 +0200 Subject: [PATCH] feat: add hook for customizing injected runtime tags Enable other plugins to tap into the tag injection mechanism in order to customize the functionality of lazy loaded chunks. Closes #40. --- package-lock.json | 7 +- package.json | 1 + src/index.js | 99 ++++++++++++------- .../attributes-option.test.js.snap.webpack5 | 4 +- .../insert-option.test.js.snap.webpack5 | 6 +- .../linkTag-option.test.js.snap.webpack5 | 6 +- ...8fbe3ca.css => 0.5f50282222b6b9c8631c.css} | 0 ...8e8fbe3ca.css => 5f50282222b6b9c8631c.css} | 0 .../expected/webpack-5/main.js | 15 ++- test/cases/hmr/expected/webpack-5/main.js | 15 ++- .../expected/webpack-5/main.js | 13 ++- .../insert-string/expected/webpack-5/main.js | 13 ++- .../expected/webpack-5/main.js | 13 ++- 13 files changed, 109 insertions(+), 83 deletions(-) rename test/cases/chunkFilename-fullhash/expected/webpack-5/{0.07119853b0d8e8fbe3ca.css => 0.5f50282222b6b9c8631c.css} (100%) rename test/cases/chunkFilename-fullhash/expected/webpack-5/{07119853b0d8e8fbe3ca.css => 5f50282222b6b9c8631c.css} (100%) diff --git a/package-lock.json b/package-lock.json index 3b902153..c0d92d9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,11 +5,12 @@ "requires": true, "packages": { "": { - "version": "1.3.9", + "version": "1.4.1", "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0", + "tapable": "^2.2.0", "webpack-sources": "^1.1.0" }, "devDependencies": { @@ -17000,7 +17001,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true, "engines": { "node": ">=6" } @@ -32115,8 +32115,7 @@ "tapable": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", - "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "dev": true + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==" }, "terminal-link": { "version": "2.1.1", diff --git a/package.json b/package.json index 26abd63e..e295e875 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0", + "tapable": "^2.2.0", "webpack-sources": "^1.1.0" }, "devDependencies": { diff --git a/src/index.js b/src/index.js index c5483c46..b197c71d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this */ import { validate } from 'schema-utils'; +import { SyncWaterfallHook } from 'tapable'; import schema from './plugin-options.json'; import { MODULE_TYPE, compareModulesByIdentifier } from './utils'; @@ -28,6 +29,8 @@ const cssModuleCache = new WeakMap(); */ const cssDependencyCache = new WeakMap(); +const compilerHookMap = new WeakMap(); + class MiniCssExtractPlugin { static getCssModule(webpack) { /** @@ -300,6 +303,20 @@ class MiniCssExtractPlugin { return CssDependency; } + static getCompilerHooks(compiler) { + /** + * Prevent creation of multiple compiler hook maps to allow other integrations to get the current mapping. + */ + let hooks = compilerHookMap.get(compiler); + if (!hooks) { + hooks = { + customize: new SyncWaterfallHook(['attributes']), + }; + compilerHookMap.set(compiler, hooks); + } + return hooks; + } + constructor(options = {}) { validate(schema, options, { name: 'Mini CSS Extract Plugin', @@ -847,30 +864,46 @@ class MiniCssExtractPlugin { return null; } + const attributes = { + href: `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId)`, + rel: JSON.stringify('stylesheet'), + onload: 'onLinkComplete', + onerror: 'onLinkComplete', + }; + + // Some attributes cannot be assigned through setAttribute, so we maintain + // a list of attributes that can safely be assigned through dot notation + const safeAttrs = ['href', 'rel', 'type', 'onload', 'onerror']; + + if (this.runtimeOptions.linkType) { + attributes.type = JSON.stringify(this.runtimeOptions.linkType); + } + + if (crossOriginLoading) { + attributes.crossOrigin = `(linkTag.href.indexOf(window.location.origin + '/') !== 0) + ? ${JSON.stringify(crossOriginLoading)} + : undefined`; + } + + // Append static attributes + if (this.runtimeOptions.attributes) { + Object.entries(this.runtimeOptions.attributes).forEach( + ([key, value]) => { + attributes[key] = JSON.stringify(value); + } + ); + } + + // Append dynamic attributes + MiniCssExtractPlugin.getCompilerHooks(compiler).customize.call( + attributes + ); + return Template.asString([ `var createStylesheet = ${runtimeTemplate.basicFunction( 'chunkId, fullhref, resolve, reject', [ 'var linkTag = document.createElement("link");', - this.runtimeOptions.attributes - ? Template.asString( - Object.entries(this.runtimeOptions.attributes).map( - (entry) => { - const [key, value] = entry; - - return `linkTag.setAttribute(${JSON.stringify( - key - )}, ${JSON.stringify(value)});`; - } - ) - ) - : '', - 'linkTag.rel = "stylesheet";', - this.runtimeOptions.linkType - ? `linkTag.type = ${JSON.stringify( - this.runtimeOptions.linkType - )};` - : '', `var onLinkComplete = ${runtimeTemplate.basicFunction( 'event', [ @@ -892,19 +925,17 @@ class MiniCssExtractPlugin { '}', ] )}`, - 'linkTag.onerror = linkTag.onload = onLinkComplete;', - 'linkTag.href = fullhref;', - crossOriginLoading - ? Template.asString([ - `if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`, - Template.indent( - `linkTag.crossOrigin = ${JSON.stringify( - crossOriginLoading - )};` - ), - '}', - ]) - : '', + Template.asString( + Object.entries(attributes).map(([key, value]) => { + if (safeAttrs.includes(key)) { + return `linkTag.${key} = ${value};`; + } + + return `linkTag.setAttribute(${JSON.stringify( + key + )}, ${value});`; + }) + ), typeof this.runtimeOptions.insert !== 'undefined' ? typeof this.runtimeOptions.insert === 'function' ? `(${this.runtimeOptions.insert.toString()})(linkTag)` @@ -945,7 +976,7 @@ class MiniCssExtractPlugin { 'resolve, reject', [ `var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`, - `var fullhref = ${RuntimeGlobals.publicPath} + href;`, + `var fullhref = ${attributes.href};`, 'if(findStylesheet(href, fullhref)) return resolve();', 'createStylesheet(chunkId, fullhref, resolve, reject);', ] @@ -1016,7 +1047,7 @@ class MiniCssExtractPlugin { 'chunkId', [ `var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`, - `var fullhref = ${RuntimeGlobals.publicPath} + href;`, + `var fullhref = ${attributes.href};`, 'var oldTag = findStylesheet(href, fullhref);', 'if(!oldTag) return;', `promises.push(new Promise(${runtimeTemplate.basicFunction( diff --git a/test/__snapshots__/attributes-option.test.js.snap.webpack5 b/test/__snapshots__/attributes-option.test.js.snap.webpack5 index 84007ca0..95d7c9ff 100644 --- a/test/__snapshots__/attributes-option.test.js.snap.webpack5 +++ b/test/__snapshots__/attributes-option.test.js.snap.webpack5 @@ -4,7 +4,7 @@ exports[`attributes option should work with attributes option: DOM 1`] = ` " style-loader test - +

Body

@@ -22,7 +22,7 @@ exports[`attributes option should work without attributes option: DOM 1`] = ` " style-loader test - +

Body

diff --git a/test/__snapshots__/insert-option.test.js.snap.webpack5 b/test/__snapshots__/insert-option.test.js.snap.webpack5 index 1e8530eb..f7e839fd 100644 --- a/test/__snapshots__/insert-option.test.js.snap.webpack5 +++ b/test/__snapshots__/insert-option.test.js.snap.webpack5 @@ -3,7 +3,7 @@ exports[`insert option should work when insert option is function: DOM 1`] = ` " style-loader test - +

Body

@@ -21,7 +21,7 @@ exports[`insert option should work when insert option is function: warnings 1`] exports[`insert option should work when insert option is string: DOM 1`] = ` " style-loader test - +

Body

@@ -40,7 +40,7 @@ exports[`insert option should work without insert option: DOM 1`] = ` " style-loader test - +

Body

diff --git a/test/__snapshots__/linkTag-option.test.js.snap.webpack5 b/test/__snapshots__/linkTag-option.test.js.snap.webpack5 index 8a4e62d9..1782a725 100644 --- a/test/__snapshots__/linkTag-option.test.js.snap.webpack5 +++ b/test/__snapshots__/linkTag-option.test.js.snap.webpack5 @@ -4,7 +4,7 @@ exports[`linkType option should work when linkType option is "false": DOM 1`] = " style-loader test - +

Body

@@ -22,7 +22,7 @@ exports[`linkType option should work when linkType option is "text/css": DOM 1`] " style-loader test - +

Body

@@ -40,7 +40,7 @@ exports[`linkType option should work without linkType option: DOM 1`] = ` " style-loader test - +

Body

diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/0.07119853b0d8e8fbe3ca.css b/test/cases/chunkFilename-fullhash/expected/webpack-5/0.5f50282222b6b9c8631c.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5/0.07119853b0d8e8fbe3ca.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5/0.5f50282222b6b9c8631c.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/07119853b0d8e8fbe3ca.css b/test/cases/chunkFilename-fullhash/expected/webpack-5/5f50282222b6b9c8631c.css similarity index 100% rename from test/cases/chunkFilename-fullhash/expected/webpack-5/07119853b0d8e8fbe3ca.css rename to test/cases/chunkFilename-fullhash/expected/webpack-5/5f50282222b6b9c8631c.css diff --git a/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js b/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js index 6ec953d4..b9dec09e 100644 --- a/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js +++ b/test/cases/chunkFilename-fullhash/expected/webpack-5/main.js @@ -73,7 +73,7 @@ __webpack_require__.r(__webpack_exports__); /******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("07119853b0d8e8fbe3ca") +/******/ __webpack_require__.h = () => ("5f50282222b6b9c8631c") /******/ })(); /******/ /******/ /* webpack/runtime/global */ @@ -174,9 +174,6 @@ __webpack_require__.r(__webpack_exports__); /******/ (() => { /******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => { /******/ var linkTag = document.createElement("link"); -/******/ -/******/ linkTag.rel = "stylesheet"; -/******/ linkTag.type = "text/css"; /******/ var onLinkComplete = (event) => { /******/ // avoid mem leaks. /******/ linkTag.onerror = linkTag.onload = null; @@ -193,9 +190,11 @@ __webpack_require__.r(__webpack_exports__); /******/ reject(err); /******/ } /******/ } -/******/ linkTag.onerror = linkTag.onload = onLinkComplete; -/******/ linkTag.href = fullhref; -/******/ +/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.onload = onLinkComplete; +/******/ linkTag.onerror = onLinkComplete; +/******/ linkTag.type = "text/css"; /******/ document.head.appendChild(linkTag); /******/ return linkTag; /******/ }; @@ -216,7 +215,7 @@ __webpack_require__.r(__webpack_exports__); /******/ var loadStylesheet = (chunkId) => { /******/ return new Promise((resolve, reject) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ if(findStylesheet(href, fullhref)) return resolve(); /******/ createStylesheet(chunkId, fullhref, resolve, reject); /******/ }); diff --git a/test/cases/hmr/expected/webpack-5/main.js b/test/cases/hmr/expected/webpack-5/main.js index ae52744f..cd2ce2ae 100644 --- a/test/cases/hmr/expected/webpack-5/main.js +++ b/test/cases/hmr/expected/webpack-5/main.js @@ -846,9 +846,6 @@ module.exports = function (urlString) { /******/ (() => { /******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => { /******/ var linkTag = document.createElement("link"); -/******/ -/******/ linkTag.rel = "stylesheet"; -/******/ linkTag.type = "text/css"; /******/ var onLinkComplete = (event) => { /******/ // avoid mem leaks. /******/ linkTag.onerror = linkTag.onload = null; @@ -865,9 +862,11 @@ module.exports = function (urlString) { /******/ reject(err); /******/ } /******/ } -/******/ linkTag.onerror = linkTag.onload = onLinkComplete; -/******/ linkTag.href = fullhref; -/******/ +/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.onload = onLinkComplete; +/******/ linkTag.onerror = onLinkComplete; +/******/ linkTag.type = "text/css"; /******/ document.head.appendChild(linkTag); /******/ return linkTag; /******/ }; @@ -888,7 +887,7 @@ module.exports = function (urlString) { /******/ var loadStylesheet = (chunkId) => { /******/ return new Promise((resolve, reject) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ if(findStylesheet(href, fullhref)) return resolve(); /******/ createStylesheet(chunkId, fullhref, resolve, reject); /******/ }); @@ -913,7 +912,7 @@ module.exports = function (urlString) { /******/ applyHandlers.push(applyHandler); /******/ chunkIds.forEach((chunkId) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ var oldTag = findStylesheet(href, fullhref); /******/ if(!oldTag) return; /******/ promises.push(new Promise((resolve, reject) => { diff --git a/test/cases/insert-function/expected/webpack-5/main.js b/test/cases/insert-function/expected/webpack-5/main.js index 0dcaf369..6c7e634a 100644 --- a/test/cases/insert-function/expected/webpack-5/main.js +++ b/test/cases/insert-function/expected/webpack-5/main.js @@ -158,9 +158,6 @@ /******/ (() => { /******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => { /******/ var linkTag = document.createElement("link"); -/******/ -/******/ linkTag.rel = "stylesheet"; -/******/ linkTag.type = "text/css"; /******/ var onLinkComplete = (event) => { /******/ // avoid mem leaks. /******/ linkTag.onerror = linkTag.onload = null; @@ -177,9 +174,11 @@ /******/ reject(err); /******/ } /******/ } -/******/ linkTag.onerror = linkTag.onload = onLinkComplete; -/******/ linkTag.href = fullhref; -/******/ +/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.onload = onLinkComplete; +/******/ linkTag.onerror = onLinkComplete; +/******/ linkTag.type = "text/css"; /******/ (function (linkTag) { /******/ const reference = document.querySelector('.hot-reload'); /******/ @@ -206,7 +205,7 @@ /******/ var loadStylesheet = (chunkId) => { /******/ return new Promise((resolve, reject) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ if(findStylesheet(href, fullhref)) return resolve(); /******/ createStylesheet(chunkId, fullhref, resolve, reject); /******/ }); diff --git a/test/cases/insert-string/expected/webpack-5/main.js b/test/cases/insert-string/expected/webpack-5/main.js index 4d61b5dc..5a72955a 100644 --- a/test/cases/insert-string/expected/webpack-5/main.js +++ b/test/cases/insert-string/expected/webpack-5/main.js @@ -158,9 +158,6 @@ /******/ (() => { /******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => { /******/ var linkTag = document.createElement("link"); -/******/ -/******/ linkTag.rel = "stylesheet"; -/******/ linkTag.type = "text/css"; /******/ var onLinkComplete = (event) => { /******/ // avoid mem leaks. /******/ linkTag.onerror = linkTag.onload = null; @@ -177,9 +174,11 @@ /******/ reject(err); /******/ } /******/ } -/******/ linkTag.onerror = linkTag.onload = onLinkComplete; -/******/ linkTag.href = fullhref; -/******/ +/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.onload = onLinkComplete; +/******/ linkTag.onerror = onLinkComplete; +/******/ linkTag.type = "text/css"; /******/ var target = document.querySelector("script[src='1.js']"); /******/ target.parentNode.insertBefore(linkTag, target.nextSibling); /******/ return linkTag; @@ -201,7 +200,7 @@ /******/ var loadStylesheet = (chunkId) => { /******/ return new Promise((resolve, reject) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ if(findStylesheet(href, fullhref)) return resolve(); /******/ createStylesheet(chunkId, fullhref, resolve, reject); /******/ }); diff --git a/test/cases/insert-undefined/expected/webpack-5/main.js b/test/cases/insert-undefined/expected/webpack-5/main.js index b998720c..2c6ef386 100644 --- a/test/cases/insert-undefined/expected/webpack-5/main.js +++ b/test/cases/insert-undefined/expected/webpack-5/main.js @@ -158,9 +158,6 @@ /******/ (() => { /******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => { /******/ var linkTag = document.createElement("link"); -/******/ -/******/ linkTag.rel = "stylesheet"; -/******/ linkTag.type = "text/css"; /******/ var onLinkComplete = (event) => { /******/ // avoid mem leaks. /******/ linkTag.onerror = linkTag.onload = null; @@ -177,9 +174,11 @@ /******/ reject(err); /******/ } /******/ } -/******/ linkTag.onerror = linkTag.onload = onLinkComplete; -/******/ linkTag.href = fullhref; -/******/ +/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); +/******/ linkTag.rel = "stylesheet"; +/******/ linkTag.onload = onLinkComplete; +/******/ linkTag.onerror = onLinkComplete; +/******/ linkTag.type = "text/css"; /******/ document.head.appendChild(linkTag); /******/ return linkTag; /******/ }; @@ -200,7 +199,7 @@ /******/ var loadStylesheet = (chunkId) => { /******/ return new Promise((resolve, reject) => { /******/ var href = __webpack_require__.miniCssF(chunkId); -/******/ var fullhref = __webpack_require__.p + href; +/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId); /******/ if(findStylesheet(href, fullhref)) return resolve(); /******/ createStylesheet(chunkId, fullhref, resolve, reject); /******/ });