Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add hook for customizing injected runtime tags #526

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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',
};
Comment on lines +867 to +872
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can write warning in docs about some attributes, they are still internal and should not change, it will simplify logic

@alexander-akait I started rebasing and changing the code based on your comments. Are you suggesting that we remove all properties (i.e. attributes that cannot be set through setAttribute) from the above attributes object?

Otherwise I suggest that we change the below declaration to

// Link properties cannot be set using setAttribute, but must instead be set through
// link.property = value. We maintain this list of known properties, so that we can
// combine attributes of both types in the above object.
const linkProperties = ['href', 'rel', 'type', 'onload', 'onerror'];

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think docs about these attributes will be enough, they are internal and rewriting them is not safe

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so keep the above behavior but add section to docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexander-akait Hi there, any plans on pulling this in soon? I followed the discussion and it seemed like you were almost done.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

解决了吗?目前也是这个sri问题,需要这个功能


// 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'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should change this logic little bit, I think we don't protect attributes, developer can add data-* attributes, any non valid HTML attribute (why not 😄 ), even onload and onerror can be rewritten

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this logic is that some attributes must be assigned through tag.prop = value while others must be assigned through tag.setAttribute("prop", value);. The array above is a bit over-cautious, as I found that only onload and onerror needed to be set through dot notation (e.g. tag.onload = ...), the other attributes defined there can just as easily be set through setAttribute.

I'm not aware of any sensible way of determining what attributes needs to be set which way, so one proposal here would be to rewrite the above code to say

const safeAttrs = ['onload', 'onerror'];

Perhaps we could also update the wording and the comment to clarify why this is done.

The problem with that solution is that user-defined attributes (other than onload and onerror) that must be assigned through dot notation will not work with that solution. What's your thoughts on this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @alexander-akait ,
hope you have nice summer weather =)
Could you please prioritise it a bit? Currently it's annoying as we have to use preload/prefetch with SRI, but chrome/safary/ff are not happy to preload with SRI but then load it without SRI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@utlime need rebase, I think it is stale PR, so feel free to send this code again and we will merge it

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexander-akait Not at all, I have been waiting for your response to my latest comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can write warning in docs about some attributes, they are still internal and should not change, it will simplify logic


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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let's rename customize to setLinkAttributes, it is more clear

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