CssLoadingRuntimeModule.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { SyncWaterfallHook } = require("tapable");
  7. const Compilation = require("../Compilation");
  8. const RuntimeGlobals = require("../RuntimeGlobals");
  9. const RuntimeModule = require("../RuntimeModule");
  10. const Template = require("../Template");
  11. const compileBooleanMatcher = require("../util/compileBooleanMatcher");
  12. const { chunkHasCss } = require("./CssModulesPlugin");
  13. /** @typedef {import("../Chunk")} Chunk */
  14. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  15. /** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */
  16. /**
  17. * @typedef {Object} JsonpCompilationPluginHooks
  18. * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
  19. */
  20. /** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
  21. const compilationHooksMap = new WeakMap();
  22. class CssLoadingRuntimeModule extends RuntimeModule {
  23. /**
  24. * @param {Compilation} compilation the compilation
  25. * @returns {JsonpCompilationPluginHooks} hooks
  26. */
  27. static getCompilationHooks(compilation) {
  28. if (!(compilation instanceof Compilation)) {
  29. throw new TypeError(
  30. "The 'compilation' argument must be an instance of Compilation"
  31. );
  32. }
  33. let hooks = compilationHooksMap.get(compilation);
  34. if (hooks === undefined) {
  35. hooks = {
  36. createStylesheet: new SyncWaterfallHook(["source", "chunk"])
  37. };
  38. compilationHooksMap.set(compilation, hooks);
  39. }
  40. return hooks;
  41. }
  42. /**
  43. * @param {Set<string>} runtimeRequirements runtime requirements
  44. */
  45. constructor(runtimeRequirements) {
  46. super("css loading", 10);
  47. this._runtimeRequirements = runtimeRequirements;
  48. }
  49. /**
  50. * @returns {string | null} runtime code
  51. */
  52. generate() {
  53. const { compilation, chunk, _runtimeRequirements } = this;
  54. const {
  55. chunkGraph,
  56. runtimeTemplate,
  57. outputOptions: {
  58. crossOriginLoading,
  59. uniqueName,
  60. chunkLoadTimeout: loadTimeout
  61. }
  62. } = /** @type {Compilation} */ (compilation);
  63. const fn = RuntimeGlobals.ensureChunkHandlers;
  64. const conditionMap = chunkGraph.getChunkConditionMap(
  65. /** @type {Chunk} */ (chunk),
  66. /**
  67. * @param {Chunk} chunk the chunk
  68. * @param {ChunkGraph} chunkGraph the chunk graph
  69. * @returns {boolean} true, if the chunk has css
  70. */
  71. (chunk, chunkGraph) =>
  72. !!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")
  73. );
  74. const hasCssMatcher = compileBooleanMatcher(conditionMap);
  75. const withLoading =
  76. _runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
  77. hasCssMatcher !== false;
  78. /** @type {boolean} */
  79. const withHmr = _runtimeRequirements.has(
  80. RuntimeGlobals.hmrDownloadUpdateHandlers
  81. );
  82. /** @type {Set<number | string | null>} */
  83. const initialChunkIdsWithCss = new Set();
  84. /** @type {Set<number | string | null>} */
  85. const initialChunkIdsWithoutCss = new Set();
  86. for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) {
  87. (chunkHasCss(c, chunkGraph)
  88. ? initialChunkIdsWithCss
  89. : initialChunkIdsWithoutCss
  90. ).add(c.id);
  91. }
  92. if (!withLoading && !withHmr && initialChunkIdsWithCss.size === 0) {
  93. return null;
  94. }
  95. const { createStylesheet } = CssLoadingRuntimeModule.getCompilationHooks(
  96. /** @type {Compilation} */ (compilation)
  97. );
  98. const stateExpression = withHmr
  99. ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css`
  100. : undefined;
  101. const code = Template.asString([
  102. "link = document.createElement('link');",
  103. uniqueName
  104. ? 'link.setAttribute("data-webpack", uniqueName + ":" + key);'
  105. : "",
  106. "link.setAttribute(loadingAttribute, 1);",
  107. 'link.rel = "stylesheet";',
  108. "link.href = url;",
  109. crossOriginLoading
  110. ? crossOriginLoading === "use-credentials"
  111. ? 'link.crossOrigin = "use-credentials";'
  112. : Template.asString([
  113. "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
  114. Template.indent(
  115. `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
  116. ),
  117. "}"
  118. ])
  119. : ""
  120. ]);
  121. /** @type {(str: string) => number} */
  122. const cc = str => str.charCodeAt(0);
  123. const name = uniqueName
  124. ? runtimeTemplate.concatenation(
  125. "--webpack-",
  126. { expr: "uniqueName" },
  127. "-",
  128. { expr: "chunkId" }
  129. )
  130. : runtimeTemplate.concatenation("--webpack-", { expr: "chunkId" });
  131. return Template.asString([
  132. "// object to store loaded and loading chunks",
  133. "// undefined = chunk not loaded, null = chunk preloaded/prefetched",
  134. "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
  135. `var installedChunks = ${
  136. stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
  137. }{${Array.from(
  138. initialChunkIdsWithoutCss,
  139. id => `${JSON.stringify(id)}:0`
  140. ).join(",")}};`,
  141. "",
  142. uniqueName
  143. ? `var uniqueName = ${JSON.stringify(
  144. runtimeTemplate.outputOptions.uniqueName
  145. )};`
  146. : "// data-webpack is not used as build has no uniqueName",
  147. `var loadCssChunkData = ${runtimeTemplate.basicFunction(
  148. "target, link, chunkId",
  149. [
  150. `var data, token = "", token2, exports = {}, exportsWithId = [], exportsWithDashes = [], ${
  151. withHmr ? "moduleIds = [], " : ""
  152. }name = ${name}, i = 0, cc = 1;`,
  153. "try {",
  154. Template.indent([
  155. "if(!link) link = loadStylesheet(chunkId);",
  156. // `link.sheet.rules` for legacy browsers
  157. "var cssRules = link.sheet.cssRules || link.sheet.rules;",
  158. "var j = cssRules.length - 1;",
  159. "while(j > -1 && !data) {",
  160. Template.indent([
  161. "var style = cssRules[j--].style;",
  162. "if(!style) continue;",
  163. `data = style.getPropertyValue(name);`
  164. ]),
  165. "}"
  166. ]),
  167. "}catch(e){}",
  168. "if(!data) {",
  169. Template.indent([
  170. "data = getComputedStyle(document.head).getPropertyValue(name);"
  171. ]),
  172. "}",
  173. "if(!data) return [];",
  174. "for(; cc; i++) {",
  175. Template.indent([
  176. "cc = data.charCodeAt(i);",
  177. `if(cc == ${cc("(")}) { token2 = token; token = ""; }`,
  178. `else if(cc == ${cc(
  179. ")"
  180. )}) { exports[token2.replace(/^_/, "")] = token.replace(/^_/, ""); token = ""; }`,
  181. `else if(cc == ${cc("/")} || cc == ${cc(
  182. "%"
  183. )}) { token = token.replace(/^_/, ""); exports[token] = token; exportsWithId.push(token); if(cc == ${cc(
  184. "%"
  185. )}) exportsWithDashes.push(token); token = ""; }`,
  186. `else if(!cc || cc == ${cc(
  187. ","
  188. )}) { token = token.replace(/^_/, ""); exportsWithId.forEach(${runtimeTemplate.expressionFunction(
  189. `exports[x] = ${
  190. uniqueName
  191. ? runtimeTemplate.concatenation(
  192. { expr: "uniqueName" },
  193. "-",
  194. { expr: "token" },
  195. "-",
  196. { expr: "exports[x]" }
  197. )
  198. : runtimeTemplate.concatenation({ expr: "token" }, "-", {
  199. expr: "exports[x]"
  200. })
  201. }`,
  202. "x"
  203. )}); exportsWithDashes.forEach(${runtimeTemplate.expressionFunction(
  204. `exports[x] = "--" + exports[x]`,
  205. "x"
  206. )}); ${
  207. RuntimeGlobals.makeNamespaceObject
  208. }(exports); target[token] = (${runtimeTemplate.basicFunction(
  209. "exports, module",
  210. `module.exports = exports;`
  211. )}).bind(null, exports); ${
  212. withHmr ? "moduleIds.push(token); " : ""
  213. }token = ""; exports = {}; exportsWithId.length = 0; }`,
  214. `else if(cc == ${cc("\\")}) { token += data[++i] }`,
  215. `else { token += data[i]; }`
  216. ]),
  217. "}",
  218. `${
  219. withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : ""
  220. }installedChunks[chunkId] = 0;`,
  221. withHmr ? "return moduleIds;" : ""
  222. ]
  223. )}`,
  224. 'var loadingAttribute = "data-webpack-loading";',
  225. `var loadStylesheet = ${runtimeTemplate.basicFunction(
  226. "chunkId, url, done" + (withHmr ? ", hmr" : ""),
  227. [
  228. 'var link, needAttach, key = "chunk-" + chunkId;',
  229. withHmr ? "if(!hmr) {" : "",
  230. 'var links = document.getElementsByTagName("link");',
  231. "for(var i = 0; i < links.length; i++) {",
  232. Template.indent([
  233. "var l = links[i];",
  234. `if(l.rel == "stylesheet" && (${
  235. withHmr
  236. ? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)'
  237. : 'l.href == url || l.getAttribute("href") == url'
  238. }${
  239. uniqueName
  240. ? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key'
  241. : ""
  242. })) { link = l; break; }`
  243. ]),
  244. "}",
  245. "if(!done) return link;",
  246. withHmr ? "}" : "",
  247. "if(!link) {",
  248. Template.indent([
  249. "needAttach = true;",
  250. createStylesheet.call(code, /** @type {Chunk} */ (this.chunk))
  251. ]),
  252. "}",
  253. `var onLinkComplete = ${runtimeTemplate.basicFunction(
  254. "prev, event",
  255. Template.asString([
  256. "link.onerror = link.onload = null;",
  257. "link.removeAttribute(loadingAttribute);",
  258. "clearTimeout(timeout);",
  259. 'if(event && event.type != "load") link.parentNode.removeChild(link)',
  260. "done(event);",
  261. "if(prev) return prev(event);"
  262. ])
  263. )};`,
  264. "if(link.getAttribute(loadingAttribute)) {",
  265. Template.indent([
  266. `var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`,
  267. "link.onerror = onLinkComplete.bind(null, link.onerror);",
  268. "link.onload = onLinkComplete.bind(null, link.onload);"
  269. ]),
  270. "} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking
  271. withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "",
  272. "needAttach && document.head.appendChild(link);",
  273. "return link;"
  274. ]
  275. )};`,
  276. initialChunkIdsWithCss.size > 2
  277. ? `${JSON.stringify(
  278. Array.from(initialChunkIdsWithCss)
  279. )}.forEach(loadCssChunkData.bind(null, ${
  280. RuntimeGlobals.moduleFactories
  281. }, 0));`
  282. : initialChunkIdsWithCss.size > 0
  283. ? `${Array.from(
  284. initialChunkIdsWithCss,
  285. id =>
  286. `loadCssChunkData(${
  287. RuntimeGlobals.moduleFactories
  288. }, 0, ${JSON.stringify(id)});`
  289. ).join("")}`
  290. : "// no initial css",
  291. "",
  292. withLoading
  293. ? Template.asString([
  294. `${fn}.css = ${runtimeTemplate.basicFunction("chunkId, promises", [
  295. "// css chunk loading",
  296. `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
  297. 'if(installedChunkData !== 0) { // 0 means "already installed".',
  298. Template.indent([
  299. "",
  300. '// a Promise means "currently loading".',
  301. "if(installedChunkData) {",
  302. Template.indent(["promises.push(installedChunkData[2]);"]),
  303. "} else {",
  304. Template.indent([
  305. hasCssMatcher === true
  306. ? "if(true) { // all chunks have CSS"
  307. : `if(${hasCssMatcher("chunkId")}) {`,
  308. Template.indent([
  309. "// setup Promise in chunk cache",
  310. `var promise = new Promise(${runtimeTemplate.expressionFunction(
  311. `installedChunkData = installedChunks[chunkId] = [resolve, reject]`,
  312. "resolve, reject"
  313. )});`,
  314. "promises.push(installedChunkData[2] = promise);",
  315. "",
  316. "// start chunk loading",
  317. `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
  318. "// create error before stack unwound to get useful stacktrace later",
  319. "var error = new Error();",
  320. `var loadingEnded = ${runtimeTemplate.basicFunction(
  321. "event",
  322. [
  323. `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
  324. Template.indent([
  325. "installedChunkData = installedChunks[chunkId];",
  326. "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
  327. "if(installedChunkData) {",
  328. Template.indent([
  329. 'if(event.type !== "load") {',
  330. Template.indent([
  331. "var errorType = event && event.type;",
  332. "var realSrc = event && event.target && event.target.src;",
  333. "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
  334. "error.name = 'ChunkLoadError';",
  335. "error.type = errorType;",
  336. "error.request = realSrc;",
  337. "installedChunkData[1](error);"
  338. ]),
  339. "} else {",
  340. Template.indent([
  341. `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`,
  342. "installedChunkData[0]();"
  343. ]),
  344. "}"
  345. ]),
  346. "}"
  347. ]),
  348. "}"
  349. ]
  350. )};`,
  351. "var link = loadStylesheet(chunkId, url, loadingEnded);"
  352. ]),
  353. "} else installedChunks[chunkId] = 0;"
  354. ]),
  355. "}"
  356. ]),
  357. "}"
  358. ])};`
  359. ])
  360. : "// no chunk loading",
  361. "",
  362. withHmr
  363. ? Template.asString([
  364. "var oldTags = [];",
  365. "var newTags = [];",
  366. `var applyHandler = ${runtimeTemplate.basicFunction("options", [
  367. `return { dispose: ${runtimeTemplate.basicFunction(
  368. "",
  369. []
  370. )}, apply: ${runtimeTemplate.basicFunction("", [
  371. "var moduleIds = [];",
  372. `newTags.forEach(${runtimeTemplate.expressionFunction(
  373. "info[1].sheet.disabled = false",
  374. "info"
  375. )});`,
  376. "while(oldTags.length) {",
  377. Template.indent([
  378. "var oldTag = oldTags.pop();",
  379. "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
  380. ]),
  381. "}",
  382. "while(newTags.length) {",
  383. Template.indent([
  384. `var info = newTags.pop();`,
  385. `var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[1], info[0]);`,
  386. `chunkModuleIds.forEach(${runtimeTemplate.expressionFunction(
  387. "moduleIds.push(id)",
  388. "id"
  389. )});`
  390. ]),
  391. "}",
  392. "return moduleIds;"
  393. ])} };`
  394. ])}`,
  395. `var cssTextKey = ${runtimeTemplate.returningFunction(
  396. `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction(
  397. "r.cssText",
  398. "r"
  399. )}).join()`,
  400. "link"
  401. )}`,
  402. `${
  403. RuntimeGlobals.hmrDownloadUpdateHandlers
  404. }.css = ${runtimeTemplate.basicFunction(
  405. "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList",
  406. [
  407. "applyHandlers.push(applyHandler);",
  408. `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [
  409. `var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
  410. `var url = ${RuntimeGlobals.publicPath} + filename;`,
  411. "var oldTag = loadStylesheet(chunkId, url);",
  412. "if(!oldTag) return;",
  413. `promises.push(new Promise(${runtimeTemplate.basicFunction(
  414. "resolve, reject",
  415. [
  416. `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction(
  417. "event",
  418. [
  419. 'if(event.type !== "load") {',
  420. Template.indent([
  421. "var errorType = event && event.type;",
  422. "var realSrc = event && event.target && event.target.src;",
  423. "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
  424. "error.name = 'ChunkLoadError';",
  425. "error.type = errorType;",
  426. "error.request = realSrc;",
  427. "reject(error);"
  428. ]),
  429. "} else {",
  430. Template.indent([
  431. "try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}",
  432. "var factories = {};",
  433. "loadCssChunkData(factories, link, chunkId);",
  434. `Object.keys(factories).forEach(${runtimeTemplate.expressionFunction(
  435. "updatedModulesList.push(id)",
  436. "id"
  437. )})`,
  438. "link.sheet.disabled = true;",
  439. "oldTags.push(oldTag);",
  440. "newTags.push([chunkId, link]);",
  441. "resolve();"
  442. ]),
  443. "}"
  444. ]
  445. )}, oldTag);`
  446. ]
  447. )}));`
  448. ])});`
  449. ]
  450. )}`
  451. ])
  452. : "// no hmr"
  453. ]);
  454. }
  455. }
  456. module.exports = CssLoadingRuntimeModule;