UNPKG

@areslabs/alita-core

Version:

alita-core

193 lines (161 loc) 15.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _tapable = require("tapable"); var _Template = _interopRequireDefault(require("webpack/lib/Template")); var _configure = _interopRequireDefault(require("../../configure")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class MpJsonpMainTemplatePlugin { 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 _tapable.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.default.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.default.indent([`return ${mainTemplate.requireFn}.p + ${getScriptSrcPath(hash, chunk, "chunkId")}`]), "}"); } if (extraCode.length === 0) return source; return _Template.default.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.default.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.default.indent(`script.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`), "}", "script.src = jsonpScriptSrc(chunkId);", crossOriginLoading ? _Template.default.asString(["if (script.src.indexOf(window.location.origin + '/') !== 0) {", _Template.default.indent(`script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), "}"]) : "", "// create error before stack unwound to get useful stacktrace later", "var error = new Error();", "onScriptComplete = function (event) {", _Template.default.indent(["// avoid mem leaks in IE.", "script.onerror = script.onload = null;", "clearTimeout(timeout);", "var chunk = installedChunks[chunkId];", "if(chunk !== 0) {", _Template.default.indent(["if(chunk) {", _Template.default.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.default.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.default.asString(["var link = document.createElement('link');", jsonpScriptType ? `link.type = ${JSON.stringify(jsonpScriptType)};` : "", "link.charset = 'utf-8';", `if (${mainTemplate.requireFn}.nc) {`, _Template.default.indent(`link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);`), "}", 'link.rel = "preload";', 'link.as = "script";', "link.href = jsonpScriptSrc(chunkId);", crossOriginLoading ? _Template.default.asString(["if (link.href.indexOf(window.location.origin + '/') !== 0) {", _Template.default.indent(`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`), "}"]) : ""]); }); mainTemplate.hooks.linkPrefetch.tap("JsonpMainTemplatePlugin", (_, chunk, hash) => { const crossOriginLoading = mainTemplate.outputOptions.crossOriginLoading; return _Template.default.asString(["var link = document.createElement('link');", crossOriginLoading ? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` : "", `if (${mainTemplate.requireFn}.nc) {`, _Template.default.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.default.asString([source, "", "// JSONP chunk loading for javascript", "", "var installedChunkData = installedChunks[chunkId];", 'if(installedChunkData !== 0) { // 0 means "already installed".', _Template.default.indent(["", '// a Promise means "currently loading".', "if(installedChunkData) {", _Template.default.indent(["promises.push(installedChunkData[2]);"]), "} else {", _Template.default.indent(["// setup Promise in chunk cache", "var promise = new Promise(function(resolve, reject) {", _Template.default.indent(["installedChunkData = installedChunks[chunkId] = [resolve, reject];"]), "});", "promises.push(installedChunkData[2] = promise);", "", "// 在分包加载的时候, miniprogram will load chunk "]), "}"]), "}"]); }); 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.default.asString([source, "", "// chunk preloadng for javascript", "", `var chunkPreloadMap = ${JSON.stringify(chunkMap, null, "\t")};`, "", "var chunkPreloadData = chunkPreloadMap[chunkId];", "if(chunkPreloadData) {", _Template.default.indent(["chunkPreloadData.forEach(function(chunkId) {", _Template.default.indent(["if(installedChunks[chunkId] === undefined) {", _Template.default.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.default.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.default.asString([source, "", "// install a JSONP callback for chunk loading", "function webpackJsonpCallback(data) {", _Template.default.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.default.indent(["chunkId = chunkIds[i];", "if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {", _Template.default.indent("resolves.push(installedChunks[chunkId][0]);"), "}", "installedChunks[chunkId] = 0;"]), "}", "for(moduleId in moreModules) {", _Template.default.indent(["if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {", _Template.default.indent(mainTemplate.renderAddModule(hash, chunk, "moduleId", "moreModules[moduleId]")), "}"]), "}", "if(parentJsonpFunction) parentJsonpFunction(data);", withPrefetch ? withDefer ? "deferredPrefetch.push.apply(deferredPrefetch, prefetchChunks);" : _Template.default.asString(["// chunk prefetching for javascript", "prefetchChunks.forEach(function(chunkId) {", _Template.default.indent(["if(installedChunks[chunkId] === undefined) {", _Template.default.indent(["installedChunks[chunkId] = null;", mainTemplate.hooks.linkPrefetch.call("", chunk, hash), "document.head.appendChild(link);"]), "}"]), "});"]) : "", "while(resolves.length) {", _Template.default.indent("resolves.shift()();"), "}", withDefer ? _Template.default.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.default.asString(["function checkDeferredModules() {", _Template.default.indent(["var result;", "for(var i = 0; i < deferredModules.length; i++) {", _Template.default.indent(["var deferredModule = deferredModules[i];", "var fulfilled = true;", "for(var j = 1; j < deferredModule.length; j++) {", _Template.default.indent(["var depId = deferredModule[j];", "if(installedChunks[depId] !== 0) fulfilled = false;"]), "}", "if(fulfilled) {", _Template.default.indent(["deferredModules.splice(i--, 1);", "result = " + mainTemplate.requireFn + "(" + mainTemplate.requireFn + ".s = deferredModule[0]);"]), "}"]), "}", withPrefetch ? _Template.default.asString(["if(deferredModules.length === 0) {", _Template.default.indent(["// chunk prefetching for javascript", "deferredPrefetch.forEach(function(chunkId) {", _Template.default.indent(["if(installedChunks[chunkId] === undefined) {", _Template.default.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 = _configure.default.mpGlobalObject; return _Template.default.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.default.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( // @ts-ignore Array.from(chunk.groupsIterable)[0] // @ts-ignore .chunks.filter(c => c !== chunk).map(c => c.id))); return _Template.default.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.default.asString(["// run deferred modules from other chunks", "checkDeferredModules();"]); } } return source; }); mainTemplate.hooks.hash.tap("JsonpMainTemplatePlugin", hash => { hash.update("jsonp"); hash.update("6"); }); } } exports.default = MpJsonpMainTemplatePlugin;