webpack/lib/RuntimeTemplate.js

546 lines
16 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Template = require("./Template");
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./ChunkGraph")} ChunkGraph */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @typedef {import("./RequestShortener")} RequestShortener */
module.exports = class RuntimeTemplate {
/**
* @param {TODO} outputOptions the compilation output options
* @param {RequestShortener} requestShortener the request shortener
*/
constructor(outputOptions, requestShortener) {
this.outputOptions = outputOptions || {};
/** @type {RequestShortener} */
this.requestShortener = requestShortener;
}
/**
* Add a comment
* @param {object} options Information content of the comment
* @param {string=} options.request request string used originally
* @param {string=} options.chunkName name of the chunk referenced
* @param {string=} options.chunkReason reason information of the chunk
* @param {string=} options.message additional message
* @param {string=} options.exportName name of the export
* @returns {string} comment
*/
comment({ request, chunkName, chunkReason, message, exportName }) {
let content;
if (this.outputOptions.pathinfo) {
content = [message, request, chunkName, chunkReason]
.filter(Boolean)
.map(item => this.requestShortener.shorten(item))
.join(" | ");
} else {
content = [message, chunkName, chunkReason]
.filter(Boolean)
.map(item => this.requestShortener.shorten(item))
.join(" | ");
}
if (!content) return "";
if (this.outputOptions.pathinfo) {
return Template.toComment(content) + " ";
} else {
return Template.toNormalComment(content) + " ";
}
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error block
*/
throwMissingModuleErrorBlock({ request }) {
const err = `Cannot find module '${request}'`;
return `var e = new Error(${JSON.stringify(
err
)}); e.code = 'MODULE_NOT_FOUND'; throw e;`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error function
*/
throwMissingModuleErrorFunction({ request }) {
return `function webpackMissingModule() { ${this.throwMissingModuleErrorBlock(
{ request }
)} }`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error IIFE
*/
missingModule({ request }) {
return `!(${this.throwMissingModuleErrorFunction({ request })}())`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error statement
*/
missingModuleStatement({ request }) {
return `${this.missingModule({ request })};\n`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error code
*/
missingModulePromise({ request }) {
return `Promise.resolve().then(${this.throwMissingModuleErrorFunction({
request
})})`;
}
/**
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {string=} options.idExpr expression to use as id expression
* @param {"expression" | "promise" | "statements"} options.type which kind of code should be returned
* @returns {string} the code
*/
weakError({ module, request, idExpr, type }) {
const errorMessage =
module.id === null
? JSON.stringify("Module is not available (weak dependency)")
: idExpr
? `"Module '" + ${idExpr} + "' is not available (weak dependency)"`
: JSON.stringify(
`Module '${module.id}' is not available (weak dependency)`
);
const comment = request ? Template.toNormalComment(request) + " " : "";
const errorStatements =
`var e = new Error(${errorMessage}); ` +
comment +
"e.code = 'MODULE_NOT_FOUND'; throw e;";
switch (type) {
case "statements":
return errorStatements;
case "promise":
return `Promise.resolve().then(function() { ${errorStatements} })`;
case "expression":
return `(function() { ${errorStatements} }())`;
}
}
/**
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
* @returns {string} the expression
*/
moduleId({ module, request, weak }) {
if (!module) {
return this.missingModule({
request
});
}
if (module.id === null) {
if (weak) {
return "null /* weak dependency, without id */";
}
throw new Error(
`RuntimeTemplate.moduleId(): Module ${module.identifier()} has no id. This should not happen.`
);
}
return `${this.comment({ request })}${JSON.stringify(module.id)}`;
}
/**
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
* @returns {string} the expression
*/
moduleRaw({ module, request, weak }) {
if (!module) {
return this.missingModule({
request
});
}
if (module.id === null) {
if (weak) {
// only weak referenced modules don't get an id
// we can always emit an error emitting code here
return this.weakError({
module,
request,
type: "expression"
});
}
throw new Error(
`RuntimeTemplate.moduleId(): Module ${module.identifier()} has no id. This should not happen.`
);
}
return `__webpack_require__(${this.moduleId({ module, request, weak })})`;
}
/**
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
* @returns {string} the expression
*/
moduleExports({ module, request, weak }) {
return this.moduleRaw({
module,
request,
weak
});
}
/**
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {boolean=} options.strict if the current module is in strict esm mode
* @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
* @returns {string} the expression
*/
moduleNamespace({ module, request, strict, weak }) {
if (!module) {
return this.missingModule({
request
});
}
if (module.id === null) {
if (weak) {
// only weak referenced modules don't get an id
// we can always emit an error emitting code here
return this.weakError({
module,
request,
type: "expression"
});
}
throw new Error(
`RuntimeTemplate.moduleNamespace(): Module ${module.identifier()} has no id. This should not happen.`
);
}
const moduleId = this.moduleId({
module,
request,
weak
});
const exportsType = module.buildMeta && module.buildMeta.exportsType;
if (exportsType === "namespace") {
const rawModule = this.moduleRaw({
module,
request,
weak
});
return rawModule;
} else if (exportsType === "named") {
return `__webpack_require__.t(${moduleId}, 3)`;
} else if (strict) {
return `__webpack_require__.t(${moduleId}, 1)`;
} else {
return `__webpack_require__.t(${moduleId}, 7)`;
}
}
/**
* @param {Object} options options object
* @param {ChunkGraph} options.chunkGraph the chunk graph
* @param {AsyncDependenciesBlock=} options.block the current dependencies block
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {string} options.message a message for the comment
* @param {boolean=} options.strict if the current module is in strict esm mode
* @param {boolean=} options.weak if the dependency is weak (will create a nice error message)
* @returns {string} the promise expression
*/
moduleNamespacePromise({
chunkGraph,
block,
module,
request,
message,
strict,
weak
}) {
if (!module) {
return this.missingModulePromise({
request
});
}
if (module.id === null) {
if (weak) {
// only weak referenced modules don't get an id
// we can always emit an error emitting code here
return this.weakError({
module,
request,
type: "promise"
});
}
throw new Error(
`RuntimeTemplate.moduleNamespacePromise(): Module ${module.identifier()} has no id. This should not happen.`
);
}
const promise = this.blockPromise({
chunkGraph,
block,
message
});
let getModuleFunction;
let idExpr = JSON.stringify(module.id);
const comment = this.comment({
request
});
let header = "";
if (weak) {
if (idExpr.length > 8) {
// 'var x="nnnnnn";x,"+x+",x' vs '"nnnnnn",nnnnnn,"nnnnnn"'
header += `var id = ${idExpr}; `;
idExpr = "id";
}
header += `if(!__webpack_require__.m[${idExpr}]) { ${this.weakError({
module,
request,
idExpr,
type: "statements"
})} } `;
}
const moduleId = this.moduleId({
module,
request,
weak
});
const exportsType = module.buildMeta && module.buildMeta.exportsType;
if (exportsType === "namespace") {
if (header) {
const rawModule = this.moduleRaw({
module,
request,
weak
});
getModuleFunction = `function() { ${header}return ${rawModule}; }`;
} else {
getModuleFunction = `__webpack_require__.bind(null, ${comment}${idExpr})`;
}
} else if (exportsType === "named") {
if (header) {
getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 3); }`;
} else {
getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 3)`;
}
} else if (strict) {
if (header) {
getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 1); }`;
} else {
getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 1)`;
}
} else {
if (header) {
getModuleFunction = `function() { ${header}return __webpack_require__.t(${moduleId}, 7); }`;
} else {
getModuleFunction = `__webpack_require__.t.bind(null, ${comment}${idExpr}, 7)`;
}
}
return `${promise || "Promise.resolve()"}.then(${getModuleFunction})`;
}
/**
*
* @param {Object} options options object
* @param {boolean=} options.update whether a new variable should be created or the existing one updated
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment
* @param {string} options.importVar name of the import variable
* @param {Module} options.originModule module in which the statement is emitted
* @param {boolean=} options.weak true, if this is a weak dependency
* @returns {string} the import statement
*/
importStatement({ update, module, request, importVar, originModule, weak }) {
if (!module) {
return this.missingModuleStatement({
request
});
}
if (module.id === null) {
if (weak) {
// only weak referenced modules don't get an id
// we can always emit an error emitting code here
return this.weakError({
module,
request,
type: "statements"
});
}
throw new Error(
`RuntimeTemplate.importStatment(): Module ${module.identifier()} has no id. This should not happen.`
);
}
const moduleId = this.moduleId({
module,
request,
weak
});
const optDeclaration = update ? "" : "var ";
const exportsType = module.buildMeta && module.buildMeta.exportsType;
let content = `/* harmony import */ ${optDeclaration}${importVar} = __webpack_require__(${moduleId});\n`;
if (!exportsType && !originModule.buildMeta.strictHarmonyModule) {
content += `/* harmony import */ ${optDeclaration}${importVar}_default = /*#__PURE__*/__webpack_require__.n(${importVar});\n`;
}
if (exportsType === "named") {
if (Array.isArray(module.buildMeta.providedExports)) {
content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/__webpack_require__.t(${moduleId}, 1);\n`;
} else {
content += `${optDeclaration}${importVar}_namespace = /*#__PURE__*/__webpack_require__.t(${moduleId});\n`;
}
}
return content;
}
/**
* @param {Object} options options
* @param {ModuleGraph} options.moduleGraph the module graph
* @param {Module} options.module the module
* @param {string} options.request the request
* @param {string} options.exportName the export name
* @param {Module} options.originModule the origin module
* @param {boolean} options.asiSafe true, if location is safe for ASI, a bracket can be emitted
* @param {boolean} options.isCall true, if expression will be called
* @param {boolean} options.callContext when false, call context will not be preserved
* @param {string} options.importVar the identifier name of the import variable
* @returns {string} expression
*/
exportFromImport({
moduleGraph,
module,
request,
exportName,
originModule,
asiSafe,
isCall,
callContext,
importVar
}) {
if (!module) {
return this.missingModule({
request
});
}
const exportsType = module.buildMeta && module.buildMeta.exportsType;
if (!exportsType) {
if (exportName === "default") {
if (!originModule.buildMeta.strictHarmonyModule) {
if (isCall) {
return `${importVar}_default()`;
} else if (asiSafe) {
return `(${importVar}_default())`;
} else {
return `${importVar}_default.a`;
}
} else {
return importVar;
}
} else if (originModule.buildMeta.strictHarmonyModule) {
if (exportName) {
return "/* non-default import from non-esm module */undefined";
} else {
return `/*#__PURE__*/__webpack_require__.t(${importVar})`;
}
}
}
if (exportsType === "named") {
if (exportName === "default") {
return importVar;
} else if (!exportName) {
return `${importVar}_namespace`;
}
}
if (exportName) {
const used = module.getUsedName(moduleGraph, exportName);
if (!used) {
const comment = Template.toNormalComment(`unused export ${exportName}`);
return `${comment} undefined`;
}
const comment =
used !== exportName ? Template.toNormalComment(exportName) + " " : "";
const access = `${importVar}[${comment}${JSON.stringify(used)}]`;
if (isCall) {
if (callContext === false && asiSafe) {
return `(0,${access})`;
} else if (callContext === false) {
return `Object(${access})`;
}
}
return access;
} else {
return importVar;
}
}
blockPromise({ block, message, chunkGraph }) {
if (!block) {
const comment = this.comment({
message
});
return `Promise.resolve(${comment.trim()})`;
}
const chunkGroup = chunkGraph.getBlockChunkGroup(block);
if (!chunkGroup || chunkGroup.chunks.length === 0) {
const comment = this.comment({
message
});
return `Promise.resolve(${comment.trim()})`;
}
const chunks = chunkGroup.chunks.filter(
chunk => !chunk.hasRuntime() && chunk.id !== null
);
const comment = this.comment({
message,
chunkName: block.chunkName,
chunkReason: block.chunkReason
});
if (chunks.length === 1) {
const chunkId = JSON.stringify(chunks[0].id);
return `__webpack_require__.e(${comment}${chunkId})`;
} else if (chunks.length > 0) {
const requireChunkId = chunk =>
`__webpack_require__.e(${JSON.stringify(chunk.id)})`;
return `Promise.all(${comment.trim()}[${chunks
.map(requireChunkId)
.join(", ")}])`;
} else {
return `Promise.resolve(${comment.trim()})`;
}
}
onError() {
return "__webpack_require__.oe";
}
defineEsModuleFlagStatement({ exportsArgument }) {
return `__webpack_require__.r(${exportsArgument});\n`;
}
};