Apply module bundling
[platform/framework/web/wrtjs.git] / node_modules / webpack / lib / hmr / LazyCompilationPlugin.js
1 /*
2         MIT License http://www.opensource.org/licenses/mit-license.php
3         Author Tobias Koppers @sokra
4 */
5
6 "use strict";
7
8 const { RawSource } = require("webpack-sources");
9 const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
10 const Dependency = require("../Dependency");
11 const Module = require("../Module");
12 const ModuleFactory = require("../ModuleFactory");
13 const RuntimeGlobals = require("../RuntimeGlobals");
14 const Template = require("../Template");
15 const CommonJsRequireDependency = require("../dependencies/CommonJsRequireDependency");
16 const { registerNotSerializable } = require("../util/serialization");
17
18 /** @typedef {import("../../declarations/WebpackOptions")} WebpackOptions */
19 /** @typedef {import("../Compilation")} Compilation */
20 /** @typedef {import("../Compiler")} Compiler */
21 /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
22 /** @typedef {import("../Module").BuildMeta} BuildMeta */
23 /** @typedef {import("../Module").CodeGenerationContext} CodeGenerationContext */
24 /** @typedef {import("../Module").CodeGenerationResult} CodeGenerationResult */
25 /** @typedef {import("../Module").LibIdentOptions} LibIdentOptions */
26 /** @typedef {import("../Module").NeedBuildContext} NeedBuildContext */
27 /** @typedef {import("../ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
28 /** @typedef {import("../ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
29 /** @typedef {import("../RequestShortener")} RequestShortener */
30 /** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */
31 /** @typedef {import("../WebpackError")} WebpackError */
32 /** @typedef {import("../dependencies/HarmonyImportDependency")} HarmonyImportDependency */
33 /** @typedef {import("../util/Hash")} Hash */
34 /** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
35
36 /**
37  * @typedef {Object} BackendApi
38  * @property {function(Error=): void} dispose
39  * @property {function(Module): { client: string, data: string, active: boolean }} module
40  */
41
42 const HMR_DEPENDENCY_TYPES = new Set([
43         "import.meta.webpackHot.accept",
44         "import.meta.webpackHot.decline",
45         "module.hot.accept",
46         "module.hot.decline"
47 ]);
48
49 /**
50  * @param {undefined|string|RegExp|Function} test test option
51  * @param {Module} module the module
52  * @returns {boolean} true, if the module should be selected
53  */
54 const checkTest = (test, module) => {
55         if (test === undefined) return true;
56         if (typeof test === "function") {
57                 return test(module);
58         }
59         if (typeof test === "string") {
60                 const name = module.nameForCondition();
61                 return name && name.startsWith(test);
62         }
63         if (test instanceof RegExp) {
64                 const name = module.nameForCondition();
65                 return name && test.test(name);
66         }
67         return false;
68 };
69
70 const TYPES = new Set(["javascript"]);
71
72 class LazyCompilationDependency extends Dependency {
73         constructor(proxyModule) {
74                 super();
75                 this.proxyModule = proxyModule;
76         }
77
78         get category() {
79                 return "esm";
80         }
81
82         get type() {
83                 return "lazy import()";
84         }
85
86         /**
87          * @returns {string | null} an identifier to merge equal requests
88          */
89         getResourceIdentifier() {
90                 return this.proxyModule.originalModule.identifier();
91         }
92 }
93
94 registerNotSerializable(LazyCompilationDependency);
95
96 class LazyCompilationProxyModule extends Module {
97         constructor(context, originalModule, request, client, data, active) {
98                 super("lazy-compilation-proxy", context, originalModule.layer);
99                 this.originalModule = originalModule;
100                 this.request = request;
101                 this.client = client;
102                 this.data = data;
103                 this.active = active;
104         }
105
106         /**
107          * @returns {string} a unique identifier of the module
108          */
109         identifier() {
110                 return `lazy-compilation-proxy|${this.originalModule.identifier()}`;
111         }
112
113         /**
114          * @param {RequestShortener} requestShortener the request shortener
115          * @returns {string} a user readable identifier of the module
116          */
117         readableIdentifier(requestShortener) {
118                 return `lazy-compilation-proxy ${this.originalModule.readableIdentifier(
119                         requestShortener
120                 )}`;
121         }
122
123         /**
124          * Assuming this module is in the cache. Update the (cached) module with
125          * the fresh module from the factory. Usually updates internal references
126          * and properties.
127          * @param {Module} module fresh module
128          * @returns {void}
129          */
130         updateCacheModule(module) {
131                 super.updateCacheModule(module);
132                 const m = /** @type {LazyCompilationProxyModule} */ (module);
133                 this.originalModule = m.originalModule;
134                 this.request = m.request;
135                 this.client = m.client;
136                 this.data = m.data;
137                 this.active = m.active;
138         }
139
140         /**
141          * @param {LibIdentOptions} options options
142          * @returns {string | null} an identifier for library inclusion
143          */
144         libIdent(options) {
145                 return `${this.originalModule.libIdent(options)}!lazy-compilation-proxy`;
146         }
147
148         /**
149          * @param {NeedBuildContext} context context info
150          * @param {function((WebpackError | null)=, boolean=): void} callback callback function, returns true, if the module needs a rebuild
151          * @returns {void}
152          */
153         needBuild(context, callback) {
154                 callback(null, !this.buildInfo || this.buildInfo.active !== this.active);
155         }
156
157         /**
158          * @param {WebpackOptions} options webpack options
159          * @param {Compilation} compilation the compilation
160          * @param {ResolverWithOptions} resolver the resolver
161          * @param {InputFileSystem} fs the file system
162          * @param {function(WebpackError=): void} callback callback function
163          * @returns {void}
164          */
165         build(options, compilation, resolver, fs, callback) {
166                 this.buildInfo = {
167                         active: this.active
168                 };
169                 /** @type {BuildMeta} */
170                 this.buildMeta = {};
171                 this.clearDependenciesAndBlocks();
172                 const dep = new CommonJsRequireDependency(this.client);
173                 this.addDependency(dep);
174                 if (this.active) {
175                         const dep = new LazyCompilationDependency(this);
176                         const block = new AsyncDependenciesBlock({});
177                         block.addDependency(dep);
178                         this.addBlock(block);
179                 }
180                 callback();
181         }
182
183         /**
184          * @returns {Set<string>} types available (do not mutate)
185          */
186         getSourceTypes() {
187                 return TYPES;
188         }
189
190         /**
191          * @param {string=} type the source type for which the size should be estimated
192          * @returns {number} the estimated size of the module (must be non-zero)
193          */
194         size(type) {
195                 return 200;
196         }
197
198         /**
199          * @param {CodeGenerationContext} context context for code generation
200          * @returns {CodeGenerationResult} result
201          */
202         codeGeneration({ runtimeTemplate, chunkGraph, moduleGraph }) {
203                 const sources = new Map();
204                 const runtimeRequirements = new Set();
205                 runtimeRequirements.add(RuntimeGlobals.module);
206                 const clientDep = /** @type {CommonJsRequireDependency} */ (
207                         this.dependencies[0]
208                 );
209                 const clientModule = moduleGraph.getModule(clientDep);
210                 const block = this.blocks[0];
211                 const client = Template.asString([
212                         `var client = ${runtimeTemplate.moduleExports({
213                                 module: clientModule,
214                                 chunkGraph,
215                                 request: clientDep.userRequest,
216                                 runtimeRequirements
217                         })}`,
218                         `var data = ${JSON.stringify(this.data)};`
219                 ]);
220                 const keepActive = Template.asString([
221                         `var dispose = client.keepAlive({ data: data, active: ${JSON.stringify(
222                                 !!block
223                         )}, module: module, onError: onError });`
224                 ]);
225                 let source;
226                 if (block) {
227                         const dep = block.dependencies[0];
228                         const module = moduleGraph.getModule(dep);
229                         source = Template.asString([
230                                 client,
231                                 `module.exports = ${runtimeTemplate.moduleNamespacePromise({
232                                         chunkGraph,
233                                         block,
234                                         module,
235                                         request: this.request,
236                                         strict: false, // TODO this should be inherited from the original module
237                                         message: "import()",
238                                         runtimeRequirements
239                                 })};`,
240                                 "if (module.hot) {",
241                                 Template.indent([
242                                         "module.hot.accept();",
243                                         `module.hot.accept(${JSON.stringify(
244                                                 chunkGraph.getModuleId(module)
245                                         )}, function() { module.hot.invalidate(); });`,
246                                         "module.hot.dispose(function(data) { delete data.resolveSelf; dispose(data); });",
247                                         "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);"
248                                 ]),
249                                 "}",
250                                 "function onError() { /* ignore */ }",
251                                 keepActive
252                         ]);
253                 } else {
254                         source = Template.asString([
255                                 client,
256                                 "var resolveSelf, onError;",
257                                 `module.exports = new Promise(function(resolve, reject) { resolveSelf = resolve; onError = reject; });`,
258                                 "if (module.hot) {",
259                                 Template.indent([
260                                         "module.hot.accept();",
261                                         "if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);",
262                                         "module.hot.dispose(function(data) { data.resolveSelf = resolveSelf; dispose(data); });"
263                                 ]),
264                                 "}",
265                                 keepActive
266                         ]);
267                 }
268                 sources.set("javascript", new RawSource(source));
269                 return {
270                         sources,
271                         runtimeRequirements
272                 };
273         }
274
275         /**
276          * @param {Hash} hash the hash used to track dependencies
277          * @param {UpdateHashContext} context context
278          * @returns {void}
279          */
280         updateHash(hash, context) {
281                 super.updateHash(hash, context);
282                 hash.update(this.active ? "active" : "");
283                 hash.update(JSON.stringify(this.data));
284         }
285 }
286
287 registerNotSerializable(LazyCompilationProxyModule);
288
289 class LazyCompilationDependencyFactory extends ModuleFactory {
290         constructor(factory) {
291                 super();
292                 this._factory = factory;
293         }
294
295         /**
296          * @param {ModuleFactoryCreateData} data data object
297          * @param {function(Error=, ModuleFactoryResult=): void} callback callback
298          * @returns {void}
299          */
300         create(data, callback) {
301                 const dependency = /** @type {LazyCompilationDependency} */ (
302                         data.dependencies[0]
303                 );
304                 callback(null, {
305                         module: dependency.proxyModule.originalModule
306                 });
307         }
308 }
309
310 class LazyCompilationPlugin {
311         /**
312          * @param {Object} options options
313          * @param {(function(Compiler, function(Error?, BackendApi?): void): void) | function(Compiler): Promise<BackendApi>} options.backend the backend
314          * @param {boolean} options.entries true, when entries are lazy compiled
315          * @param {boolean} options.imports true, when import() modules are lazy compiled
316          * @param {RegExp | string | (function(Module): boolean)} options.test additional filter for lazy compiled entrypoint modules
317          */
318         constructor({ backend, entries, imports, test }) {
319                 this.backend = backend;
320                 this.entries = entries;
321                 this.imports = imports;
322                 this.test = test;
323         }
324         /**
325          * Apply the plugin
326          * @param {Compiler} compiler the compiler instance
327          * @returns {void}
328          */
329         apply(compiler) {
330                 let backend;
331                 compiler.hooks.beforeCompile.tapAsync(
332                         "LazyCompilationPlugin",
333                         (params, callback) => {
334                                 if (backend !== undefined) return callback();
335                                 const promise = this.backend(compiler, (err, result) => {
336                                         if (err) return callback(err);
337                                         backend = result;
338                                         callback();
339                                 });
340                                 if (promise && promise.then) {
341                                         promise.then(b => {
342                                                 backend = b;
343                                                 callback();
344                                         }, callback);
345                                 }
346                         }
347                 );
348                 compiler.hooks.thisCompilation.tap(
349                         "LazyCompilationPlugin",
350                         (compilation, { normalModuleFactory }) => {
351                                 normalModuleFactory.hooks.module.tap(
352                                         "LazyCompilationPlugin",
353                                         (originalModule, createData, resolveData) => {
354                                                 if (
355                                                         resolveData.dependencies.every(dep =>
356                                                                 HMR_DEPENDENCY_TYPES.has(dep.type)
357                                                         )
358                                                 ) {
359                                                         // for HMR only resolving, try to determine if the HMR accept/decline refers to
360                                                         // an import() or not
361                                                         const hmrDep = resolveData.dependencies[0];
362                                                         const originModule =
363                                                                 compilation.moduleGraph.getParentModule(hmrDep);
364                                                         const isReferringToDynamicImport = originModule.blocks.some(
365                                                                 block =>
366                                                                         block.dependencies.some(
367                                                                                 dep =>
368                                                                                         dep.type === "import()" &&
369                                                                                         /** @type {HarmonyImportDependency} */ (dep).request ===
370                                                                                                 hmrDep.request
371                                                                         )
372                                                         );
373                                                         if (!isReferringToDynamicImport) return;
374                                                 } else if (
375                                                         !resolveData.dependencies.every(
376                                                                 dep =>
377                                                                         HMR_DEPENDENCY_TYPES.has(dep.type) ||
378                                                                         (this.imports &&
379                                                                                 (dep.type === "import()" ||
380                                                                                         dep.type === "import() context element")) ||
381                                                                         (this.entries && dep.type === "entry")
382                                                         )
383                                                 )
384                                                         return;
385                                                 if (
386                                                         /webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client|webpack-hot-middleware[/\\]client/.test(
387                                                                 resolveData.request
388                                                         ) ||
389                                                         !checkTest(this.test, originalModule)
390                                                 )
391                                                         return;
392                                                 const moduleInfo = backend.module(originalModule);
393                                                 if (!moduleInfo) return;
394                                                 const { client, data, active } = moduleInfo;
395
396                                                 return new LazyCompilationProxyModule(
397                                                         compiler.context,
398                                                         originalModule,
399                                                         resolveData.request,
400                                                         client,
401                                                         data,
402                                                         active
403                                                 );
404                                         }
405                                 );
406                                 compilation.dependencyFactories.set(
407                                         LazyCompilationDependency,
408                                         new LazyCompilationDependencyFactory()
409                                 );
410                         }
411                 );
412                 compiler.hooks.shutdown.tapAsync("LazyCompilationPlugin", callback => {
413                         backend.dispose(callback);
414                 });
415         }
416 }
417
418 module.exports = LazyCompilationPlugin;