UNPKG

16.6 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const {
8 ConcatSource,
9 OriginalSource,
10 PrefixSource,
11 RawSource
12} = require("webpack-sources");
13const {
14 Tapable,
15 SyncWaterfallHook,
16 SyncHook,
17 SyncBailHook
18} = require("tapable");
19const 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
56module.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 render: new SyncWaterfallHook([
102 "source",
103 "chunk",
104 "hash",
105 "moduleTemplate",
106 "dependencyTemplates"
107 ]),
108 renderWithEntry: new SyncWaterfallHook(["source", "chunk", "hash"]),
109 moduleRequire: new SyncWaterfallHook([
110 "source",
111 "chunk",
112 "hash",
113 "moduleIdExpression"
114 ]),
115 addModule: new SyncWaterfallHook([
116 "source",
117 "chunk",
118 "hash",
119 "moduleIdExpression",
120 "moduleExpression"
121 ]),
122 currentHash: new SyncWaterfallHook(["source", "requestedLength"]),
123 assetPath: new SyncWaterfallHook(["path", "options"]),
124 hash: new SyncHook(["hash"]),
125 hashForChunk: new SyncHook(["hash", "chunk"]),
126 globalHashPaths: new SyncWaterfallHook(["paths"]),
127 globalHash: new SyncBailHook(["chunk", "paths"]),
128
129 // TODO this should be moved somewhere else
130 // It's weird here
131 hotBootstrap: new SyncWaterfallHook(["source", "chunk", "hash"])
132 };
133 this.hooks.startup.tap("MainTemplate", (source, chunk, hash) => {
134 /** @type {string[]} */
135 const buf = [];
136 if (chunk.entryModule) {
137 buf.push("// Load entry module and return exports");
138 buf.push(
139 `return ${this.renderRequireFunctionForModule(
140 hash,
141 chunk,
142 JSON.stringify(chunk.entryModule.id)
143 )}(${this.requireFn}.s = ${JSON.stringify(chunk.entryModule.id)});`
144 );
145 }
146 return Template.asString(buf);
147 });
148 this.hooks.render.tap(
149 "MainTemplate",
150 (bootstrapSource, chunk, hash, moduleTemplate, dependencyTemplates) => {
151 const source = new ConcatSource();
152 source.add("/******/ (function(modules) { // webpackBootstrap\n");
153 source.add(new PrefixSource("/******/", bootstrapSource));
154 source.add("/******/ })\n");
155 source.add(
156 "/************************************************************************/\n"
157 );
158 source.add("/******/ (");
159 source.add(
160 this.hooks.modules.call(
161 new RawSource(""),
162 chunk,
163 hash,
164 moduleTemplate,
165 dependencyTemplates
166 )
167 );
168 source.add(")");
169 return source;
170 }
171 );
172 this.hooks.localVars.tap("MainTemplate", (source, chunk, hash) => {
173 return Template.asString([
174 source,
175 "// The module cache",
176 "var installedModules = {};"
177 ]);
178 });
179 this.hooks.require.tap("MainTemplate", (source, chunk, hash) => {
180 return Template.asString([
181 source,
182 "// Check if module is in cache",
183 "if(installedModules[moduleId]) {",
184 Template.indent("return installedModules[moduleId].exports;"),
185 "}",
186 "// Create a new module (and put it into the cache)",
187 "var module = installedModules[moduleId] = {",
188 Template.indent(this.hooks.moduleObj.call("", chunk, hash, "moduleId")),
189 "};",
190 "",
191 Template.asString(
192 outputOptions.strictModuleExceptionHandling
193 ? [
194 "// Execute the module function",
195 "var threw = true;",
196 "try {",
197 Template.indent([
198 `modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
199 hash,
200 chunk,
201 "moduleId"
202 )});`,
203 "threw = false;"
204 ]),
205 "} finally {",
206 Template.indent([
207 "if(threw) delete installedModules[moduleId];"
208 ]),
209 "}"
210 ]
211 : [
212 "// Execute the module function",
213 `modules[moduleId].call(module.exports, module, module.exports, ${this.renderRequireFunctionForModule(
214 hash,
215 chunk,
216 "moduleId"
217 )});`
218 ]
219 ),
220 "",
221 "// Flag the module as loaded",
222 "module.l = true;",
223 "",
224 "// Return the exports of the module",
225 "return module.exports;"
226 ]);
227 });
228 this.hooks.moduleObj.tap(
229 "MainTemplate",
230 (source, chunk, hash, varModuleId) => {
231 return Template.asString(["i: moduleId,", "l: false,", "exports: {}"]);
232 }
233 );
234 this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
235 const buf = [];
236 const chunkMaps = chunk.getChunkMaps();
237 // Check if there are non initial chunks which need to be imported using require-ensure
238 if (Object.keys(chunkMaps.hash).length) {
239 buf.push("// This file contains only the entry chunk.");
240 buf.push("// The chunk loading function for additional chunks");
241 buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
242 buf.push(Template.indent("var promises = [];"));
243 buf.push(
244 Template.indent(
245 this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
246 )
247 );
248 buf.push(Template.indent("return Promise.all(promises);"));
249 buf.push("};");
250 } else if (
251 chunk.hasModuleInGraph(m =>
252 m.blocks.some(b => b.chunkGroup && b.chunkGroup.chunks.length > 0)
253 )
254 ) {
255 // There async blocks in the graph, so we need to add an empty requireEnsure
256 // function anyway. This can happen with multiple entrypoints.
257 buf.push("// The chunk loading function for additional chunks");
258 buf.push("// Since all referenced chunks are already included");
259 buf.push("// in this file, this function is empty here.");
260 buf.push(`${this.requireFn}.e = function requireEnsure() {`);
261 buf.push(Template.indent("return Promise.resolve();"));
262 buf.push("};");
263 }
264 buf.push("");
265 buf.push("// expose the modules object (__webpack_modules__)");
266 buf.push(`${this.requireFn}.m = modules;`);
267
268 buf.push("");
269 buf.push("// expose the module cache");
270 buf.push(`${this.requireFn}.c = installedModules;`);
271
272 buf.push("");
273 buf.push("// define getter function for harmony exports");
274 buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
275 buf.push(
276 Template.indent([
277 `if(!${this.requireFn}.o(exports, name)) {`,
278 Template.indent([
279 "Object.defineProperty(exports, name, { enumerable: true, get: getter });"
280 ]),
281 "}"
282 ])
283 );
284 buf.push("};");
285
286 buf.push("");
287 buf.push("// define __esModule on exports");
288 buf.push(`${this.requireFn}.r = function(exports) {`);
289 buf.push(
290 Template.indent([
291 "if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
292 Template.indent([
293 "Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
294 ]),
295 "}",
296 "Object.defineProperty(exports, '__esModule', { value: true });"
297 ])
298 );
299 buf.push("};");
300
301 buf.push("");
302 buf.push("// create a fake namespace object");
303 buf.push("// mode & 1: value is a module id, require it");
304 buf.push("// mode & 2: merge all properties of value into the ns");
305 buf.push("// mode & 4: return value when already ns object");
306 buf.push("// mode & 8|1: behave like require");
307 buf.push(`${this.requireFn}.t = function(value, mode) {`);
308 buf.push(
309 Template.indent([
310 `if(mode & 1) value = ${this.requireFn}(value);`,
311 `if(mode & 8) return value;`,
312 "if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
313 "var ns = Object.create(null);",
314 `${this.requireFn}.r(ns);`,
315 "Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
316 "if(mode & 2 && typeof value != 'string') for(var key in value) " +
317 `${this.requireFn}.d(ns, key, function(key) { ` +
318 "return value[key]; " +
319 "}.bind(null, key));",
320 "return ns;"
321 ])
322 );
323 buf.push("};");
324
325 buf.push("");
326 buf.push(
327 "// getDefaultExport function for compatibility with non-harmony modules"
328 );
329 buf.push(this.requireFn + ".n = function(module) {");
330 buf.push(
331 Template.indent([
332 "var getter = module && module.__esModule ?",
333 Template.indent([
334 "function getDefault() { return module['default']; } :",
335 "function getModuleExports() { return module; };"
336 ]),
337 `${this.requireFn}.d(getter, 'a', getter);`,
338 "return getter;"
339 ])
340 );
341 buf.push("};");
342
343 buf.push("");
344 buf.push("// Object.prototype.hasOwnProperty.call");
345 buf.push(
346 `${
347 this.requireFn
348 }.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 buf.push(Template.asString(this.hooks.startup.call("", chunk, hash)));
408 return buf;
409 }
410
411 /**
412 * @param {string} hash hash to be used for render call
413 * @param {Chunk} chunk Chunk instance
414 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
415 * @param {Map<Function, DependencyTemplate>} dependencyTemplates dependency templates
416 * @returns {ConcatSource} the newly generated source from rendering
417 */
418 render(hash, chunk, moduleTemplate, dependencyTemplates) {
419 const buf = this.renderBootstrap(
420 hash,
421 chunk,
422 moduleTemplate,
423 dependencyTemplates
424 );
425 let source = this.hooks.render.call(
426 new OriginalSource(
427 Template.prefix(buf, " \t") + "\n",
428 "webpack/bootstrap"
429 ),
430 chunk,
431 hash,
432 moduleTemplate,
433 dependencyTemplates
434 );
435 if (chunk.hasEntryModule()) {
436 source = this.hooks.renderWithEntry.call(source, chunk, hash);
437 }
438 if (!source) {
439 throw new Error(
440 "Compiler error: MainTemplate plugin 'render' should return something"
441 );
442 }
443 chunk.rendered = true;
444 return new ConcatSource(source, ";");
445 }
446
447 /**
448 *
449 * @param {string} hash hash for render fn
450 * @param {Chunk} chunk Chunk instance for require
451 * @param {(number|string)=} varModuleId module id
452 * @returns {TODO} the moduleRequire hook call return signature
453 */
454 renderRequireFunctionForModule(hash, chunk, varModuleId) {
455 return this.hooks.moduleRequire.call(
456 this.requireFn,
457 chunk,
458 hash,
459 varModuleId
460 );
461 }
462
463 /**
464 *
465 * @param {string} hash hash for render add fn
466 * @param {Chunk} chunk Chunk instance for require add fn
467 * @param {(string|number)=} varModuleId module id
468 * @param {Module} varModule Module instance
469 * @returns {TODO} renderAddModule call
470 */
471 renderAddModule(hash, chunk, varModuleId, varModule) {
472 return this.hooks.addModule.call(
473 `modules[${varModuleId}] = ${varModule};`,
474 chunk,
475 hash,
476 varModuleId,
477 varModule
478 );
479 }
480
481 /**
482 *
483 * @param {string} hash string hash
484 * @param {number=} length length
485 * @returns {string} call hook return
486 */
487 renderCurrentHashCode(hash, length) {
488 length = length || Infinity;
489 return this.hooks.currentHash.call(
490 JSON.stringify(hash.substr(0, length)),
491 length
492 );
493 }
494
495 /**
496 *
497 * @param {object} options get public path options
498 * @returns {string} hook call
499 */
500 getPublicPath(options) {
501 return this.hooks.assetPath.call(
502 this.outputOptions.publicPath || "",
503 options
504 );
505 }
506
507 getAssetPath(path, options) {
508 return this.hooks.assetPath.call(path, options);
509 }
510
511 /**
512 * Updates hash with information from this template
513 * @param {Hash} hash the hash to update
514 * @returns {void}
515 */
516 updateHash(hash) {
517 hash.update("maintemplate");
518 hash.update("3");
519 this.hooks.hash.call(hash);
520 }
521
522 /**
523 * TODO webpack 5: remove moduleTemplate and dependencyTemplates
524 * Updates hash with chunk-specific information from this template
525 * @param {Hash} hash the hash to update
526 * @param {Chunk} chunk the chunk
527 * @param {ModuleTemplate} moduleTemplate ModuleTemplate instance for render
528 * @param {Map<Function, DependencyTemplate>} dependencyTemplates dependency templates
529 * @returns {void}
530 */
531 updateHashForChunk(hash, chunk, moduleTemplate, dependencyTemplates) {
532 this.updateHash(hash);
533 this.hooks.hashForChunk.call(hash, chunk);
534 for (const line of this.renderBootstrap(
535 "0000",
536 chunk,
537 moduleTemplate,
538 dependencyTemplates
539 )) {
540 hash.update(line);
541 }
542 }
543
544 useChunkHash(chunk) {
545 const paths = this.hooks.globalHashPaths.call([]);
546 return !this.hooks.globalHash.call(chunk, paths);
547 }
548};