UNPKG

7.25 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 {
13 getChunkFilenameTemplate,
14 chunkHasJs
15} = require("../javascript/JavascriptModulesPlugin");
16const { getInitialChunkIds } = require("../javascript/StartupHelpers");
17const compileBooleanMatcher = require("../util/compileBooleanMatcher");
18const { getUndoPath } = require("../util/identifier");
19
20/** @typedef {import("../Chunk")} Chunk */
21
22/**
23 * @typedef {Object} JsonpCompilationPluginHooks
24 * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
25 * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
26 */
27
28/** @type {WeakMap<Compilation, JsonpCompilationPluginHooks>} */
29const compilationHooksMap = new WeakMap();
30
31class ModuleChunkLoadingRuntimeModule extends RuntimeModule {
32 /**
33 * @param {Compilation} compilation the compilation
34 * @returns {JsonpCompilationPluginHooks} hooks
35 */
36 static getCompilationHooks(compilation) {
37 if (!(compilation instanceof Compilation)) {
38 throw new TypeError(
39 "The 'compilation' argument must be an instance of Compilation"
40 );
41 }
42 let hooks = compilationHooksMap.get(compilation);
43 if (hooks === undefined) {
44 hooks = {
45 linkPreload: new SyncWaterfallHook(["source", "chunk"]),
46 linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
47 };
48 compilationHooksMap.set(compilation, hooks);
49 }
50 return hooks;
51 }
52
53 constructor(runtimeRequirements) {
54 super("import chunk loading", RuntimeModule.STAGE_ATTACH);
55 this._runtimeRequirements = runtimeRequirements;
56 }
57
58 /**
59 * @returns {string} runtime code
60 */
61 generate() {
62 const { compilation, chunk } = this;
63 const {
64 runtimeTemplate,
65 chunkGraph,
66 outputOptions: { importFunctionName, importMetaName }
67 } = compilation;
68 const fn = RuntimeGlobals.ensureChunkHandlers;
69 const withBaseURI = this._runtimeRequirements.has(RuntimeGlobals.baseURI);
70 const withExternalInstallChunk = this._runtimeRequirements.has(
71 RuntimeGlobals.externalInstallChunk
72 );
73 const withLoading = this._runtimeRequirements.has(
74 RuntimeGlobals.ensureChunkHandlers
75 );
76 const withOnChunkLoad = this._runtimeRequirements.has(
77 RuntimeGlobals.onChunksLoaded
78 );
79 const withHmr = this._runtimeRequirements.has(
80 RuntimeGlobals.hmrDownloadUpdateHandlers
81 );
82 const conditionMap = chunkGraph.getChunkConditionMap(chunk, chunkHasJs);
83 const hasJsMatcher = compileBooleanMatcher(conditionMap);
84 const initialChunkIds = getInitialChunkIds(chunk, chunkGraph);
85
86 const outputName = this.compilation.getPath(
87 getChunkFilenameTemplate(chunk, this.compilation.outputOptions),
88 {
89 chunk,
90 contentHashType: "javascript"
91 }
92 );
93 const rootOutputDir = getUndoPath(
94 outputName,
95 this.compilation.outputOptions.path,
96 true
97 );
98
99 const stateExpression = withHmr
100 ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_module`
101 : undefined;
102
103 return Template.asString([
104 withBaseURI
105 ? Template.asString([
106 `${RuntimeGlobals.baseURI} = new URL(${JSON.stringify(
107 rootOutputDir
108 )}, ${importMetaName}.url);`
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 || withExternalInstallChunk
126 ? `var installChunk = ${runtimeTemplate.basicFunction("data", [
127 runtimeTemplate.destructureObject(
128 ["ids", "modules", "runtime"],
129 "data"
130 ),
131 '// add "modules" to the modules object,',
132 '// then flag all "ids" as loaded and fire callback',
133 "var moduleId, chunkId, i = 0;",
134 "for(moduleId in modules) {",
135 Template.indent([
136 `if(${RuntimeGlobals.hasOwnProperty}(modules, moduleId)) {`,
137 Template.indent(
138 `${RuntimeGlobals.moduleFactories}[moduleId] = modules[moduleId];`
139 ),
140 "}"
141 ]),
142 "}",
143 "if(runtime) runtime(__webpack_require__);",
144 "for(;i < ids.length; i++) {",
145 Template.indent([
146 "chunkId = ids[i];",
147 `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) && installedChunks[chunkId]) {`,
148 Template.indent("installedChunks[chunkId][0]();"),
149 "}",
150 "installedChunks[ids[i]] = 0;"
151 ]),
152 "}",
153 withOnChunkLoad ? `${RuntimeGlobals.onChunksLoaded}();` : ""
154 ])}`
155 : "// no install chunk",
156 "",
157 withLoading
158 ? Template.asString([
159 `${fn}.j = ${runtimeTemplate.basicFunction(
160 "chunkId, promises",
161 hasJsMatcher !== false
162 ? Template.indent([
163 "// import() chunk loading for javascript",
164 `var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
165 'if(installedChunkData !== 0) { // 0 means "already installed".',
166 Template.indent([
167 "",
168 '// a Promise means "currently loading".',
169 "if(installedChunkData) {",
170 Template.indent([
171 "promises.push(installedChunkData[1]);"
172 ]),
173 "} else {",
174 Template.indent([
175 hasJsMatcher === true
176 ? "if(true) { // all chunks have JS"
177 : `if(${hasJsMatcher("chunkId")}) {`,
178 Template.indent([
179 "// setup Promise in chunk cache",
180 `var promise = ${importFunctionName}(${JSON.stringify(
181 rootOutputDir
182 )} + ${
183 RuntimeGlobals.getChunkScriptFilename
184 }(chunkId)).then(installChunk, ${runtimeTemplate.basicFunction(
185 "e",
186 [
187 "if(installedChunks[chunkId] !== 0) installedChunks[chunkId] = undefined;",
188 "throw e;"
189 ]
190 )});`,
191 `var promise = Promise.race([promise, new Promise(${runtimeTemplate.expressionFunction(
192 `installedChunkData = installedChunks[chunkId] = [resolve]`,
193 "resolve"
194 )})])`,
195 `promises.push(installedChunkData[1] = promise);`
196 ]),
197 "} else installedChunks[chunkId] = 0;"
198 ]),
199 "}"
200 ]),
201 "}"
202 ])
203 : Template.indent(["installedChunks[chunkId] = 0;"])
204 )};`
205 ])
206 : "// no chunk on demand loading",
207 "",
208 withExternalInstallChunk
209 ? Template.asString([
210 `${RuntimeGlobals.externalInstallChunk} = installChunk;`
211 ])
212 : "// no external install chunk",
213 "",
214 withOnChunkLoad
215 ? `${
216 RuntimeGlobals.onChunksLoaded
217 }.j = ${runtimeTemplate.returningFunction(
218 "installedChunks[chunkId] === 0",
219 "chunkId"
220 )};`
221 : "// no on chunks loaded"
222 ]);
223 }
224}
225
226module.exports = ModuleChunkLoadingRuntimeModule;