Projekt

Obecné

Profil

Stáhnout (17 KB) Statistiky
| Větev: | Revize:
1
/*
2
	MIT License http://www.opensource.org/licenses/mit-license.php
3
	Author Tobias Koppers @sokra
4
*/
5
"use strict";
6

    
7
const {
8
	ConcatSource,
9
	OriginalSource,
10
	PrefixSource,
11
	RawSource
12
} = require("webpack-sources");
13
const {
14
	Tapable,
15
	SyncWaterfallHook,
16
	SyncHook,
17
	SyncBailHook
18
} = require("tapable");
19
const Template = require("./Template");
20

    
21
/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
22
/** @typedef {import("webpack-sources").Source} Source */
23
/** @typedef {import("./ModuleTemplate")} ModuleTemplate */
24
/** @typedef {import("./Chunk")} Chunk */
25
/** @typedef {import("./Module")} Module} */
26
/** @typedef {import("./util/createHash").Hash} Hash} */
27
/** @typedef {import("./Dependency").DependencyTemplate} DependencyTemplate} */
28

    
29
/**
30
 * @typedef {Object} RenderManifestOptions
31
 * @property {Chunk} chunk the chunk used to render
32
 * @property {string} hash
33
 * @property {string} fullHash
34
 * @property {TODO} outputOptions
35
 * @property {{javascript: ModuleTemplate, webassembly: ModuleTemplate}} moduleTemplates
36
 * @property {Map<TODO, TODO>} dependencyTemplates
37
 */
38

    
39
// require function shortcuts:
40
// __webpack_require__.s = the module id of the entry point
41
// __webpack_require__.c = the module cache
42
// __webpack_require__.m = the module functions
43
// __webpack_require__.p = the bundle public path
44
// __webpack_require__.i = the identity function used for harmony imports
45
// __webpack_require__.e = the chunk ensure function
46
// __webpack_require__.d = the exported property define getter function
47
// __webpack_require__.o = Object.prototype.hasOwnProperty.call
48
// __webpack_require__.r = define compatibility on export
49
// __webpack_require__.t = create a fake namespace object
50
// __webpack_require__.n = compatibility get default export
51
// __webpack_require__.h = the webpack hash
52
// __webpack_require__.w = an object containing all installed WebAssembly.Instance export objects keyed by module id
53
// __webpack_require__.oe = the uncaught error handler for the webpack runtime
54
// __webpack_require__.nc = the script nonce
55

    
56
module.exports = class MainTemplate extends Tapable {
57
	/**
58
	 *
59
	 * @param {TODO=} outputOptions output options for the MainTemplate
60
	 */
61
	constructor(outputOptions) {
62
		super();
63
		/** @type {TODO?} */
64
		this.outputOptions = outputOptions || {};
65
		this.hooks = {
66
			/** @type {SyncWaterfallHook<TODO[], RenderManifestOptions>} */
67
			renderManifest: new SyncWaterfallHook(["result", "options"]),
68
			modules: new SyncWaterfallHook([
69
				"modules",
70
				"chunk",
71
				"hash",
72
				"moduleTemplate",
73
				"dependencyTemplates"
74
			]),
75
			moduleObj: new SyncWaterfallHook([
76
				"source",
77
				"chunk",
78
				"hash",
79
				"moduleIdExpression"
80
			]),
81
			requireEnsure: new SyncWaterfallHook([
82
				"source",
83
				"chunk",
84
				"hash",
85
				"chunkIdExpression"
86
			]),
87
			bootstrap: new SyncWaterfallHook([
88
				"source",
89
				"chunk",
90
				"hash",
91
				"moduleTemplate",
92
				"dependencyTemplates"
93
			]),
94
			localVars: new SyncWaterfallHook(["source", "chunk", "hash"]),
95
			require: new SyncWaterfallHook(["source", "chunk", "hash"]),
96
			requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]),
97
			/** @type {SyncWaterfallHook<string, Chunk, string>} */
98
			beforeStartup: new SyncWaterfallHook(["source", "chunk", "hash"]),
99
			/** @type {SyncWaterfallHook<string, Chunk, string>} */
100
			startup: new SyncWaterfallHook(["source", "chunk", "hash"]),
101
			/** @type {SyncWaterfallHook<string, Chunk, string>} */
102
			afterStartup: new SyncWaterfallHook(["source", "chunk", "hash"]),
103
			render: new SyncWaterfallHook([
104
				"source",
105
				"chunk",
106
				"hash",
107
				"moduleTemplate",
108
				"dependencyTemplates"
109
			]),
110
			renderWithEntry: new SyncWaterfallHook(["source", "chunk", "hash"]),
111
			moduleRequire: new SyncWaterfallHook([
112
				"source",
113
				"chunk",
114
				"hash",
115
				"moduleIdExpression"
116
			]),
117
			addModule: new SyncWaterfallHook([
118
				"source",
119
				"chunk",
120
				"hash",
121
				"moduleIdExpression",
122
				"moduleExpression"
123
			]),
124
			currentHash: new SyncWaterfallHook(["source", "requestedLength"]),
125
			assetPath: new SyncWaterfallHook(["path", "options", "assetInfo"]),
126
			hash: new SyncHook(["hash"]),
127
			hashForChunk: new SyncHook(["hash", "chunk"]),
128
			globalHashPaths: new SyncWaterfallHook(["paths"]),
129
			globalHash: new SyncBailHook(["chunk", "paths"]),
130

    
131
			// TODO this should be moved somewhere else
132
			// It's weird here
133
			hotBootstrap: new SyncWaterfallHook(["source", "chunk", "hash"])
134
		};
