UNPKG

17.4 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 /** @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};