webpack/lib/css/CssLoadingRuntimeModule.js

447 lines
15 KiB
JavaScript
Raw Normal View History

2021-11-30 19:55:51 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { SyncWaterfallHook } = require("tapable");
const Compilation = require("../Compilation");
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const compileBooleanMatcher = require("../util/compileBooleanMatcher");
2021-12-03 23:23:09 +08:00
const { chunkHasCss } = require("./CssModulesPlugin");
2021-11-30 19:55:51 +08:00
/** @typedef {import("../Chunk")} Chunk */
2023-06-04 01:52:25 +08:00
/** @typedef {import("../ChunkGraph")} ChunkGraph */
2023-04-29 03:26:27 +08:00
/** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */
2021-11-30 19:55:51 +08:00
/**
* @typedef {Object} JsonpCompilationPluginHooks
* @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
*/
/** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
const compilationHooksMap = new WeakMap();
class CssLoadingRuntimeModule extends RuntimeModule {
/**
* @param {Compilation} compilation the compilation
* @returns {JsonpCompilationPluginHooks} hooks
*/
static getCompilationHooks(compilation) {
if (!(compilation instanceof Compilation)) {
throw new TypeError(
"The 'compilation' argument must be an instance of Compilation"
);
}
let hooks = compilationHooksMap.get(compilation);
if (hooks === undefined) {
hooks = {
createStylesheet: new SyncWaterfallHook(["source", "chunk"])
};
compilationHooksMap.set(compilation, hooks);
}
return hooks;
}
2023-04-29 03:26:27 +08:00
/**
* @param {Set<string>} runtimeRequirements runtime requirements
*/
constructor(runtimeRequirements) {
2021-11-30 19:55:51 +08:00
super("css loading", 10);
this._runtimeRequirements = runtimeRequirements;
}
/**
2023-06-17 01:13:03 +08:00
* @returns {string | null} runtime code
2021-11-30 19:55:51 +08:00
*/
generate() {
const { compilation, chunk, _runtimeRequirements } = this;
const {
chunkGraph,
runtimeTemplate,
outputOptions: {
crossOriginLoading,
uniqueName,
chunkLoadTimeout: loadTimeout
}
2023-06-04 01:52:25 +08:00
} = /** @type {Compilation} */ (compilation);
2021-11-30 19:55:51 +08:00
const fn = RuntimeGlobals.ensureChunkHandlers;
const conditionMap = chunkGraph.getChunkConditionMap(
2023-06-04 01:52:25 +08:00
/** @type {Chunk} */ (chunk),
/**
* @param {Chunk} chunk the chunk
* @param {ChunkGraph} chunkGraph the chunk graph
* @returns {boolean} true, if the chunk has css
*/
2021-11-30 19:55:51 +08:00
(chunk, chunkGraph) =>
!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")
);
const hasCssMatcher = compileBooleanMatcher(conditionMap);
const withLoading =
_runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
hasCssMatcher !== false;
2023-04-29 03:26:27 +08:00
/** @type {boolean} */
2021-11-30 19:55:51 +08:00
const withHmr = _runtimeRequirements.has(
RuntimeGlobals.hmrDownloadUpdateHandlers
);
2023-04-29 03:26:27 +08:00
/** @type {Set<number | string | null>} */
2021-12-03 23:23:09 +08:00
const initialChunkIdsWithCss = new Set();
2023-04-29 03:26:27 +08:00
/** @type {Set<number | string | null>} */
2021-12-03 23:23:09 +08:00
const initialChunkIdsWithoutCss = new Set();
2023-06-04 01:52:25 +08:00
for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) {
2021-12-03 23:23:09 +08:00
(chunkHasCss(c, chunkGraph)
? initialChunkIdsWithCss
: initialChunkIdsWithoutCss
).add(c.id);
}
if (!withLoading && !withHmr && initialChunkIdsWithCss.size === 0) {
return null;
}
2023-06-04 01:52:25 +08:00
const { createStylesheet } = CssLoadingRuntimeModule.getCompilationHooks(
/** @type {Compilation} */ (compilation)
);
2021-11-30 19:55:51 +08:00
const stateExpression = withHmr
? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css`
: undefined;
const code = Template.asString([
"link = document.createElement('link');",
uniqueName
2021-12-15 23:46:13 +08:00
? 'link.setAttribute("data-webpack", uniqueName + ":" + key);'
2021-11-30 19:55:51 +08:00
: "",
"link.setAttribute(loadingAttribute, 1);",
'link.rel = "stylesheet";',
"link.href = url;",
crossOriginLoading
? crossOriginLoading === "use-credentials"
? 'link.crossOrigin = "use-credentials";'
: Template.asString([
"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
),
"}"
2024-01-14 09:41:34 +08:00
])
2021-11-30 19:55:51 +08:00
: ""
]);
2023-04-29 03:26:27 +08:00
/** @type {(str: string) => number} */
2021-12-14 23:02:26 +08:00
const cc = str => str.charCodeAt(0);
2023-06-19 09:40:06 +08:00
const name = uniqueName
? runtimeTemplate.concatenation(
"--webpack-",
{ expr: "uniqueName" },
"-",
{ expr: "chunkId" }
2024-01-14 09:41:34 +08:00
)
2023-06-19 09:40:06 +08:00
: runtimeTemplate.concatenation("--webpack-", { expr: "chunkId" });
2021-12-14 23:02:26 +08:00
2021-11-30 19:55:51 +08:00
return Template.asString([
"// object to store loaded and loading chunks",
"// undefined = chunk not loaded, null = chunk preloaded/prefetched",
"// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
`var installedChunks = ${
stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
2021-12-03 23:23:09 +08:00
}{${Array.from(
initialChunkIdsWithoutCss,
id => `${JSON.stringify(id)}:0`
).join(",")}};`,
2021-11-30 19:55:51 +08:00
"",
2021-12-15 23:46:13 +08:00
uniqueName
? `var uniqueName = ${JSON.stringify(
runtimeTemplate.outputOptions.uniqueName
2024-01-14 09:41:34 +08:00
)};`
2021-12-15 23:46:13 +08:00
: "// data-webpack is not used as build has no uniqueName",
2022-01-13 22:26:56 +08:00
`var loadCssChunkData = ${runtimeTemplate.basicFunction(
"target, link, chunkId",
[
2024-02-21 18:34:40 +08:00
`var data, token = "", token2 = "", exports = {}, ${
2022-01-13 22:26:56 +08:00
withHmr ? "moduleIds = [], " : ""
2023-06-19 09:40:06 +08:00
}name = ${name}, i = 0, cc = 1;`,
"try {",
Template.indent([
"if(!link) link = loadStylesheet(chunkId);",
// `link.sheet.rules` for legacy browsers
"var cssRules = link.sheet.cssRules || link.sheet.rules;",
"var j = cssRules.length - 1;",
"while(j > -1 && !data) {",
Template.indent([
"var style = cssRules[j--].style;",
"if(!style) continue;",
`data = style.getPropertyValue(name);`
]),
"}"
]),
"}catch(e){}",
"if(!data) {",
Template.indent([
"data = getComputedStyle(document.head).getPropertyValue(name);"
]),
"}",
2022-01-13 22:26:56 +08:00
"if(!data) return [];",
"for(; cc; i++) {",
Template.indent([
"cc = data.charCodeAt(i);",
2024-02-21 16:00:24 +08:00
`if(cc == ${cc(":")}) { token2 = token; token = ""; }`,
`else if(cc == ${cc("/")}) { token = token.replace(/^_/, ""); token2 = token2.replace(/^_/, ""); exports[token2] = token; token = ""; token2 = ""; }`,
`else if(!cc || cc == ${cc(",")}) { token = token.replace(/^_/, ""); ${
2022-01-13 22:26:56 +08:00
RuntimeGlobals.makeNamespaceObject
}(exports); target[token] = (${runtimeTemplate.basicFunction(
"exports, module",
`module.exports = exports;`
)}).bind(null, exports); ${
withHmr ? "moduleIds.push(token); " : ""
2024-02-21 18:34:40 +08:00
}token = ""; token2 = ""; exports = {}; }`,
2022-01-13 22:26:56 +08:00
`else if(cc == ${cc("\\")}) { token += data[++i] }`,
`else { token += data[i]; }`
]),
"}",
`${
withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : ""
}installedChunks[chunkId] = 0;`,
withHmr ? "return moduleIds;" : ""
]
)}`,
2021-11-30 19:55:51 +08:00
'var loadingAttribute = "data-webpack-loading";',
`var loadStylesheet = ${runtimeTemplate.basicFunction(
"chunkId, url, done" + (withHmr ? ", hmr" : ""),
[
'var link, needAttach, key = "chunk-" + chunkId;',
withHmr ? "if(!hmr) {" : "",
'var links = document.getElementsByTagName("link");',
"for(var i = 0; i < links.length; i++) {",
Template.indent([
"var l = links[i];",
`if(l.rel == "stylesheet" && (${
2022-01-13 22:26:56 +08:00
withHmr
? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)'
: 'l.href == url || l.getAttribute("href") == url'
}${
2021-11-30 19:55:51 +08:00
uniqueName
2021-12-15 23:46:13 +08:00
? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key'
2021-11-30 19:55:51 +08:00
: ""
})) { link = l; break; }`
2021-11-30 19:55:51 +08:00
]),
"}",
2022-01-13 22:26:56 +08:00
"if(!done) return link;",
2021-11-30 19:55:51 +08:00
withHmr ? "}" : "",
"if(!link) {",
Template.indent([
"needAttach = true;",
2023-06-04 01:52:25 +08:00
createStylesheet.call(code, /** @type {Chunk} */ (this.chunk))
2021-11-30 19:55:51 +08:00
]),
"}",
`var onLinkComplete = ${runtimeTemplate.basicFunction(
"prev, event",
Template.asString([
"link.onerror = link.onload = null;",
"link.removeAttribute(loadingAttribute);",
"clearTimeout(timeout);",
'if(event && event.type != "load") link.parentNode.removeChild(link)',
"done(event);",
"if(prev) return prev(event);"
])
)};`,
"if(link.getAttribute(loadingAttribute)) {",
Template.indent([
`var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`,
"link.onerror = onLinkComplete.bind(null, link.onerror);",
"link.onload = onLinkComplete.bind(null, link.onload);"
]),
"} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking
withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "",
2021-11-30 19:55:51 +08:00
"needAttach && document.head.appendChild(link);",
"return link;"
]
)};`,
2022-01-13 22:26:56 +08:00
initialChunkIdsWithCss.size > 2
2021-11-30 19:55:51 +08:00
? `${JSON.stringify(
2021-12-03 23:23:09 +08:00
Array.from(initialChunkIdsWithCss)
2024-01-14 09:41:34 +08:00
)}.forEach(loadCssChunkData.bind(null, ${
2022-01-13 22:26:56 +08:00
RuntimeGlobals.moduleFactories
2024-01-14 09:41:34 +08:00
}, 0));`
2021-12-03 23:23:09 +08:00
: initialChunkIdsWithCss.size > 0
2024-01-14 09:41:34 +08:00
? `${Array.from(
initialChunkIdsWithCss,
id =>
`loadCssChunkData(${
RuntimeGlobals.moduleFactories
}, 0, ${JSON.stringify(id)});`
).join("")}`
: "// no initial css",
2021-11-30 19:55:51 +08:00
"",
withLoading
? Template.asString([
2022-02-08 20:29:56 +08:00
`${fn}.css = ${runtimeTemplate.basicFunction("chunkId, promises", [
"// css chunk loading",
`var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
'if(installedChunkData !== 0) { // 0 means "already installed".',
Template.indent([
"",
'// a Promise means "currently loading".',
"if(installedChunkData) {",
Template.indent(["promises.push(installedChunkData[2]);"]),
"} else {",
Template.indent([
hasCssMatcher === true
? "if(true) { // all chunks have CSS"
: `if(${hasCssMatcher("chunkId")}) {`,
Template.indent([
"// setup Promise in chunk cache",
`var promise = new Promise(${runtimeTemplate.expressionFunction(
`installedChunkData = installedChunks[chunkId] = [resolve, reject]`,
"resolve, reject"
)});`,
"promises.push(installedChunkData[2] = promise);",
"",
"// start chunk loading",
`var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
"// create error before stack unwound to get useful stacktrace later",
"var error = new Error();",
`var loadingEnded = ${runtimeTemplate.basicFunction(
"event",
[
`if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
2021-11-30 19:55:51 +08:00
Template.indent([
2022-02-08 20:29:56 +08:00
"installedChunkData = installedChunks[chunkId];",
"if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
"if(installedChunkData) {",
Template.indent([
'if(event.type !== "load") {',
Template.indent([
"var errorType = event && event.type;",
2024-01-10 23:43:19 +08:00
"var realHref = event && event.target && event.target.href;",
"error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
2022-02-08 20:29:56 +08:00
"error.name = 'ChunkLoadError';",
"error.type = errorType;",
2024-01-10 23:43:19 +08:00
"error.request = realHref;",
2022-02-08 20:29:56 +08:00
"installedChunkData[1](error);"
]),
"} else {",
Template.indent([
`loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`,
"installedChunkData[0]();"
]),
"}"
]),
"}"
2021-11-30 19:55:51 +08:00
]),
2022-02-08 20:29:56 +08:00
"}"
]
)};`,
"var link = loadStylesheet(chunkId, url, loadingEnded);"
]),
"} else installedChunks[chunkId] = 0;"
]),
"}"
]),
"}"
])};`
2024-01-14 09:41:34 +08:00
])
2021-11-30 19:55:51 +08:00
: "// no chunk loading",
"",
withHmr
? Template.asString([
"var oldTags = [];",
"var newTags = [];",
`var applyHandler = ${runtimeTemplate.basicFunction("options", [
2022-01-13 22:26:56 +08:00
`return { dispose: ${runtimeTemplate.basicFunction(
"",
[]
)}, apply: ${runtimeTemplate.basicFunction("", [
"var moduleIds = [];",
`newTags.forEach(${runtimeTemplate.expressionFunction(
"info[1].sheet.disabled = false",
"info"
)});`,
2021-11-30 19:55:51 +08:00
"while(oldTags.length) {",
Template.indent([
"var oldTag = oldTags.pop();",
"if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
]),
2022-01-13 22:26:56 +08:00
"}",
"while(newTags.length) {",
Template.indent([
`var info = newTags.pop();`,
`var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[1], info[0]);`,
`chunkModuleIds.forEach(${runtimeTemplate.expressionFunction(
"moduleIds.push(id)",
"id"
)});`
]),
"}",
"return moduleIds;"
2021-11-30 19:55:51 +08:00
])} };`
])}`,
2022-01-13 22:26:56 +08:00
`var cssTextKey = ${runtimeTemplate.returningFunction(
`Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction(
"r.cssText",
"r"
)}).join()`,
"link"
)}`,
2021-11-30 19:55:51 +08:00
`${
RuntimeGlobals.hmrDownloadUpdateHandlers
}.css = ${runtimeTemplate.basicFunction(
"chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList",
[
"applyHandlers.push(applyHandler);",
`chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [
`var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
`var url = ${RuntimeGlobals.publicPath} + filename;`,
2022-01-13 22:26:56 +08:00
"var oldTag = loadStylesheet(chunkId, url);",
"if(!oldTag) return;",
2021-11-30 19:55:51 +08:00
`promises.push(new Promise(${runtimeTemplate.basicFunction(
"resolve, reject",
[
2022-01-13 22:26:56 +08:00
`var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction(
2021-11-30 19:55:51 +08:00
"event",
[
'if(event.type !== "load") {',
Template.indent([
"var errorType = event && event.type;",
2024-01-10 23:43:19 +08:00
"var realHref = event && event.target && event.target.href;",
"error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
2021-11-30 19:55:51 +08:00
"error.name = 'ChunkLoadError';",
"error.type = errorType;",
2024-01-10 23:43:19 +08:00
"error.request = realHref;",
2021-11-30 19:55:51 +08:00
"reject(error);"
]),
2022-01-13 22:26:56 +08:00
"} else {",
Template.indent([
"try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}",
"var factories = {};",
"loadCssChunkData(factories, link, chunkId);",
`Object.keys(factories).forEach(${runtimeTemplate.expressionFunction(
"updatedModulesList.push(id)",
"id"
)})`,
"link.sheet.disabled = true;",
"oldTags.push(oldTag);",
"newTags.push([chunkId, link]);",
"resolve();"
]),
"}"
2021-11-30 19:55:51 +08:00
]
)}, oldTag);`
2021-11-30 19:55:51 +08:00
]
)}));`
])});`
]
)}`
2024-01-14 09:41:34 +08:00
])
2021-11-30 19:55:51 +08:00
: "// no hmr"
]);
}
}
module.exports = CssLoadingRuntimeModule;