135
		this.hooks.startup.tap("MainTemplate", (source, chunk, hash) => {
136
			/** @type {string[]} */
137
			const buf = [];
138
			if (chunk.entryModule) {
139
				buf.push("// Load entry module and return exports");
140
				buf.push(
141
					`return ${this.renderRequireFunctionForModule(
142
						hash,
143
						chunk,
144
						JSON.stringify(chunk.entryModule.id)
145
					)}(${this.requireFn}.s = ${JSON.stringify(chunk.entryModule.id)});`
146
				);
147
			}
148
			return Template.asString(buf);
149
		});
150
		this.hooks.render.tap(
151
			"MainTemplate",
152
			(bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => {
153
				const source = new ConcatSource();
154
				source.add("/******/ (function(modules) { // webpackBootstrap\n");
155
				source.add(new PrefixSource("/******/", bootstrapSource));
156
				source.add("/******/ })\n");
157
				source.add(
158
					"/************************************************************************/\n"
159
				);
160
				source.add("/******/ (");
161
				source.add(
162
					this.hooks.modules.call(
163
						new RawSource(""),
164
						chunk,
165
						hash,
166
						moduleTemplate,
167
						dependencyTemplates
168
					)
169
				);
170
				source.add(")");
171
				return source;
172
			}
173
		);
174
		this.hooks.localVars.tap("MainTemplate", (source, chunk, hash) => {
175
			return Template.asString([
176
				source,
177
				"// The module cache",
178
				"var installedModules = {};"
179
			]);
180
		});
181
		this.hooks.require.tap("MainTemplate", (source, chunk, hash) => {
182
			return Template.asString([
183
				source,
184
				"// Check if module is in cache",
185
				"if(installedModules[moduleId]) {",
186
				Template.indent("return installedModules[moduleId].exports;"),
187
				"}",
188
				"// Create a new module (and put it into the cache)",
189
				"var module = installedModules[moduleId] = {",
190
				Template.indent(this.hooks.moduleObj.call("", chunk, hash, "moduleId")),
191
				"};",
192
				"",
193
				Template.asString(
194
					outputOptions.strictModuleExceptionHandling
195
						? [
196
								"// Execute the module function",
197
								"var threw = true;",
198
								"try {",
199
								Template.indent([
200
									`modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
201
										hash,
202
										chunk,
203
										"moduleId"
204
									)});`,
205
									"threw = false;"
206
								]),
207
								"} finally {",
208
								Template.indent([
209
									"if(threw) delete installedModules[moduleId];"
210
								]),
211
								"}"
212
						  ]
213
						: [
214
								"// Execute the module function",
215
								`modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
216
									hash,
217
									chunk,
218
									"moduleId"
219
								)});`
220
						  ]
221
				),
222
				"",
223
				"// Flag the module as loaded",
224
				"module.l = true;",
225
				"",
226
				"// Return the exports of the module",
227
				"return module.exports;"
228
			]);
229
		});
230
		this.hooks.moduleObj.tap(
231
			"MainTemplate",
232
			(source, chunk, hash, varModuleId) => {
233
				return Template.asString(["i: moduleId,", "l: false,", "exports: {}"]);
234
			}
235
		);
236
		this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
237
			const buf = [];
238
			const chunkMaps = chunk.getChunkMaps();
239
			// Check if there are non initial chunks which need to be imported using require-ensure
240
			if (Object.keys(chunkMaps.hash).length) {
241
				buf.push("// This file contains only the entry chunk.");
242
				buf.push("// The chunk loading function for additional chunks");
243
				buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
244
				buf.push(Template.indent("var promises = [];"));
245
				buf.push(
246
					Template.indent(
247
						this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
248
					)
249
				);
250
				buf.push(Template.indent("return Promise.all(promises);"));
251
				buf.push("};");
252
			} else if (
253
				chunk.hasModuleInGraph(m =>
254
					m.blocks.some(b => b.chunkGroup && b.chunkGroup.chunks.length > 0)
255
				)
256
			) {
257
				// There async blocks in the graph, so we need to add an empty requireEnsure
258
				// function anyway. This can happen with multiple entrypoints.
259
				buf.push("// The chunk loading function for additional chunks");
260
				buf.push("// Since all referenced chunks are already included");
261
				buf.push("// in this file, this function is empty here.");
262
				buf.push(`${this.requireFn}.e = function requireEnsure() {`);
263
				buf.push(Template.indent("return Promise.resolve();"));
264
				buf.push("};");
265
			}
266
			buf.push("");
267
			buf.push("// expose the modules object (__webpack_modules__)");
268
			buf.push(`${this.requireFn}.m = modules;`);
269

    
270
			buf.push("");
271
			buf.push("// expose the module cache");
272
			buf.push(`${this.requireFn}.c = installedModules;`);
273

    
274
			buf.push("");
275
			buf.push("// define getter function for harmony exports");
276
			buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
277
			buf.push(
278
				Template.indent([
279
					`if(!${this.requireFn}.o(exports, name)) {`,
280
					Template.indent([
281
						"Object.defineProperty(exports, name, { enumerable: true, get: getter });"
282
					]),
283
					"}"
284
				])
285
			);
286
			buf.push("};");
287

    
288
			buf.push("");
289
			buf.push("// define __esModule on exports");
290
			buf.push(`${this.requireFn}.r = function(exports) {`);
291
			buf.push(
292
				Template.indent([
293
					"if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
294
					Template.indent([
295
						"Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
296
					]),
297
					"}",
298
					"Object.defineProperty(exports, '__esModule', { value: true });"
299
				])
300
			);
301
			buf.push("};");
302

    
303
			buf.push("");
304
			buf.push("// create a fake namespace object");
305
			buf.push("// mode & 1: value is a module id, require it");
306
			buf.push("// mode & 2: merge all properties of value into the ns");
307
			buf.push("// mode & 4: return value when already ns object");
308
			buf.push("// mode & 8|1: behave like require");
309
			buf.push(`${this.requireFn}.t = function(value, mode) {`);
310
			buf.push(
311
				Template.indent([
312
					`if(mode & 1) value = ${this.requireFn}(value);`,
313
					`if(mode & 8) return value;`,
314
					"if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
315
					"var ns = Object.create(null);",
316
					`${this.requireFn}.r(ns);`,
317
					"Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
318
					"if(mode & 2 && typeof value != 'string') for(var key in value) " +
319
						`${this.requireFn}.d(ns, key, function(key) { ` +
320
						"return value[key]; " +
321
						"}.bind(null, key));",
322
					"return ns;"
323
				])
324
			);
325
			buf.push("};");
326

    
327
			buf.push("");
328
			buf.push(
329
				"// getDefaultExport function for compatibility with non-harmony modules"
330
			);
331
			buf.push(this.requireFn + ".n = function(module) {");
332
			buf.push(
333
				Template.indent([
334
					"var getter = module && module.__esModule ?",
335
					Template.indent([
336
						"function getDefault() { return module['default']; } :",
337
						"function getModuleExports() { return module; };"
338
					]),
339
					`${this.requireFn}.d(getter, 'a', getter);`,
340
					"return getter;"
341
				])
342
			);
343
			buf.push("};");
344

    
345
			buf.push("");
346
			buf.push("// Object.prototype.hasOwnProperty.call");
347
			buf.push(
348
				`${this.requireFn}.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
349
			);
350

    
351
			const publicPath = this.getPublicPath({
352
				hash: hash
353
			});
354
			buf.push("");
355
			buf.push("// __webpack_public_path__");
356
			buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
357
			return Template.asString(buf);
358
		});
359

    
360
		this.requireFn = "__webpack_require__";
361
	}
362

    
363
	/**
364
	 *
365
	 * @param {RenderManifestOptions} options render manifest options
366
	 * @returns {TODO[]} returns render manifest
367
	 */
368
	getRenderManifest(options) {
369
		const result = [];
370

    
371
		this.hooks.renderManifest.call(result, options);
372

    
373
		return result;
374
	}
375

    
376
	/**
377
	 * TODO webpack 5: remove moduleTemplate and dependencyTemplates
378
	 * @param {string} hash hash to be used for render call
379
	 * @param {Chunk} chunk Chunk instance
380
	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
381
	 * @param {Map<Function, DependencyTemplate>} dependencyTemplates dependency templates
382
	 * @returns {string[]} the generated source of the bootstrap code
383
	 */
384
	renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates) {
385
		const buf = [];
386
		buf.push(
387
			this.hooks.bootstrap.call(
388
				"",
389
				chunk,
390
				hash,
391
				moduleTemplate,
392
				dependencyTemplates
393
			)
394
		);
395
		buf.push(this.hooks.localVars.call("", chunk, hash));
396
		buf.push("");
397
		buf.push("// The require function");
398
		buf.push(`function ${this.requireFn}(moduleId) {`);
399
		buf.push(Template.indent(this.hooks.require.call("", chunk, hash)));
400
		buf.push("}");
401
		buf.push("");
402
		buf.push(
403
			Template.asString(this.hooks.requireExtensions.call("", chunk, hash))
404
		);
405
		buf.push("");
406
		buf.push(Template.asString(this.hooks.beforeStartup.call("", chunk, hash)));
407
		const afterStartupCode = Template.asString(
408
			this.hooks.afterStartup.call("", chunk, hash)
409
		);
410
		if (afterStartupCode) {
411
			// TODO webpack 5: this is a bit hacky to avoid a breaking change
412
			// change it to a better way
413
			buf.push("var startupResult = (function() {");
414
		}
415
		buf.push(Template.asString(this.hooks.startup.call("", chunk, hash)));
416
		if (afterStartupCode) {
417
			buf.push("})();");
418
			buf.push(afterStartupCode);
419
			buf.push("return startupResult;");
420
		}
421
		return buf;
422
	}
423

    
424
	/**
425
	 * @param {string} hash hash to be used for render call
426
	 * @param {Chunk} chunk Chunk instance
427
	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
428
	 * @param {Map<Function, DependencyTemplate>} dependencyTemplates dependency templates
429
	 * @returns {ConcatSource} the newly generated source from rendering
430
	 */
431
	render(hash, chunk, moduleTemplate, dependencyTemplates) {
432
		const buf = this.renderBootstrap(
433
			hash,
434
			chunk,
435
			moduleTemplate,
436
			dependencyTemplates
437
		);
438
		let source = this.hooks.render.call(
439
			new OriginalSource(
440
				Template.prefix(buf, " \t") + "\n",
441
				"webpack/bootstrap"
442
			),
443
			chunk,
444
			hash,
445
			moduleTemplate,
446
			dependencyTemplates
447
		);
448
		if (chunk.hasEntryModule()) {
449
			source = this.hooks.renderWithEntry.call(source, chunk, hash);
450
		}
451
		if (!source) {
452
			throw new Error(
453
				"Compiler error: MainTemplate plugin 'render' should return something"
454
			);
455
		}
456
		chunk.rendered = true;
457
		return new ConcatSource(source, ";");
458
	}
459

    
460
	/**
461
	 *
462
	 * @param {string} hash hash for render fn
463
	 * @param {Chunk} chunk Chunk instance for require
464
	 * @param {(number|string)=} varModuleId module id
465
	 * @returns {TODO} the moduleRequire hook call return signature
466
	 */
467
	renderRequireFunctionForModule(hash, chunk, varModuleId) {
468
		return this.hooks.moduleRequire.call(
469
			this.requireFn,
470
			chunk,
471
			hash,
472
			varModuleId
473
		);
474
	}
475

    
476
	/**
477
	 *
478
	 * @param {string} hash hash for render add fn
479
	 * @param {Chunk} chunk Chunk instance for require add fn
480
	 * @param {(string|number)=} varModuleId module id
481
	 * @param {Module} varModule Module instance
482
	 * @returns {TODO} renderAddModule call
483
	 */
484
	renderAddModule(hash, chunk, varModuleId, varModule) {
485
		return this.hooks.addModule.call(
486
			`modules[${varModuleId}] = ${varModule};`,
487
			chunk,
488
			hash,
489
			varModuleId,
490
			varModule
491
		);
492
	}
493

    
494
	/**
495
	 *
496
	 * @param {string} hash string hash
497
	 * @param {number=} length length
498
	 * @returns {string} call hook return
499
	 */
500
	renderCurrentHashCode(hash, length) {
501
		length = length || Infinity;
502
		return this.hooks.currentHash.call(
503
			JSON.stringify(hash.substr(0, length)),
504
			length
505
		);
506
	}
507

    
508
	/**
509
	 *
510
	 * @param {object} options get public path options
511
	 * @returns {string} hook call
512
	 */
513
	getPublicPath(options) {
514
		return this.hooks.assetPath.call(
515
			this.outputOptions.publicPath || "",
516
			options
517
		);
518
	}
519

    
520
	getAssetPath(path, options) {
521
		return this.hooks.assetPath.call(path, options);
522
	}
523

    
524
	getAssetPathWithInfo(path, options) {
525
		const assetInfo = {};
526
		// TODO webpack 5: refactor assetPath hook to receive { path, info } object
527
		const newPath = this.hooks.assetPath.call(path, options, assetInfo);
528
		return { path: newPath, info: assetInfo };
529
	}
530

    
531
	/**
532
	 * Updates hash with information from this template
533
	 * @param {Hash} hash the hash to update
534
	 * @returns {void}
535
	 */
536
	updateHash(hash) {
537
		hash.update("maintemplate");
538
		hash.update("3");
539
		this.hooks.hash.call(hash);
540
	}
541

    
542
	/**
543
	 * TODO webpack 5: remove moduleTemplate and dependencyTemplates
544
	 * Updates hash with chunk-specific information from this template
545
	 * @param {Hash} hash the hash to update
546
	 * @param {Chunk} chunk the chunk
547
	 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
548
	 * @param {Map<Function, DependencyTemplate>} dependencyTemplates dependency templates
549
	 * @returns {void}
550
	 */
551
	updateHashForChunk(hash, chunk, moduleTemplate, dependencyTemplates) {
552
		this.updateHash(hash);
553
		this.hooks.hashForChunk.call(hash, chunk);
554
		for (const line of this.renderBootstrap(
555
			"0000",
556
			chunk,
557
			moduleTemplate,
558
			dependencyTemplates
559
		)) {
560
			hash.update(line);
561
		}
562
	}
563

    
564
	useChunkHash(chunk) {
565
		const paths = this.hooks.globalHashPaths.call([]);
566
		return !this.hooks.globalHash.call(chunk, paths);
567
	}
568
};
(76-76/144)