| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const { SyncWaterfallHook } = require("tapable");const Template = require("../Template");class JsonpMainTemplatePlugin {	apply(mainTemplate) {		const needChunkOnDemandLoadingCode = chunk => {			for (const chunkGroup of chunk.groupsIterable) {				if (chunkGroup.getNumberOfChildren() > 0) return true;			}			return false;		};		const needChunkLoadingCode = chunk => {			for (const chunkGroup of chunk.groupsIterable) {				if (chunkGroup.chunks.length > 1) return true;				if (chunkGroup.getNumberOfChildren() > 0) return true;			}			return false;		};		const needEntryDeferringCode = chunk => {			for (const chunkGroup of chunk.groupsIterable) {				if (chunkGroup.chunks.length > 1) return true;			}			return false;		};		const needPrefetchingCode = chunk => {			const allPrefetchChunks = chunk.getChildIdsByOrdersMap(true).prefetch;			return allPrefetchChunks && Object.keys(allPrefetchChunks).length;		};		// TODO webpack 5, no adding to .hooks, use WeakMap and static methods		["jsonpScript", "linkPreload", "linkPrefetch"].forEach(hook => {			if (!mainTemplate.hooks[hook]) {				mainTemplate.hooks[hook] = new SyncWaterfallHook([					"source",					"chunk",					"hash"				]);			}		});		const getScriptSrcPath = (hash, chunk, chunkIdExpression) => {			const chunkFilename = mainTemplate.outputOptions.chunkFilename;			const chunkMaps = chunk.getChunkMaps();			return mainTemplate.getAssetPath(JSON.stringify(chunkFilename), {				hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,				hashWithLength: length =>					`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,				chunk: {					id: `" + ${chunkIdExpression} + "`,					hash: `" + ${JSON.stringify(						chunkMaps.hash					)}[${chunkIdExpression}] + "`,					hashWithLength(length) {						const shortChunkHashMap = Object.create(null);						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						)}[${chunkIdExpression}] + "`;					},					name: `" + (${JSON.stringify(						chunkMaps.name					)}[${chunkIdExpression}]||${chunkIdExpression}) + "`,					contentHash: {						javascript: `" + ${JSON.stringify(							chunkMaps.contentHash.javascript						)}[${chunkIdExpression}] + "`					},					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							)}[${chunkIdExpression}] + "`;						}					}				},				contentHashType: "javascript"			});		};		mainTemplate.hooks.localVars.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				const extraCode = [];				if (needChunkLoadingCode(chunk)) {					extraCode.push(						"",						"// object to store loaded and loading chunks",						"// undefined = chunk not loaded, null = chunk preloaded/prefetched",						"// Promise = chunk loading, 0 = chunk loaded",						"var installedChunks = {",						Template.indent(							chunk.ids.map(id => `${JSON.stringify(id)}: 0`).join(",\n")						),						"};",						"",						needEntryDeferringCode(chunk)							? needPrefetchingCode(chunk)								? "var deferredModules = [], deferredPrefetch = [];"								: "var deferredModules = [];"							: ""					);				}				if (needChunkOnDemandLoadingCode(chunk)) {					extraCode.push(						"",						"// script path function",						"function jsonpScriptSrc(chunkId) {",						Template.indent([							`return ${mainTemplate.requireFn}.p + ${getScriptSrcPath(								hash,								chunk,								"chunkId"							)}`						]),						"}"					);				}				if (extraCode.length === 0) return source;				return Template.asString([source, ...extraCode]);			}		);		mainTemplate.hooks.jsonpScript.tap(			"JsonpMainTemplatePlugin",			(_, chunk, hash) => {				const crossOriginLoading =					mainTemplate.outputOptions.crossOriginLoading;				const chunkLoadTimeout = mainTemplate.outputOptions.chunkLoadTimeout;				const jsonpScriptType = mainTemplate.outputOptions.jsonpScriptType;				return Template.asString([					"var script = document.createElement('script');",					"var onScriptComplete;",					jsonpScriptType						? `script.type = ${JSON.stringify(jsonpScriptType)};`						: "",					"script.charset = 'utf-8';",					`script.timeout = ${chunkLoadTimeout / 1000};`,					`if (${mainTemplate.requireFn}.nc) {`,					Template.indent(						`script.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`					),					"}",					"script.src = jsonpScriptSrc(chunkId);",					crossOriginLoading						? Template.asString([								"if (script.src.indexOf(window.location.origin + '/') !== 0) {",								Template.indent(									`script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`								),								"}"						  ])						: "",					"// create error before stack unwound to get useful stacktrace later",					"var error = new Error();",					"onScriptComplete = function (event) {",					Template.indent([						"// avoid mem leaks in IE.",						"script.onerror = script.onload = null;",						"clearTimeout(timeout);",						"var chunk = installedChunks[chunkId];",						"if(chunk !== 0) {",						Template.indent([							"if(chunk) {",							Template.indent([								"var errorType = event && (event.type === 'load' ? 'missing' : event.type);",								"var realSrc = event && event.target && event.target.src;",								"error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",								"error.name = 'ChunkLoadError';",								"error.type = errorType;",								"error.request = realSrc;",								"chunk[1](error);"							]),							"}",							"installedChunks[chunkId] = undefined;"						]),						"}"					]),					"};",					"var timeout = setTimeout(function(){",					Template.indent([						"onScriptComplete({ type: 'timeout', target: script });"					]),					`}, ${chunkLoadTimeout});`,					"script.onerror = script.onload = onScriptComplete;"				]);			}		);		mainTemplate.hooks.linkPreload.tap(			"JsonpMainTemplatePlugin",			(_, chunk, hash) => {				const crossOriginLoading =					mainTemplate.outputOptions.crossOriginLoading;				const jsonpScriptType = mainTemplate.outputOptions.jsonpScriptType;				return Template.asString([					"var link = document.createElement('link');",					jsonpScriptType						? `link.type = ${JSON.stringify(jsonpScriptType)};`						: "",					"link.charset = 'utf-8';",					`if (${mainTemplate.requireFn}.nc) {`,					Template.indent(						`link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`					),					"}",					'link.rel = "preload";',					'link.as = "script";',					"link.href = jsonpScriptSrc(chunkId);",					crossOriginLoading						? Template.asString([								"if (link.href.indexOf(window.location.origin + '/') !== 0) {",								Template.indent(									`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`								),								"}"						  ])						: ""				]);			}		);		mainTemplate.hooks.linkPrefetch.tap(			"JsonpMainTemplatePlugin",			(_, chunk, hash) => {				const crossOriginLoading =					mainTemplate.outputOptions.crossOriginLoading;				return Template.asString([					"var link = document.createElement('link');",					crossOriginLoading						? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`						: "",					`if (${mainTemplate.requireFn}.nc) {`,					Template.indent(						`link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`					),					"}",					'link.rel = "prefetch";',					'link.as = "script";',					"link.href = jsonpScriptSrc(chunkId);"				]);			}		);		mainTemplate.hooks.requireEnsure.tap(			"JsonpMainTemplatePlugin load",			(source, chunk, hash) => {				return Template.asString([					source,					"",					"// JSONP chunk loading for javascript",					"",					"var installedChunkData = installedChunks[chunkId];",					'if(installedChunkData !== 0) { // 0 means "already installed".',					Template.indent([						"",						'// a Promise means "currently loading".',						"if(installedChunkData) {",						Template.indent(["promises.push(installedChunkData[2]);"]),						"} else {",						Template.indent([							"// setup Promise in chunk cache",							"var promise = new Promise(function(resolve, reject) {",							Template.indent([								"installedChunkData = installedChunks[chunkId] = [resolve, reject];"							]),							"});",							"promises.push(installedChunkData[2] = promise);",							"",							"// start chunk loading",							mainTemplate.hooks.jsonpScript.call("", chunk, hash),							"document.head.appendChild(script);"						]),						"}"					]),					"}"				]);			}		);		mainTemplate.hooks.requireEnsure.tap(			{				name: "JsonpMainTemplatePlugin preload",				stage: 10			},			(source, chunk, hash) => {				const chunkMap = chunk.getChildIdsByOrdersMap().preload;				if (!chunkMap || Object.keys(chunkMap).length === 0) return source;				return Template.asString([					source,					"",					"// chunk preloadng for javascript",					"",					`var chunkPreloadMap = ${JSON.stringify(chunkMap, null, "\t")};`,					"",					"var chunkPreloadData = chunkPreloadMap[chunkId];",					"if(chunkPreloadData) {",					Template.indent([						"chunkPreloadData.forEach(function(chunkId) {",						Template.indent([							"if(installedChunks[chunkId] === undefined) {",							Template.indent([								"installedChunks[chunkId] = null;",								mainTemplate.hooks.linkPreload.call("", chunk, hash),								"document.head.appendChild(link);"							]),							"}"						]),						"});"					]),					"}"				]);			}		);		mainTemplate.hooks.requireExtensions.tap(			"JsonpMainTemplatePlugin",			(source, chunk) => {				if (!needChunkOnDemandLoadingCode(chunk)) return source;				return Template.asString([					source,					"",					"// on error function for async loading",					`${mainTemplate.requireFn}.oe = function(err) { console.error(err); throw err; };`				]);			}		);		mainTemplate.hooks.bootstrap.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				if (needChunkLoadingCode(chunk)) {					const withDefer = needEntryDeferringCode(chunk);					const withPrefetch = needPrefetchingCode(chunk);					return Template.asString([						source,						"",						"// install a JSONP callback for chunk loading",						"function webpackJsonpCallback(data) {",						Template.indent([							"var chunkIds = data[0];",							"var moreModules = data[1];",							withDefer ? "var executeModules = data[2];" : "",							withPrefetch ? "var prefetchChunks = data[3] || [];" : "",							'// add "moreModules" to the modules object,',							'// then flag all "chunkIds" as loaded and fire callback',							"var moduleId, chunkId, i = 0, resolves = [];",							"for(;i < chunkIds.length; i++) {",							Template.indent([								"chunkId = chunkIds[i];",								"if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {",								Template.indent("resolves.push(installedChunks[chunkId][0]);"),								"}",								"installedChunks[chunkId] = 0;"							]),							"}",							"for(moduleId in moreModules) {",							Template.indent([								"if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {",								Template.indent(									mainTemplate.renderAddModule(										hash,										chunk,										"moduleId",										"moreModules[moduleId]"									)								),								"}"							]),							"}",							"if(parentJsonpFunction) parentJsonpFunction(data);",							withPrefetch								? withDefer									? "deferredPrefetch.push.apply(deferredPrefetch, prefetchChunks);"									: Template.asString([											"// chunk prefetching for javascript",											"prefetchChunks.forEach(function(chunkId) {",											Template.indent([												"if(installedChunks[chunkId] === undefined) {",												Template.indent([													"installedChunks[chunkId] = null;",													mainTemplate.hooks.linkPrefetch.call("", chunk, hash),													"document.head.appendChild(link);"												]),												"}"											]),											"});"									  ])								: "",							"while(resolves.length) {",							Template.indent("resolves.shift()();"),							"}",							withDefer								? Template.asString([										"",										"// add entry modules from loaded chunk to deferred list",										"deferredModules.push.apply(deferredModules, executeModules || []);",										"",										"// run deferred modules when all chunks ready",										"return checkDeferredModules();"								  ])								: ""						]),						"};",						withDefer							? Template.asString([									"function checkDeferredModules() {",									Template.indent([										"var result;",										"for(var i = 0; i < deferredModules.length; i++) {",										Template.indent([											"var deferredModule = deferredModules[i];",											"var fulfilled = true;",											"for(var j = 1; j < deferredModule.length; j++) {",											Template.indent([												"var depId = deferredModule[j];",												"if(installedChunks[depId] !== 0) fulfilled = false;"											]),											"}",											"if(fulfilled) {",											Template.indent([												"deferredModules.splice(i--, 1);",												"result = " +													mainTemplate.requireFn +													"(" +													mainTemplate.requireFn +													".s = deferredModule[0]);"											]),											"}"										]),										"}",										withPrefetch											? Template.asString([													"if(deferredModules.length === 0) {",													Template.indent([														"// chunk prefetching for javascript",														"deferredPrefetch.forEach(function(chunkId) {",														Template.indent([															"if(installedChunks[chunkId] === undefined) {",															Template.indent([																"installedChunks[chunkId] = null;",																mainTemplate.hooks.linkPrefetch.call(																	"",																	chunk,																	hash																),																"document.head.appendChild(link);"															]),															"}"														]),														"});",														"deferredPrefetch.length = 0;"													]),													"}"											  ])											: "",										"return result;"									]),									"}"							  ])							: ""					]);				}				return source;			}		);		mainTemplate.hooks.beforeStartup.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				if (needChunkLoadingCode(chunk)) {					var jsonpFunction = mainTemplate.outputOptions.jsonpFunction;					var globalObject = mainTemplate.outputOptions.globalObject;					return Template.asString([						`var jsonpArray = ${globalObject}[${JSON.stringify(							jsonpFunction						)}] = ${globalObject}[${JSON.stringify(jsonpFunction)}] || [];`,						"var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);",						"jsonpArray.push = webpackJsonpCallback;",						"jsonpArray = jsonpArray.slice();",						"for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);",						"var parentJsonpFunction = oldJsonpFunction;",						"",						source					]);				}				return source;			}		);		mainTemplate.hooks.afterStartup.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				const prefetchChunks = chunk.getChildIdsByOrders().prefetch;				if (					needChunkLoadingCode(chunk) &&					prefetchChunks &&					prefetchChunks.length				) {					return Template.asString([						source,						`webpackJsonpCallback([[], {}, 0, ${JSON.stringify(							prefetchChunks						)}]);`					]);				}				return source;			}		);		mainTemplate.hooks.startup.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				if (needEntryDeferringCode(chunk)) {					if (chunk.hasEntryModule()) {						const entries = [chunk.entryModule].filter(Boolean).map(m =>							[m.id].concat(								Array.from(chunk.groupsIterable)[0]									.chunks.filter(c => c !== chunk)									.map(c => c.id)							)						);						return Template.asString([							"// add entry module to deferred list",							`deferredModules.push(${entries								.map(e => JSON.stringify(e))								.join(", ")});`,							"// run deferred modules when ready",							"return checkDeferredModules();"						]);					} else {						return Template.asString([							"// run deferred modules from other chunks",							"checkDeferredModules();"						]);					}				}				return source;			}		);		mainTemplate.hooks.hotBootstrap.tap(			"JsonpMainTemplatePlugin",			(source, chunk, hash) => {				const globalObject = mainTemplate.outputOptions.globalObject;				const hotUpdateChunkFilename =					mainTemplate.outputOptions.hotUpdateChunkFilename;				const hotUpdateMainFilename =					mainTemplate.outputOptions.hotUpdateMainFilename;				const crossOriginLoading =					mainTemplate.outputOptions.crossOriginLoading;				const hotUpdateFunction = mainTemplate.outputOptions.hotUpdateFunction;				const currentHotUpdateChunkFilename = mainTemplate.getAssetPath(					JSON.stringify(hotUpdateChunkFilename),					{						hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,						hashWithLength: length =>							`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,						chunk: {							id: '" + chunkId + "'						}					}				);				const currentHotUpdateMainFilename = mainTemplate.getAssetPath(					JSON.stringify(hotUpdateMainFilename),					{						hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,						hashWithLength: length =>							`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`					}				);				const runtimeSource = Template.getFunctionContent(					require("./JsonpMainTemplate.runtime")				)					.replace(/\/\/\$semicolon/g, ";")					.replace(/\$require\$/g, mainTemplate.requireFn)					.replace(						/\$crossOriginLoading\$/g,						crossOriginLoading ? JSON.stringify(crossOriginLoading) : "null"					)					.replace(/\$hotMainFilename\$/g, currentHotUpdateMainFilename)					.replace(/\$hotChunkFilename\$/g, currentHotUpdateChunkFilename)					.replace(/\$hash\$/g, JSON.stringify(hash));				return `${source}function hotDisposeChunk(chunkId) {	delete installedChunks[chunkId];}var parentHotUpdateCallback = ${globalObject}[${JSON.stringify(					hotUpdateFunction				)}];${globalObject}[${JSON.stringify(hotUpdateFunction)}] = ${runtimeSource}`;			}		);		mainTemplate.hooks.hash.tap("JsonpMainTemplatePlugin", hash => {			hash.update("jsonp");			hash.update("6");		});	}}module.exports = JsonpMainTemplatePlugin;
 |