UNPKG

15.6 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const { SyncWaterfallHook } = require("tapable");
8const Compilation = require("../Compilation");
9const RuntimeGlobals = require("../RuntimeGlobals");
10const RuntimeModule = require("../RuntimeModule");
11const Template = require("../Template");
12const chunkHasJs = require("../javascript/JavascriptModulesPlugin").chunkHasJs;
13const { getInitialChunkIds } = require("../javascript/StartupHelpers");
14const compileBooleanMatcher = require("../util/compileBooleanMatcher");
15
16/** @typedef {import("../Chunk")} Chunk */
17
18/**
19 * @typedef {Object} JsonpCompilationPluginHooks
20 * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
21 * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
22 */
23
24/** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
25const compilationHooksMap = new WeakMap();
26
27class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
28 /**
29 * @param {Compilation} compilation the compilation
30 * @returns {JsonpCompilationPluginHooks} hooks
31 */
32 static getCompilationHooks(compilation) {
33 if (!(compilation instanceof Compilation)) {
34 throw new TypeError(
35 "The 'compilation' argument must be an instance of Compilation"
36 );
37 }
38 let hooks = compilationHooksMap.get(compilation);
39 if (hooks === undefined) {
40 hooks = {
41 linkPreload: new SyncWaterfallHook(["source", "chunk"]),
42 linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
43 };
44 compilationHooksMap.set(compilation, hooks);
45 }
46 return hooks;
47 }
48
49 constructor(runtimeRequirements) {
50 super("jsonp chunk loading", RuntimeModule.STAGE_ATTACH);
51 this._runtimeRequirements = runtimeRequirements;
52 }
53
54 /**
55 * @returns {string} runtime code
56 */
57 generate() {
58 const { chunkGraph, compilation, chunk } = this;
59 const {
60 runtimeTemplate,
61 outputOptions: {
62 chunkLoadingGlobal,
63 hotUpdateGlobal,
64 crossOriginLoading,
65 scriptType
66 }
67 } = compilation;
68 const globalObject = runtimeTemplate.globalObject;
69 const { linkPreload, linkPrefetch } =
70 JsonpChunkLoadingRuntimeModule.getCompilationHooks(compilation);
71 const fn = RuntimeGlobals.ensureChunkHandlers;
72 const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
73 const withLoading = this._runtimeRequirements.has(
74 RuntimeGlobals.ensureChunkHandlers
75 );
76 const withCallback = this._runtimeRequirements.has(
77 RuntimeGlobals.chunkCallback
78 );
79 const withOnChunkLoad = this._runtimeRequirements.has(
80 RuntimeGlobals.onChunksLoaded
81 );
82 const withHmr = this._runtimeRequirements.has(
83 RuntimeGlobals.hmrDownloadUpdateHandlers
84 );
85 const withHmrManifest = this._runtimeRequirements.has(
86 RuntimeGlobals.hmrDownloadManifest
87 );
88 const withPrefetch = this._runtimeRequirements.has(
89 RuntimeGlobals.prefetchChunkHandlers
90 );
91 const withPreload = this._runtimeRequirements.has(
92 RuntimeGlobals.preloadChunkHandlers
93 );
94 const chunkLoadingGlobalExpr = `${globalObject}[${JSON.stringify(
95 chunkLoadingGlobal
96 )}]`;
97 const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
98 const hasJsMatcher = compileBooleanMatcher(conditionMap);
99 const initialChunkIds = getInitialChunkIds(chunk, chunkGraph, chunkHasJs);
100
101 const stateExpression = withHmr
102 ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_jsonp`
103 : undefined;
104
105 return Template.asString([
106 withBaseURI
107 ? Template.asString([
108 `${RuntimeGlobals.baseURI} = document.baseURI || self.location.href;`
109 ])
110 : "// no baseURI",
111 "",
112 "// object to store loaded and loading chunks",
113 "// undefined = chunk not loaded, null = chunk preloaded/prefetched",
114 "// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
115 `var installedChunks = ${
116 stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
117 }{`,
118 Template.indent(
119 Array.from(initialChunkIds, id => `${JSON.stringify(id)}: 0`).join(
120 ",\n"
121 )
122 ),
123 "};",
124 "",
125 withLoading
126 ? Template.asString([
127 `${fn}.j = ${runtimeTemplate.basicFunction(
128 "chunkId, promises",
129 hasJsMatcher !== false
130 ? Template.indent([
131 "// JSONP chunk loading for javascript",
132 `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
133 'if(installedChunkData !== 0) { // 0 means "already installed".',
134 Template.indent([
135 "",
136 '// a Promise means "currently loading".',
137 "if(installedChunkData) {",
138 Template.indent([
139 "promises.push(installedChunkData[2]);"
140 ]),
141 "} else {",
142 Template.indent([
143 hasJsMatcher === true
144 ? "if(true) { // all chunks have JS"
145 : `if(${hasJsMatcher("chunkId")}) {`,
146 Template.indent([
147 "// setup Promise in chunk cache",
148 `var promise = new Promise(${runtimeTemplate.expressionFunction(
149 `installedChunkData = installedChunks[chunkId] = [resolve, reject]`,
150 "resolve, reject"
151 )});`,
152 "promises.push(installedChunkData[2] = promise);",
153 "",
154 "// start chunk loading",
155 `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
156 "// create error before stack unwound to get useful stacktrace later",
157 "var error = new Error();",
158 `var loadingEnded = ${runtimeTemplate.basicFunction(
159 "event",
160 [
161 `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
162 Template.indent([
163 "installedChunkData = installedChunks[chunkId];",
164 "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
165 "if(installedChunkData) {",
166 Template.indent([
167 "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
168 "var realSrc = event && event.target && event.target.src;",
169 "error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
170 "error.name = 'ChunkLoadError';",
171 "error.type = errorType;",
172 "error.request = realSrc;",
173 "installedChunkData[1](error);"
174 ]),
175 "}"
176 ]),
177 "}"
178 ]
179 )};`,
180 `${RuntimeGlobals.loadScript}(url, loadingEnded, "chunk-" + chunkId, chunkId);`
181 ]),
182 "} else installedChunks[chunkId] = 0;"
183 ]),
184 "}"
185 ]),
186 "}"
187 ])
188 : Template.indent(["installedChunks[chunkId] = 0;"])
189 )};`
190 ])
191 : "// no chunk on demand loading",
192 "",
193 withPrefetch && hasJsMatcher !== false
194 ? `${
195 RuntimeGlobals.prefetchChunkHandlers
196 }.j = ${runtimeTemplate.basicFunction("chunkId", [
197 `if((!${
198 RuntimeGlobals.hasOwnProperty
199 }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
200 hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
201 }) {`,
202 Template.indent([
203 "installedChunks[chunkId] = null;",
204 linkPrefetch.call(
205 Template.asString([
206 "var link = document.createElement('link');",
207 crossOriginLoading
208 ? `link.crossOrigin = ${JSON.stringify(
209 crossOriginLoading
210 )};`
211 : "",
212 `if (${RuntimeGlobals.scriptNonce}) {`,
213 Template.indent(
214 `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
215 ),
216 "}",
217 'link.rel = "prefetch";',
218 'link.as = "script";',
219 `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`
220 ]),
221 chunk
222 ),
223 "document.head.appendChild(link);"
224 ]),
225 "}"
226 ])};`
227 : "// no prefetching",
228 "",
229 withPreload && hasJsMatcher !== false
230 ? `${
231 RuntimeGlobals.preloadChunkHandlers
232 }.j = ${runtimeTemplate.basicFunction("chunkId", [
233 `if((!${
234 RuntimeGlobals.hasOwnProperty
235 }(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
236 hasJsMatcher === true ? "true" : hasJsMatcher("chunkId")
237 }) {`,
238 Template.indent([
239 "installedChunks[chunkId] = null;",
240 linkPreload.call(
241 Template.asString([
242 "var link = document.createElement('link');",
243 scriptType
244 ? `link.type = ${JSON.stringify(scriptType)};`
245 : "",
246 "link.charset = 'utf-8';",
247 `if (${RuntimeGlobals.scriptNonce}) {`,
248 Template.indent(
249 `link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
250 ),
251 "}",
252 'link.rel = "preload";',
253 'link.as = "script";',
254 `link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
255 crossOriginLoading
256 ? Template.asString([
257 "if (link.href.indexOf(window.location.origin + '/') !== 0) {",
258 Template.indent(
259 `link.crossOrigin = ${JSON.stringify(
260 crossOriginLoading
261 )};`
262 ),
263 "}"
264 ])
265 : ""
266 ]),
267 chunk
268 ),
269 "document.head.appendChild(link);"
270 ]),
271 "}"
272 ])};`
273 : "// no preloaded",
274 "",
275 withHmr
276 ? Template.asString([
277 "var currentUpdatedModulesList;",
278 "var waitingUpdateResolves = {};",
279 "function loadUpdateChunk(chunkId) {",
280 Template.indent([
281 `return new Promise(${runtimeTemplate.basicFunction(
282 "resolve, reject",
283 [
284 "waitingUpdateResolves[chunkId] = resolve;",
285 "// start update chunk loading",
286 `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`,
287 "// create error before stack unwound to get useful stacktrace later",
288 "var error = new Error();",
289 `var loadingEnded = ${runtimeTemplate.basicFunction("event", [
290 "if(waitingUpdateResolves[chunkId]) {",
291 Template.indent([
292 "waitingUpdateResolves[chunkId] = undefined",
293 "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
294 "var realSrc = event && event.target && event.target.src;",
295 "error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
296 "error.name = 'ChunkLoadError';",
297 "error.type = errorType;",
298 "error.request = realSrc;",
299 "reject(error);"
300 ]),
301 "}"
302 ])};`,
303 `${RuntimeGlobals.loadScript}(url, loadingEnded);`
304 ]
305 )});`
306 ]),
307 "}",
308 "",
309 `${globalObject}[${JSON.stringify(
310 hotUpdateGlobal
311 )}] = ${runtimeTemplate.basicFunction(
312 "chunkId, moreModules, runtime",
313 [
314 "for(var moduleId in moreModules) {",
315 Template.indent([
316 `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
317 Template.indent([
318 "currentUpdate[moduleId] = moreModules[moduleId];",
319 "if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);"
320 ]),
321 "}"
322 ]),
323 "}",
324 "if(runtime) currentUpdateRuntime.push(runtime);",
325 "if(waitingUpdateResolves[chunkId]) {",
326 Template.indent([
327 "waitingUpdateResolves[chunkId]();",
328 "waitingUpdateResolves[chunkId] = undefined;"
329 ]),
330 "}"
331 ]
332 )};`,
333 "",
334 Template.getFunctionContent(
335 require("../hmr/JavascriptHotModuleReplacement.runtime.js")
336 )
337 .replace(/\$key\$/g, "jsonp")
338 .replace(/\$installedChunks\$/g, "installedChunks")
339 .replace(/\$loadUpdateChunk\$/g, "loadUpdateChunk")
340 .replace(/\$moduleCache\$/g, RuntimeGlobals.moduleCache)
341 .replace(/\$moduleFactories\$/g, RuntimeGlobals.moduleFactories)
342 .replace(
343 /\$ensureChunkHandlers\$/g,
344 RuntimeGlobals.ensureChunkHandlers
345 )
346 .replace(/\$hasOwnProperty\$/g, RuntimeGlobals.hasOwnProperty)
347 .replace(/\$hmrModuleData\$/g, RuntimeGlobals.hmrModuleData)
348 .replace(
349 /\$hmrDownloadUpdateHandlers\$/g,
350 RuntimeGlobals.hmrDownloadUpdateHandlers
351 )
352 .replace(
353 /\$hmrInvalidateModuleHandlers\$/g,
354 RuntimeGlobals.hmrInvalidateModuleHandlers
355 )
356 ])
357 : "// no HMR",
358 "",
359 withHmrManifest
360 ? Template.asString([
361 `${
362 RuntimeGlobals.hmrDownloadManifest
363 } = ${runtimeTemplate.basicFunction("", [
364 'if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");',
365 `return fetch(${RuntimeGlobals.publicPath} + ${
366 RuntimeGlobals.getUpdateManifestFilename
367 }()).then(${runtimeTemplate.basicFunction("response", [
368 "if(response.status === 404) return; // no update available",
369 'if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);',
370 "return response.json();"
371 ])});`
372 ])};`
373 ])
374 : "// no HMR manifest",
375 "",
376 withOnChunkLoad
377 ? `${
378 RuntimeGlobals.onChunksLoaded
379 }.j = ${runtimeTemplate.returningFunction(
380 "installedChunks[chunkId] === 0",
381 "chunkId"
382 )};`
383 : "// no on chunks loaded",
384 "",
385 withCallback || withLoading
386 ? Template.asString([
387 "// install a JSONP callback for chunk loading",
388 `var webpackJsonpCallback = ${runtimeTemplate.basicFunction(
389 "parentChunkLoadingFunction, data",
390 [
391 runtimeTemplate.destructureArray(
392 ["chunkIds", "moreModules", "runtime"],
393 "data"
394 ),
395 '// add "moreModules" to the modules object,',
396 '// then flag all "chunkIds" as loaded and fire callback',
397 "var moduleId, chunkId, i = 0;",
398 `if(chunkIds.some(${runtimeTemplate.returningFunction(
399 "installedChunks[id] !== 0",
400 "id"
401 )})) {`,
402 Template.indent([
403 "for(moduleId in moreModules) {",
404 Template.indent([
405 `if(${RuntimeGlobals.hasOwnProperty}(moreModules, moduleId)) {`,
406 Template.indent(
407 `${RuntimeGlobals.moduleFactories}[moduleId] = moreModules[moduleId];`
408 ),
409 "}"
410 ]),
411 "}",
412 "if(runtime) var result = runtime(__webpack_require__);"
413 ]),
414 "}",
415 "if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);",
416 "for(;i < chunkIds.length; i++) {",
417 Template.indent([
418 "chunkId = chunkIds[i];",
419 `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
420 Template.indent("installedChunks[chunkId][0]();"),
421 "}",
422 "installedChunks[chunkId] = 0;"
423 ]),
424 "}",
425 withOnChunkLoad
426 ? `return ${RuntimeGlobals.onChunksLoaded}(result);`
427 : ""
428 ]
429 )}`,
430 "",
431 `var chunkLoadingGlobal = ${chunkLoadingGlobalExpr} = ${chunkLoadingGlobalExpr} || [];`,
432 "chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));",
433 "chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"
434 ])
435 : "// no jsonp function"
436 ]);
437 }
438}
439
440module.exports = JsonpChunkLoadingRuntimeModule;