UNPKG

15.3 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("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
440module.exports = CssLoadingRuntimeModule;