mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			332 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const Template = require("../Template");
 | |
| 
 | |
| /** @typedef {import("../MainTemplate")} MainTemplate */
 | |
| 
 | |
| module.exports = class NodeMainTemplatePlugin {
 | |
| 	constructor(asyncChunkLoading) {
 | |
| 		this.asyncChunkLoading = asyncChunkLoading;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {MainTemplate} mainTemplate the main template
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	apply(mainTemplate) {
 | |
| 		const needChunkOnDemandLoadingCode = chunk => {
 | |
| 			for (const chunkGroup of chunk.groupsIterable) {
 | |
| 				if (chunkGroup.getNumberOfChildren() > 0) return true;
 | |
| 			}
 | |
| 			return false;
 | |
| 		};
 | |
| 		const asyncChunkLoading = this.asyncChunkLoading;
 | |
| 		mainTemplate.hooks.localVars.tap(
 | |
| 			"NodeMainTemplatePlugin",
 | |
| 			(source, chunk) => {
 | |
| 				if (needChunkOnDemandLoadingCode(chunk)) {
 | |
| 					return Template.asString([
 | |
| 						source,
 | |
| 						"",
 | |
| 						"// object to store loaded chunks",
 | |
| 						'// "0" means "already loaded"',
 | |
| 						"var installedChunks = {",
 | |
| 						Template.indent(
 | |
| 							chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")
 | |
| 						),
 | |
| 						"};"
 | |
| 					]);
 | |
| 				}
 | |
| 				return source;
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.requireExtensions.tap(
 | |
| 			"NodeMainTemplatePlugin",
 | |
| 			(source, { chunk }) => {
 | |
| 				if (needChunkOnDemandLoadingCode(chunk)) {
 | |
| 					return Template.asString([
 | |
| 						source,
 | |
| 						"",
 | |
| 						"// uncaught error handler for webpack runtime",
 | |
| 						`${mainTemplate.requireFn}.oe = function(err) {`,
 | |
| 						Template.indent([
 | |
| 							"process.nextTick(function() {",
 | |
| 							Template.indent(
 | |
| 								"throw err; // catch this error by using import().catch()"
 | |
| 							),
 | |
| 							"});"
 | |
| 						]),
 | |
| 						"};"
 | |
| 					]);
 | |
| 				}
 | |
| 				return source;
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.requireEnsure.tap(
 | |
| 			"NodeMainTemplatePlugin",
 | |
| 			(source, chunkIdExpression, renderContext) => {
 | |
| 				// TODO use chunkIdExpression instead of hard-coded chunkId
 | |
| 				const { chunk, hash } = renderContext;
 | |
| 				const chunkFilename = mainTemplate.outputOptions.chunkFilename;
 | |
| 				const chunkMaps = chunk.getChunkMaps();
 | |
| 				const insertMoreModules = [
 | |
| 					"var moreModules = chunk.modules, chunkIds = chunk.ids;",
 | |
| 					"for(var moduleId in moreModules) {",
 | |
| 					Template.indent(
 | |
| 						mainTemplate.renderAddModule(
 | |
| 							"moduleId",
 | |
| 							"moreModules[moduleId]",
 | |
| 							renderContext
 | |
| 						)
 | |
| 					),
 | |
| 					"}"
 | |
| 				];
 | |
| 				if (asyncChunkLoading) {
 | |
| 					return Template.asString([
 | |
| 						source,
 | |
| 						"",
 | |
| 						"// ReadFile + VM.run chunk loading for javascript",
 | |
| 						"",
 | |
| 						"var installedChunkData = installedChunks[chunkId];",
 | |
| 						'if(installedChunkData !== 0) { // 0 means "already installed".',
 | |
| 						Template.indent([
 | |
| 							'// array of [resolve, reject, promise] means "currently loading"',
 | |
| 							"if(installedChunkData) {",
 | |
| 							Template.indent(["promises.push(installedChunkData[2]);"]),
 | |
| 							"} else {",
 | |
| 							Template.indent([
 | |
| 								"// load the chunk and return promise to it",
 | |
| 								"var promise = new Promise(function(resolve, reject) {",
 | |
| 								Template.indent([
 | |
| 									"installedChunkData = installedChunks[chunkId] = [resolve, reject];",
 | |
| 									"var filename = __dirname + " +
 | |
| 										mainTemplate.getAssetPath(
 | |
| 											JSON.stringify(`/${chunkFilename}`),
 | |
| 											{
 | |
| 												hash: `" + ${mainTemplate.renderCurrentHashCode(
 | |
| 													hash
 | |
| 												)} + "`,
 | |
| 												hashWithLength: length =>
 | |
| 													`" + ${mainTemplate.renderCurrentHashCode(
 | |
| 														hash,
 | |
| 														length
 | |
| 													)} + "`,
 | |
| 												chunk: {
 | |
| 													id: '" + chunkId + "',
 | |
| 													hash: `" + ${JSON.stringify(
 | |
| 														chunkMaps.hash
 | |
| 													)}[chunkId] + "`,
 | |
| 													hashWithLength: length => {
 | |
| 														const shortChunkHashMap = {};
 | |
| 														for (const chunkId of Object.keys(chunkMaps.hash)) {
 | |
| 															if (typeof chunkMaps.hash[chunkId] === "string") {
 | |
| 																shortChunkHashMap[chunkId] = chunkMaps.hash[
 | |
| 																	chunkId
 | |
| 																].substr(0, length);
 | |
| 															}
 | |
| 														}
 | |
| 														return `" + ${JSON.stringify(
 | |
| 															shortChunkHashMap
 | |
| 														)}[chunkId] + "`;
 | |
| 													},
 | |
| 													contentHash: {
 | |
| 														javascript: `" + ${JSON.stringify(
 | |
| 															chunkMaps.contentHash.javascript
 | |
| 														)}[chunkId] + "`
 | |
| 													},
 | |
| 													contentHashWithLength: {
 | |
| 														javascript: length => {
 | |
| 															const shortContentHashMap = {};
 | |
| 															const contentHash =
 | |
| 																chunkMaps.contentHash.javascript;
 | |
| 															for (const chunkId of Object.keys(contentHash)) {
 | |
| 																if (typeof contentHash[chunkId] === "string") {
 | |
| 																	shortContentHashMap[chunkId] = contentHash[
 | |
| 																		chunkId
 | |
| 																	].substr(0, length);
 | |
| 																}
 | |
| 															}
 | |
| 															return `" + ${JSON.stringify(
 | |
| 																shortContentHashMap
 | |
| 															)}[chunkId] + "`;
 | |
| 														}
 | |
| 													},
 | |
| 													name: `" + (${JSON.stringify(
 | |
| 														chunkMaps.name
 | |
| 													)}[chunkId]||chunkId) + "`
 | |
| 												},
 | |
| 												contentHashType: "javascript"
 | |
| 											}
 | |
| 										) +
 | |
| 										";",
 | |
| 									"require('fs').readFile(filename, 'utf-8',  function(err, content) {",
 | |
| 									Template.indent(
 | |
| 										[
 | |
| 											"if(err) return reject(err);",
 | |
| 											"var chunk = {};",
 | |
| 											"require('vm').runInThisContext('(function(exports, require, __dirname, __filename) {' + content + '\\n})', filename)" +
 | |
| 												"(chunk, require, require('path').dirname(filename), filename);"
 | |
| 										]
 | |
| 											.concat(insertMoreModules)
 | |
| 											.concat([
 | |
| 												"var callbacks = [];",
 | |
| 												"for(var i = 0; i < chunkIds.length; i++) {",
 | |
| 												Template.indent([
 | |
| 													"if(installedChunks[chunkIds[i]])",
 | |
| 													Template.indent([
 | |
| 														"callbacks = callbacks.concat(installedChunks[chunkIds[i]][0]);"
 | |
| 													]),
 | |
| 													"installedChunks[chunkIds[i]] = 0;"
 | |
| 												]),
 | |
| 												"}",
 | |
| 												"for(i = 0; i < callbacks.length; i++)",
 | |
| 												Template.indent("callbacks[i]();")
 | |
| 											])
 | |
| 									),
 | |
| 									"});"
 | |
| 								]),
 | |
| 								"});",
 | |
| 								"promises.push(installedChunkData[2] = promise);"
 | |
| 							]),
 | |
| 							"}"
 | |
| 						]),
 | |
| 						"}"
 | |
| 					]);
 | |
| 				} else {
 | |
| 					const request = mainTemplate.getAssetPath(
 | |
| 						JSON.stringify(`./${chunkFilename}`),
 | |
| 						{
 | |
| 							hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
 | |
| 							hashWithLength: length =>
 | |
| 								`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
 | |
| 							chunk: {
 | |
| 								id: '" + chunkId + "',
 | |
| 								hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
 | |
| 								hashWithLength: length => {
 | |
| 									const shortChunkHashMap = {};
 | |
| 									for (const chunkId of Object.keys(chunkMaps.hash)) {
 | |
| 										if (typeof chunkMaps.hash[chunkId] === "string") {
 | |
| 											shortChunkHashMap[chunkId] = chunkMaps.hash[
 | |
| 												chunkId
 | |
| 											].substr(0, length);
 | |
| 										}
 | |
| 									}
 | |
| 									return `" + ${JSON.stringify(
 | |
| 										shortChunkHashMap
 | |
| 									)}[chunkId] + "`;
 | |
| 								},
 | |
| 								contentHash: {
 | |
| 									javascript: `" + ${JSON.stringify(
 | |
| 										chunkMaps.contentHash.javascript
 | |
| 									)}[chunkId] + "`
 | |
| 								},
 | |
| 								contentHashWithLength: {
 | |
| 									javascript: length => {
 | |
| 										const shortContentHashMap = {};
 | |
| 										const contentHash = chunkMaps.contentHash.javascript;
 | |
| 										for (const chunkId of Object.keys(contentHash)) {
 | |
| 											if (typeof contentHash[chunkId] === "string") {
 | |
| 												shortContentHashMap[chunkId] = contentHash[
 | |
| 													chunkId
 | |
| 												].substr(0, length);
 | |
| 											}
 | |
| 										}
 | |
| 										return `" + ${JSON.stringify(
 | |
| 											shortContentHashMap
 | |
| 										)}[chunkId] + "`;
 | |
| 									}
 | |
| 								},
 | |
| 								name: `" + (${JSON.stringify(
 | |
| 									chunkMaps.name
 | |
| 								)}[chunkId]||chunkId) + "`
 | |
| 							},
 | |
| 							contentHashType: "javascript"
 | |
| 						}
 | |
| 					);
 | |
| 					return Template.asString([
 | |
| 						source,
 | |
| 						"",
 | |
| 						"// require() chunk loading for javascript",
 | |
| 						"",
 | |
| 						'// "0" is the signal for "already loaded"',
 | |
| 						"if(installedChunks[chunkId] !== 0) {",
 | |
| 						Template.indent(
 | |
| 							[`var chunk = require(${request});`]
 | |
| 								.concat(insertMoreModules)
 | |
| 								.concat([
 | |
| 									"for(var i = 0; i < chunkIds.length; i++)",
 | |
| 									Template.indent("installedChunks[chunkIds[i]] = 0;")
 | |
| 								])
 | |
| 						),
 | |
| 						"}"
 | |
| 					]);
 | |
| 				}
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.hotBootstrap.tap(
 | |
| 			"NodeMainTemplatePlugin",
 | |
| 			(source, chunk, hash) => {
 | |
| 				const hotUpdateChunkFilename =
 | |
| 					mainTemplate.outputOptions.hotUpdateChunkFilename;
 | |
| 				const hotUpdateMainFilename =
 | |
| 					mainTemplate.outputOptions.hotUpdateMainFilename;
 | |
| 				const chunkMaps = chunk.getChunkMaps();
 | |
| 				const currentHotUpdateChunkFilename = mainTemplate.getAssetPath(
 | |
| 					JSON.stringify(hotUpdateChunkFilename),
 | |
| 					{
 | |
| 						hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
 | |
| 						hashWithLength: length =>
 | |
| 							`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
 | |
| 						chunk: {
 | |
| 							id: '" + chunkId + "',
 | |
| 							hash: `" + ${JSON.stringify(chunkMaps.hash)}[chunkId] + "`,
 | |
| 							hashWithLength: length => {
 | |
| 								const shortChunkHashMap = {};
 | |
| 								for (const chunkId of Object.keys(chunkMaps.hash)) {
 | |
| 									if (typeof chunkMaps.hash[chunkId] === "string") {
 | |
| 										shortChunkHashMap[chunkId] = chunkMaps.hash[chunkId].substr(
 | |
| 											0,
 | |
| 											length
 | |
| 										);
 | |
| 									}
 | |
| 								}
 | |
| 								return `" + ${JSON.stringify(shortChunkHashMap)}[chunkId] + "`;
 | |
| 							},
 | |
| 							name: `" + (${JSON.stringify(
 | |
| 								chunkMaps.name
 | |
| 							)}[chunkId]||chunkId) + "`
 | |
| 						}
 | |
| 					}
 | |
| 				);
 | |
| 				const currentHotUpdateMainFilename = mainTemplate.getAssetPath(
 | |
| 					JSON.stringify(hotUpdateMainFilename),
 | |
| 					{
 | |
| 						hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
 | |
| 						hashWithLength: length =>
 | |
| 							`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`
 | |
| 					}
 | |
| 				);
 | |
| 				return Template.getFunctionContent(
 | |
| 					asyncChunkLoading
 | |
| 						? require("./NodeMainTemplateAsync.runtime")
 | |
| 						: require("./NodeMainTemplate.runtime")
 | |
| 				)
 | |
| 					.replace(/\$require\$/g, mainTemplate.requireFn)
 | |
| 					.replace(/\$hotMainFilename\$/g, currentHotUpdateMainFilename)
 | |
| 					.replace(/\$hotChunkFilename\$/g, currentHotUpdateChunkFilename);
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.hash.tap("NodeMainTemplatePlugin", hash => {
 | |
| 			hash.update("node");
 | |
| 			hash.update("3");
 | |
| 			hash.update(mainTemplate.outputOptions.filename + "");
 | |
| 			hash.update(mainTemplate.outputOptions.chunkFilename + "");
 | |
| 		});
 | |
| 	}
 | |
| };
 |