CssLoadingRuntimeModule.js 15 KB

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