|
|
- /*
- 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;
|