UNPKG

15.6 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { SyncWaterfallHook } = require("tapable");
9const Compilation = require("../Compilation");
10const RuntimeGlobals = require("../RuntimeGlobals");
11const RuntimeModule = require("../RuntimeModule");
12const Template = require("../Template");
13const compileBooleanMatcher = require("../util/compileBooleanMatcher");
14const { chunkHasCss } = require("./CssModulesPlugin");
15
16/** @typedef {import("../Chunk")} Chunk */
17
18/**
19 * @typedef {Object} JsonpCompilationPluginHooks
20 * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
21 */
22
23/** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
24const compilationHooksMap = new WeakMap();
25
26class CssLoadingRuntimeModule extends RuntimeModule {
27 /**
28 * @param {Compilation} compilation the compilation
29 * @returns {JsonpCompilationPluginHooks} hooks
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 * @returns {string} runtime code
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(
278 "chunkId, promises",
279 hasCssMatcher !== false
280 ? [
281 "// css chunk loading",
282 `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
283 'if(installedChunkData !== 0) { // 0 means "already installed".',
284 Template.indent([
285 "",
286 '// a Promise means "currently loading".',
287 "if(installedChunkData) {",
288 Template.indent([
289 "promises.push(installedChunkData[2]);"
290 ]),
291 "} else {",
292 Template.indent([
293 hasCssMatcher === true
294 ? "if(true) { // all chunks have CSS"
295 : `if(${hasCssMatcher("chunkId")}) {`,
296 Template.indent([
297 "// setup Promise in chunk cache",
298 `var promise = new Promise(${runtimeTemplate.expressionFunction(
299 `installedChunkData = installedChunks[chunkId] = [resolve, reject]`,
300 "resolve, reject"
301 )});`,
302 "promises.push(installedChunkData[2] = promise);",
303 "",
304 "// start chunk loading",
305 `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
306 "// create error before stack unwound to get useful stacktrace later",
307 "var error = new Error();",
308 `var loadingEnded = ${runtimeTemplate.basicFunction(
309 "event",
310 [
311 `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
312 Template.indent([
313 "installedChunkData = installedChunks[chunkId];",
314 "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
315 "if(installedChunkData) {",
316 Template.indent([
317 'if(event.type !== "load") {',
318 Template.indent([
319 "var errorType = event && event.type;",
320 "var realSrc = event && event.target && event.target.src;",
321 "error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
322 "error.name = 'ChunkLoadError';",
323 "error.type = errorType;",
324 "error.request = realSrc;",
325 "installedChunkData[1](error);"
326 ]),
327 "} else {",
328 Template.indent([
329 `loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`,
330 "installedChunkData[0]();"
331 ]),
332 "}"
333 ]),
334 "}"
335 ]),
336 "}"
337 ]
338 )};`,
339 "var link = loadStylesheet(chunkId, url, loadingEnded);"
340 ]),
341 "} else installedChunks[chunkId] = 0;"
342 ]),
343 "}"
344 ]),
345 "}"
346 ]
347 : "installedChunks[chunkId] = 0;"
348 )};`
349 ])
350 : "// no chunk loading",
351 "",
352 withHmr
353 ? Template.asString([
354 "var oldTags = [];",
355 "var newTags = [];",
356 `var applyHandler = ${runtimeTemplate.basicFunction("options", [
357 `return { dispose: ${runtimeTemplate.basicFunction(
358 "",
359 []
360 )}, apply: ${runtimeTemplate.basicFunction("", [
361 "var moduleIds = [];",
362 `newTags.forEach(${runtimeTemplate.expressionFunction(
363 "info[1].sheet.disabled = false",
364 "info"
365 )});`,
366 "while(oldTags.length) {",
367 Template.indent([
368 "var oldTag = oldTags.pop();",
369 "if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
370 ]),
371 "}",
372 "while(newTags.length) {",
373 Template.indent([
374 `var info = newTags.pop();`,
375 `var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[1], info[0]);`,
376 `chunkModuleIds.forEach(${runtimeTemplate.expressionFunction(
377 "moduleIds.push(id)",
378 "id"
379 )});`
380 ]),
381 "}",
382 "return moduleIds;"
383 ])} };`
384 ])}`,
385 `var cssTextKey = ${runtimeTemplate.returningFunction(
386 `Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction(
387 "r.cssText",
388 "r"
389 )}).join()`,
390 "link"
391 )}`,
392 `${
393 RuntimeGlobals.hmrDownloadUpdateHandlers
394 }.css = ${runtimeTemplate.basicFunction(
395 "chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList",
396 [
397 "applyHandlers.push(applyHandler);",
398 `chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [
399 `var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
400 `var url = ${RuntimeGlobals.publicPath} + filename;`,
401 "var oldTag = loadStylesheet(chunkId, url);",
402 "if(!oldTag) return;",
403 `promises.push(new Promise(${runtimeTemplate.basicFunction(
404 "resolve, reject",
405 [
406 `var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction(
407 "event",
408 [
409 'if(event.type !== "load") {',
410 Template.indent([
411 "var errorType = event && event.type;",
412 "var realSrc = event && event.target && event.target.src;",
413 "error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
414 "error.name = 'ChunkLoadError';",
415 "error.type = errorType;",
416 "error.request = realSrc;",
417 "reject(error);"
418 ]),
419 "} else {",
420 Template.indent([
421 "try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}",
422 "var factories = {};",
423 "loadCssChunkData(factories, link, chunkId);",
424 `Object.keys(factories).forEach(${runtimeTemplate.expressionFunction(
425 "updatedModulesList.push(id)",
426 "id"
427 )})`,
428 "link.sheet.disabled = true;",
429 "oldTags.push(oldTag);",
430 "newTags.push([chunkId, link]);",
431 "resolve();"
432 ]),
433 "}"
434 ]
435 )}, oldTag);`
436 ]
437 )}));`
438 ])});`
439 ]
440 )}`
441 ])
442 : "// no hmr"
443 ]);
444 }
445}
446
447module.exports = CssLoadingRuntimeModule;