Skip to content

Commit

Permalink
feat: add hook for customizing injected runtime tags
Browse files Browse the repository at this point in the history
    Enable other plugins to tap into the tag injection mechanism in order to
    customize the functionality of lazy loaded chunks.

    Closes #40.
  • Loading branch information
Patrik Sletmo committed Apr 14, 2021
1 parent a33f22f commit a0ab666
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 83 deletions.
7 changes: 3 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -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": {
Expand Down
99 changes: 65 additions & 34 deletions 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';
Expand Down Expand Up @@ -28,6 +29,8 @@ const cssModuleCache = new WeakMap();
*/
const cssDependencyCache = new WeakMap();

const compilerHookMap = new WeakMap();

class MiniCssExtractPlugin {
static getCssModule(webpack) {
/**
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
[
Expand All @@ -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)`
Expand Down Expand Up @@ -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);',
]
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions test/__snapshots__/attributes-option.test.js.snap.webpack5
Expand Up @@ -4,7 +4,7 @@ exports[`attributes option should work with attributes option: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link id=\\"target\\" data-target=\\"example\\" rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\" id=\\"target\\" data-target=\\"example\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand All @@ -22,7 +22,7 @@ exports[`attributes option should work without attributes option: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/insert-option.test.js.snap.webpack5
Expand Up @@ -3,7 +3,7 @@
exports[`insert option should work when insert option is function: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><style id=\\"existing-style\\">.existing { color: yellow }</style>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><style id=\\"existing-style\\">.existing { color: yellow }</style>
<script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
Expand All @@ -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`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style><link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\">
<style id=\\"existing-style\\">.existing { color: yellow }</style><link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\">
<script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
Expand All @@ -40,7 +40,7 @@ exports[`insert option should work without insert option: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand Down
6 changes: 3 additions & 3 deletions test/__snapshots__/linkTag-option.test.js.snap.webpack5
Expand Up @@ -4,7 +4,7 @@ exports[`linkType option should work when linkType option is "false": DOM 1`] =
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link rel=\\"stylesheet\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand All @@ -22,7 +22,7 @@ exports[`linkType option should work when linkType option is "text/css": DOM 1`]
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand All @@ -40,7 +40,7 @@ exports[`linkType option should work without linkType option: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<link rel=\\"stylesheet\\" type=\\"text/css\\" href=\\"simple.css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<link href=\\"simple.css\\" rel=\\"stylesheet\\" type=\\"text/css\\"><script charset=\\"utf-8\\" src=\\"simple.bundle.js\\"></script></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
Expand Down
15 changes: 7 additions & 8 deletions test/cases/chunkFilename-fullhash/expected/webpack-5/main.js
Expand Up @@ -73,7 +73,7 @@ __webpack_require__.r(__webpack_exports__);
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("07119853b0d8e8fbe3ca")
/******/ __webpack_require__.h = () => ("5f50282222b6b9c8631c")
/******/ })();
/******/
/******/ /* webpack/runtime/global */
Expand Down Expand Up @@ -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;
Expand All @@ -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;
/******/ };
Expand All @@ -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);
/******/ });
Expand Down
15 changes: 7 additions & 8 deletions test/cases/hmr/expected/webpack-5/main.js
Expand Up @@ -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;
Expand All @@ -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;
/******/ };
Expand All @@ -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);
/******/ });
Expand All @@ -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) => {
Expand Down
13 changes: 6 additions & 7 deletions test/cases/insert-function/expected/webpack-5/main.js
Expand Up @@ -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;
Expand All @@ -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');
/******/
Expand All @@ -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);
/******/ });
Expand Down

0 comments on commit a0ab666

Please sign in to comment.