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