Skip to content

Commit a0ab666

Browse files
author
Patrik Sletmo
committed
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.
1 parent a33f22f commit a0ab666

File tree

13 files changed

+109
-83
lines changed

13 files changed

+109
-83
lines changed

package-lock.json

+3-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"dependencies": {
4545
"loader-utils": "^2.0.0",
4646
"schema-utils": "^3.0.0",
47+
"tapable": "^2.2.0",
4748
"webpack-sources": "^1.1.0"
4849
},
4950
"devDependencies": {

src/index.js

+65-34
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable class-methods-use-this */
22

33
import { validate } from 'schema-utils';
4+
import { SyncWaterfallHook } from 'tapable';
45

56
import schema from './plugin-options.json';
67
import { MODULE_TYPE, compareModulesByIdentifier } from './utils';
@@ -28,6 +29,8 @@ const cssModuleCache = new WeakMap();
2829
*/
2930
const cssDependencyCache = new WeakMap();
3031

32+
const compilerHookMap = new WeakMap();
33+
3134
class MiniCssExtractPlugin {
3235
static getCssModule(webpack) {
3336
/**
@@ -300,6 +303,20 @@ class MiniCssExtractPlugin {
300303
return CssDependency;
301304
}
302305

306+
static getCompilerHooks(compiler) {
307+
/**
308+
* Prevent creation of multiple compiler hook maps to allow other integrations to get the current mapping.
309+
*/
310+
let hooks = compilerHookMap.get(compiler);
311+
if (!hooks) {
312+
hooks = {
313+
customize: new SyncWaterfallHook(['attributes']),
314+
};
315+
compilerHookMap.set(compiler, hooks);
316+
}
317+
return hooks;
318+
}
319+
303320
constructor(options = {}) {
304321
validate(schema, options, {
305322
name: 'Mini CSS Extract Plugin',
@@ -847,30 +864,46 @@ class MiniCssExtractPlugin {
847864
return null;
848865
}
849866

867+
const attributes = {
868+
href: `${RuntimeGlobals.publicPath} + ${RuntimeGlobals.require}.miniCssF(chunkId)`,
869+
rel: JSON.stringify('stylesheet'),
870+
onload: 'onLinkComplete',
871+
onerror: 'onLinkComplete',
872+
};
873+
874+
// Some attributes cannot be assigned through setAttribute, so we maintain
875+
// a list of attributes that can safely be assigned through dot notation
876+
const safeAttrs = ['href', 'rel', 'type', 'onload', 'onerror'];
877+
878+
if (this.runtimeOptions.linkType) {
879+
attributes.type = JSON.stringify(this.runtimeOptions.linkType);
880+
}
881+
882+
if (crossOriginLoading) {
883+
attributes.crossOrigin = `(linkTag.href.indexOf(window.location.origin + '/') !== 0)
884+
? ${JSON.stringify(crossOriginLoading)}
885+
: undefined`;
886+
}
887+
888+
// Append static attributes
889+
if (this.runtimeOptions.attributes) {
890+
Object.entries(this.runtimeOptions.attributes).forEach(
891+
([key, value]) => {
892+
attributes[key] = JSON.stringify(value);
893+
}
894+
);
895+
}
896+
897+
// Append dynamic attributes
898+
MiniCssExtractPlugin.getCompilerHooks(compiler).customize.call(
899+
attributes
900+
);
901+
850902
return Template.asString([
851903
`var createStylesheet = ${runtimeTemplate.basicFunction(
852904
'chunkId, fullhref, resolve, reject',
853905
[
854906
'var linkTag = document.createElement("link");',
855-
this.runtimeOptions.attributes
856-
? Template.asString(
857-
Object.entries(this.runtimeOptions.attributes).map(
858-
(entry) => {
859-
const [key, value] = entry;
860-
861-
return `linkTag.setAttribute(${JSON.stringify(
862-
key
863-
)}, ${JSON.stringify(value)});`;
864-
}
865-
)
866-
)
867-
: '',
868-
'linkTag.rel = "stylesheet";',
869-
this.runtimeOptions.linkType
870-
? `linkTag.type = ${JSON.stringify(
871-
this.runtimeOptions.linkType
872-
)};`
873-
: '',
874907
`var onLinkComplete = ${runtimeTemplate.basicFunction(
875908
'event',
876909
[
@@ -892,19 +925,17 @@ class MiniCssExtractPlugin {
892925
'}',
893926
]
894927
)}`,
895-
'linkTag.onerror = linkTag.onload = onLinkComplete;',
896-
'linkTag.href = fullhref;',
897-
crossOriginLoading
898-
? Template.asString([
899-
`if (linkTag.href.indexOf(window.location.origin + '/') !== 0) {`,
900-
Template.indent(
901-
`linkTag.crossOrigin = ${JSON.stringify(
902-
crossOriginLoading
903-
)};`
904-
),
905-
'}',
906-
])
907-
: '',
928+
Template.asString(
929+
Object.entries(attributes).map(([key, value]) => {
930+
if (safeAttrs.includes(key)) {
931+
return `linkTag.${key} = ${value};`;
932+
}
933+
934+
return `linkTag.setAttribute(${JSON.stringify(
935+
key
936+
)}, ${value});`;
937+
})
938+
),
908939
typeof this.runtimeOptions.insert !== 'undefined'
909940
? typeof this.runtimeOptions.insert === 'function'
910941
? `(${this.runtimeOptions.insert.toString()})(linkTag)`
@@ -945,7 +976,7 @@ class MiniCssExtractPlugin {
945976
'resolve, reject',
946977
[
947978
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
948-
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
979+
`var fullhref = ${attributes.href};`,
949980
'if(findStylesheet(href, fullhref)) return resolve();',
950981
'createStylesheet(chunkId, fullhref, resolve, reject);',
951982
]
@@ -1016,7 +1047,7 @@ class MiniCssExtractPlugin {
10161047
'chunkId',
10171048
[
10181049
`var href = ${RuntimeGlobals.require}.miniCssF(chunkId);`,
1019-
`var fullhref = ${RuntimeGlobals.publicPath} + href;`,
1050+
`var fullhref = ${attributes.href};`,
10201051
'var oldTag = findStylesheet(href, fullhref);',
10211052
'if(!oldTag) return;',
10221053
`promises.push(new Promise(${runtimeTemplate.basicFunction(

test/__snapshots__/attributes-option.test.js.snap.webpack5

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`attributes option should work with attributes option: DOM 1`] = `
44
"<!DOCTYPE html><html><head>
55
<title>style-loader test</title>
66
<style id=\\"existing-style\\">.existing { color: yellow }</style>
7-
<link id=\\"target\\" data-target=\\"example\\" rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
7+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\" id=\\"target\\" data-target=\\"example\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
88
<body>
99
<h1>Body</h1>
1010
<div class=\\"target\\"></div>
@@ -22,7 +22,7 @@ exports[`attributes option should work without attributes option: DOM 1`] = `
2222
"<!DOCTYPE html><html><head>
2323
<title>style-loader test</title>
2424
<style id=\\"existing-style\\">.existing { color: yellow }</style>
25-
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
25+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
2626
<body>
2727
<h1>Body</h1>
2828
<div class=\\"target\\"></div>

test/__snapshots__/insert-option.test.js.snap.webpack5

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
exports[`insert option should work when insert option is function: DOM 1`] = `
44
"<!DOCTYPE html><html><head>
55
<title>style-loader test</title>
6-
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><style id=\\"existing-style\\">.existing { color: yellow }</style>
6+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><style id=\\"existing-style\\">.existing { color: yellow }</style>
77
<script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
88
<body>
99
<h1>Body</h1>
@@ -21,7 +21,7 @@ exports[`insert option should work when insert option is function: warnings 1`]
2121
exports[`insert option should work when insert option is string: DOM 1`] = `
2222
"<!DOCTYPE html><html><head>
2323
<title>style-loader test</title>
24-
<style id=\\"existing-style\\">.existing { color: yellow }</style><link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\">
24+
<style id=\\"existing-style\\">.existing { color: yellow }</style><link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\">
2525
<script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
2626
<body>
2727
<h1>Body</h1>
@@ -40,7 +40,7 @@ exports[`insert option should work without insert option: DOM 1`] = `
4040
"<!DOCTYPE html><html><head>
4141
<title>style-loader test</title>
4242
<style id=\\"existing-style\\">.existing { color: yellow }</style>
43-
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
43+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
4444
<body>
4545
<h1>Body</h1>
4646
<div class=\\"target\\"></div>

test/__snapshots__/linkTag-option.test.js.snap.webpack5

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`linkType option should work when linkType option is "false": DOM 1`] =
44
"<!DOCTYPE html><html><head>
55
<title>style-loader test</title>
66
<style id=\\"existing-style\\">.existing { color: yellow }</style>
7-
<link rel=\\"stylesheet\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
7+
<link href=\\"simple.css\\" rel=\\"stylesheet\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
88
<body>
99
<h1>Body</h1>
1010
<div class=\\"target\\"></div>
@@ -22,7 +22,7 @@ exports[`linkType option should work when linkType option is "text/css": DOM 1`]
2222
"<!DOCTYPE html><html><head>
2323
<title>style-loader test</title>
2424
<style id=\\"existing-style\\">.existing { color: yellow }</style>
25-
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
25+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
2626
<body>
2727
<h1>Body</h1>
2828
<div class=\\"target\\"></div>
@@ -40,7 +40,7 @@ exports[`linkType option should work without linkType option: DOM 1`] = `
4040
"<!DOCTYPE html><html><head>
4141
<title>style-loader test</title>
4242
<style id=\\"existing-style\\">.existing { color: yellow }</style>
43-
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
43+
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
4444
<body>
4545
<h1>Body</h1>
4646
<div class=\\"target\\"></div>

test/cases/chunkFilename-fullhash/expected/webpack-5/main.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ __webpack_require__.r(__webpack_exports__);
7373
/******/
7474
/******/ /* webpack/runtime/getFullHash */
7575
/******/ (() => {
76-
/******/ __webpack_require__.h = () => ("07119853b0d8e8fbe3ca")
76+
/******/ __webpack_require__.h = () => ("5f50282222b6b9c8631c")
7777
/******/ })();
7878
/******/
7979
/******/ /* webpack/runtime/global */
@@ -174,9 +174,6 @@ __webpack_require__.r(__webpack_exports__);
174174
/******/ (() => {
175175
/******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => {
176176
/******/ var linkTag = document.createElement("link");
177-
/******/
178-
/******/ linkTag.rel = "stylesheet";
179-
/******/ linkTag.type = "text/css";
180177
/******/ var onLinkComplete = (event) => {
181178
/******/ // avoid mem leaks.
182179
/******/ linkTag.onerror = linkTag.onload = null;
@@ -193,9 +190,11 @@ __webpack_require__.r(__webpack_exports__);
193190
/******/ reject(err);
194191
/******/ }
195192
/******/ }
196-
/******/ linkTag.onerror = linkTag.onload = onLinkComplete;
197-
/******/ linkTag.href = fullhref;
198-
/******/
193+
/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
194+
/******/ linkTag.rel = "stylesheet";
195+
/******/ linkTag.onload = onLinkComplete;
196+
/******/ linkTag.onerror = onLinkComplete;
197+
/******/ linkTag.type = "text/css";
199198
/******/ document.head.appendChild(linkTag);
200199
/******/ return linkTag;
201200
/******/ };
@@ -216,7 +215,7 @@ __webpack_require__.r(__webpack_exports__);
216215
/******/ var loadStylesheet = (chunkId) => {
217216
/******/ return new Promise((resolve, reject) => {
218217
/******/ var href = __webpack_require__.miniCssF(chunkId);
219-
/******/ var fullhref = __webpack_require__.p + href;
218+
/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
220219
/******/ if(findStylesheet(href, fullhref)) return resolve();
221220
/******/ createStylesheet(chunkId, fullhref, resolve, reject);
222221
/******/ });

test/cases/hmr/expected/webpack-5/main.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -846,9 +846,6 @@ module.exports = function (urlString) {
846846
/******/ (() => {
847847
/******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => {
848848
/******/ var linkTag = document.createElement("link");
849-
/******/
850-
/******/ linkTag.rel = "stylesheet";
851-
/******/ linkTag.type = "text/css";
852849
/******/ var onLinkComplete = (event) => {
853850
/******/ // avoid mem leaks.
854851
/******/ linkTag.onerror = linkTag.onload = null;
@@ -865,9 +862,11 @@ module.exports = function (urlString) {
865862
/******/ reject(err);
866863
/******/ }
867864
/******/ }
868-
/******/ linkTag.onerror = linkTag.onload = onLinkComplete;
869-
/******/ linkTag.href = fullhref;
870-
/******/
865+
/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
866+
/******/ linkTag.rel = "stylesheet";
867+
/******/ linkTag.onload = onLinkComplete;
868+
/******/ linkTag.onerror = onLinkComplete;
869+
/******/ linkTag.type = "text/css";
871870
/******/ document.head.appendChild(linkTag);
872871
/******/ return linkTag;
873872
/******/ };
@@ -888,7 +887,7 @@ module.exports = function (urlString) {
888887
/******/ var loadStylesheet = (chunkId) => {
889888
/******/ return new Promise((resolve, reject) => {
890889
/******/ var href = __webpack_require__.miniCssF(chunkId);
891-
/******/ var fullhref = __webpack_require__.p + href;
890+
/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
892891
/******/ if(findStylesheet(href, fullhref)) return resolve();
893892
/******/ createStylesheet(chunkId, fullhref, resolve, reject);
894893
/******/ });
@@ -913,7 +912,7 @@ module.exports = function (urlString) {
913912
/******/ applyHandlers.push(applyHandler);
914913
/******/ chunkIds.forEach((chunkId) => {
915914
/******/ var href = __webpack_require__.miniCssF(chunkId);
916-
/******/ var fullhref = __webpack_require__.p + href;
915+
/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
917916
/******/ var oldTag = findStylesheet(href, fullhref);
918917
/******/ if(!oldTag) return;
919918
/******/ promises.push(new Promise((resolve, reject) => {

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

+6-7
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,6 @@
158158
/******/ (() => {
159159
/******/ var createStylesheet = (chunkId, fullhref, resolve, reject) => {
160160
/******/ var linkTag = document.createElement("link");
161-
/******/
162-
/******/ linkTag.rel = "stylesheet";
163-
/******/ linkTag.type = "text/css";
164161
/******/ var onLinkComplete = (event) => {
165162
/******/ // avoid mem leaks.
166163
/******/ linkTag.onerror = linkTag.onload = null;
@@ -177,9 +174,11 @@
177174
/******/ reject(err);
178175
/******/ }
179176
/******/ }
180-
/******/ linkTag.onerror = linkTag.onload = onLinkComplete;
181-
/******/ linkTag.href = fullhref;
182-
/******/
177+
/******/ linkTag.href = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
178+
/******/ linkTag.rel = "stylesheet";
179+
/******/ linkTag.onload = onLinkComplete;
180+
/******/ linkTag.onerror = onLinkComplete;
181+
/******/ linkTag.type = "text/css";
183182
/******/ (function (linkTag) {
184183
/******/ const reference = document.querySelector('.hot-reload');
185184
/******/
@@ -206,7 +205,7 @@
206205
/******/ var loadStylesheet = (chunkId) => {
207206
/******/ return new Promise((resolve, reject) => {
208207
/******/ var href = __webpack_require__.miniCssF(chunkId);
209-
/******/ var fullhref = __webpack_require__.p + href;
208+
/******/ var fullhref = __webpack_require__.p + __webpack_require__.miniCssF(chunkId);
210209
/******/ if(findStylesheet(href, fullhref)) return resolve();
211210
/******/ createStylesheet(chunkId, fullhref, resolve, reject);
212211
/******/ });

0 commit comments

Comments
 (0)