123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- /*
- 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");
- const { chunkHasCss } = require("./CssModulesPlugin");
- /** @typedef {import("../Chunk")} Chunk */
- /** @typedef {import("../ChunkGraph")} ChunkGraph */
- /** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */
- /** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
- /**
- * @typedef {object} CssLoadingRuntimeModulePluginHooks
- * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
- * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
- */
- /** @type {WeakMap<Compilation, CssLoadingRuntimeModulePluginHooks>} */
- const compilationHooksMap = new WeakMap();
- class CssLoadingRuntimeModule extends RuntimeModule {
- /**
- * @param {Compilation} compilation the compilation
- * @returns {CssLoadingRuntimeModulePluginHooks} 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"]),
- linkPreload: new SyncWaterfallHook(["source", "chunk"]),
- linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
- };
- compilationHooksMap.set(compilation, hooks);
- }
- return hooks;
- }
- /**
- * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
- */
- constructor(runtimeRequirements) {
- super("css loading", 10);
- this._runtimeRequirements = runtimeRequirements;
- }
- /**
- * @returns {string | null} runtime code
- */
- generate() {
- const { _runtimeRequirements } = this;
- const compilation = /** @type {Compilation} */ (this.compilation);
- const chunk = /** @type {Chunk} */ (this.chunk);
- const {
- chunkGraph,
- runtimeTemplate,
- outputOptions: {
- crossOriginLoading,
- uniqueName,
- chunkLoadTimeout: loadTimeout,
- cssHeadDataCompression: withCompression
- }
- } = compilation;
- const fn = RuntimeGlobals.ensureChunkHandlers;
- const conditionMap = chunkGraph.getChunkConditionMap(
- /** @type {Chunk} */ (chunk),
- /**
- * @param {Chunk} chunk the chunk
- * @param {ChunkGraph} chunkGraph the chunk graph
- * @returns {boolean} true, if the chunk has css
- */
- (chunk, chunkGraph) =>
- !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")
- );
- const hasCssMatcher = compileBooleanMatcher(conditionMap);
- const withLoading =
- _runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
- hasCssMatcher !== false;
- const withPrefetch = this._runtimeRequirements.has(
- RuntimeGlobals.prefetchChunkHandlers
- );
- const withPreload = this._runtimeRequirements.has(
- RuntimeGlobals.preloadChunkHandlers
- );
- /** @type {boolean} */
- const withHmr = _runtimeRequirements.has(
- RuntimeGlobals.hmrDownloadUpdateHandlers
- );
- /** @type {Set<number | string | null>} */
- const initialChunkIdsWithCss = new Set();
- /** @type {Set<number | string | null>} */
- const initialChunkIdsWithoutCss = new Set();
- for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) {
- (chunkHasCss(c, chunkGraph)
- ? initialChunkIdsWithCss
- : initialChunkIdsWithoutCss
- ).add(c.id);
- }
- if (!withLoading && !withHmr && initialChunkIdsWithCss.size === 0) {
- return null;
- }
- const { linkPreload, linkPrefetch } =
- CssLoadingRuntimeModule.getCompilationHooks(compilation);
- const withFetchPriority = _runtimeRequirements.has(
- RuntimeGlobals.hasFetchPriority
- );
- const { createStylesheet } =
- CssLoadingRuntimeModule.getCompilationHooks(compilation);
- const stateExpression = withHmr
- ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css`
- : undefined;
- const code = Template.asString([
- "link = document.createElement('link');",
- `if (${RuntimeGlobals.scriptNonce}) {`,
- Template.indent(
- `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
- ),
- "}",
- uniqueName
- ? 'link.setAttribute("data-webpack", uniqueName + ":" + key);'
- : "",
- withFetchPriority
- ? Template.asString([
- "if(fetchPriority) {",
- Template.indent(
- 'link.setAttribute("fetchpriority", fetchPriority);'
- ),
- "}"
- ])
- : "",
- "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)};`
- ),
- "}"
- ])
- : ""
- ]);
- /** @type {(str: string) => number} */
- const cc = str => str.charCodeAt(0);
- const name = uniqueName
- ? runtimeTemplate.concatenation(
- "--webpack-",
- { expr: "uniqueName" },
- "-",
- { expr: "chunkId" }
- )
- : runtimeTemplate.concatenation("--webpack-", { expr: "chunkId" });
- 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} || ` : ""
- }{${Array.from(
- initialChunkIdsWithoutCss,
- id => `${JSON.stringify(id)}:0`
- ).join(",")}};`,
- "",
- uniqueName
- ? `var uniqueName = ${JSON.stringify(
- runtimeTemplate.outputOptions.uniqueName
- )};`
- : "// data-webpack is not used as build has no uniqueName",
- `var loadCssChunkData = ${runtimeTemplate.basicFunction(
- "target, link, chunkId",
- [
- `var data, token = "", token2 = "", exports = {}, ${
- withHmr ? "moduleIds = [], " : ""
- }name = ${name}, i, 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);"
- ]),
- "}",
- "if(!data) return [];",
- withCompression
- ? Template.asString([
- // LZW decode
- `var map = {}, char = data[0], oldPhrase = char, decoded = char, code = 256, maxCode = ${"\uffff".charCodeAt(
- 0
- )}, phrase;`,
- "for (i = 1; i < data.length; i++) {",
- Template.indent([
- "cc = data[i].charCodeAt(0);",
- "if (cc < 256) phrase = data[i]; else phrase = map[cc] ? map[cc] : (oldPhrase + char);",
- "decoded += phrase;",
- "char = phrase.charAt(0);",
- "map[code] = oldPhrase + char;",
- "if (++code > maxCode) { code = 256; map = {}; }",
- "oldPhrase = phrase;"
- ]),
- "}",
- "data = decoded;"
- ])
- : "// css head data compression is disabled",
- "for(i = 0; cc; i++) {",
- Template.indent([
- "cc = data.charCodeAt(i);",
- `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("&")}) { ${
- RuntimeGlobals.makeNamespaceObject
- }(exports); }`,
- `else if(!cc || cc == ${cc(
- ","
- )}) { token = token.replace(/^_/, ""); target[token] = (${runtimeTemplate.basicFunction(
- "exports, module",
- `module.exports = exports;`
- )}).bind(null, exports); ${
- withHmr ? "moduleIds.push(token); " : ""
- }token = ""; token2 = ""; exports = {}; }`,
- `else if(cc == ${cc("\\")}) { token += data[++i] }`,
- `else { token += data[i]; }`
- ]),
- "}",
- `${
- withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : ""
- }installedChunks[chunkId] = 0;`,
- withHmr ? "return moduleIds;" : ""
- ]
- )}`,
- 'var loadingAttribute = "data-webpack-loading";',
- `var loadStylesheet = ${runtimeTemplate.basicFunction(
- "chunkId, url, done" +
- (withHmr ? ", hmr" : "") +
- (withFetchPriority ? ", fetchPriority" : ""),
- [
- '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" && (${
- withHmr
- ? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)'
- : 'l.href == url || l.getAttribute("href") == url'
- }${
- uniqueName
- ? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key'
- : ""
- })) { link = l; break; }`
- ]),
- "}",
- "if(!done) return link;",
- withHmr ? "}" : "",
- "if(!link) {",
- Template.indent([
- "needAttach = true;",
- createStylesheet.call(code, /** @type {Chunk} */ (this.chunk))
- ]),
- "}",
- `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) :" : "",
- "needAttach && document.head.appendChild(link);",
- "return link;"
- ]
- )};`,
- initialChunkIdsWithCss.size > 2
- ? `${JSON.stringify(
- Array.from(initialChunkIdsWithCss)
- )}.forEach(loadCssChunkData.bind(null, ${
- RuntimeGlobals.moduleFactories
- }, 0));`
- : initialChunkIdsWithCss.size > 0
- ? `${Array.from(
- initialChunkIdsWithCss,
- id =>
- `loadCssChunkData(${
- RuntimeGlobals.moduleFactories
- }, 0, ${JSON.stringify(id)});`
- ).join("")}`
- : "// no initial css",
- "",
- withLoading
- ? Template.asString([
- `${fn}.css = ${runtimeTemplate.basicFunction(
- `chunkId, promises${withFetchPriority ? " , fetchPriority" : ""}`,
- [
- "// 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)) {`,
- Template.indent([
- "installedChunkData = installedChunks[chunkId];",
- "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
- "if(installedChunkData) {",
- Template.indent([
- 'if(event.type !== "load") {',
- Template.indent([
- "var errorType = event && event.type;",
- "var realHref = event && event.target && event.target.href;",
- "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
- "error.name = 'ChunkLoadError';",
- "error.type = errorType;",
- "error.request = realHref;",
- "installedChunkData[1](error);"
- ]),
- "} else {",
- Template.indent([
- `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`,
- "installedChunkData[0]();"
- ]),
- "}"
- ]),
- "}"
- ]),
- "}"
- ]
- )};`,
- `var link = loadStylesheet(chunkId, url, loadingEnded${
- withFetchPriority ? ", fetchPriority" : ""
- });`
- ]),
- "} else installedChunks[chunkId] = 0;"
- ]),
- "}"
- ]),
- "}"
- ]
- )};`
- ])
- : "// no chunk loading",
- "",
- withPrefetch && hasCssMatcher !== false
- ? `${
- RuntimeGlobals.prefetchChunkHandlers
- }.s = ${runtimeTemplate.basicFunction("chunkId", [
- `if((!${
- RuntimeGlobals.hasOwnProperty
- }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
- hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
- }) {`,
- Template.indent([
- "installedChunks[chunkId] = null;",
- linkPrefetch.call(
- Template.asString([
- "var link = document.createElement('link');",
- crossOriginLoading
- ? `link.crossOrigin = ${JSON.stringify(
- crossOriginLoading
- )};`
- : "",
- `if (${RuntimeGlobals.scriptNonce}) {`,
- Template.indent(
- `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
- ),
- "}",
- 'link.rel = "prefetch";',
- 'link.as = "style";',
- `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`
- ]),
- chunk
- ),
- "document.head.appendChild(link);"
- ]),
- "}"
- ])};`
- : "// no prefetching",
- "",
- withPreload && hasCssMatcher !== false
- ? `${
- RuntimeGlobals.preloadChunkHandlers
- }.s = ${runtimeTemplate.basicFunction("chunkId", [
- `if((!${
- RuntimeGlobals.hasOwnProperty
- }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
- hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
- }) {`,
- Template.indent([
- "installedChunks[chunkId] = null;",
- linkPreload.call(
- Template.asString([
- "var link = document.createElement('link');",
- "link.charset = 'utf-8';",
- `if (${RuntimeGlobals.scriptNonce}) {`,
- Template.indent(
- `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
- ),
- "}",
- 'link.rel = "preload";',
- 'link.as = "style";',
- `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
- 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
- )};`
- ),
- "}"
- ])
- : ""
- ]),
- chunk
- ),
- "document.head.appendChild(link);"
- ]),
- "}"
- ])};`
- : "// no preloaded",
- withHmr
- ? Template.asString([
- "var oldTags = [];",
- "var newTags = [];",
- `var applyHandler = ${runtimeTemplate.basicFunction("options", [
- `return { dispose: ${runtimeTemplate.basicFunction(
- "",
- []
- )}, apply: ${runtimeTemplate.basicFunction("", [
- "var moduleIds = [];",
- `newTags.forEach(${runtimeTemplate.expressionFunction(
- "info[1].sheet.disabled = false",
- "info"
- )});`,
- "while(oldTags.length) {",
- Template.indent([
- "var oldTag = oldTags.pop();",
- "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
- ]),
- "}",
- "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;"
- ])} };`
- ])}`,
- `var cssTextKey = ${runtimeTemplate.returningFunction(
- `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction(
- "r.cssText",
- "r"
- )}).join()`,
- "link"
- )}`,
- `${
- 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;`,
- "var oldTag = loadStylesheet(chunkId, url);",
- "if(!oldTag) return;",
- `promises.push(new Promise(${runtimeTemplate.basicFunction(
- "resolve, reject",
- [
- `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction(
- "event",
- [
- 'if(event.type !== "load") {',
- Template.indent([
- "var errorType = event && event.type;",
- "var realHref = event && event.target && event.target.href;",
- "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
- "error.name = 'ChunkLoadError';",
- "error.type = errorType;",
- "error.request = realHref;",
- "reject(error);"
- ]),
- "} 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();"
- ]),
- "}"
- ]
- )}, oldTag);`
- ]
- )}));`
- ])});`
- ]
- )}`
- ])
- : "// no hmr"
- ]);
- }
- }
- module.exports = CssLoadingRuntimeModule;
|