add support for reexporting in CommonJS

and necessary refactoring + fixes for that
This commit is contained in:
Tobias Koppers 2020-08-17 21:32:47 +02:00
parent 0090eb941c
commit 71cf7f4dc8
25 changed files with 941 additions and 246 deletions

View File

@ -490,6 +490,7 @@ class ContextModule extends Module {
const fakeMap = Object.create(null);
for (const module of sortedModules) {
const exportsType = module.getExportsType(
moduleGraph,
this.options.namespaceObject === "strict"
);
const id = chunkGraph.getModuleId(module);

View File

@ -425,9 +425,6 @@ class ExportsInfo {
return true;
}
}
if (this._sideEffectsOnlyInfo.getUsed(runtime) !== UsageState.Unused) {
return true;
}
for (const exportInfo of this._exports.values()) {
if (exportInfo.getUsed(runtime) !== UsageState.Unused) {
return true;
@ -641,7 +638,10 @@ class ExportsInfo {
getUsedName(name, runtime) {
if (Array.isArray(name)) {
// TODO improve this
if (name.length === 0) return name;
if (name.length === 0) {
if (!this.isUsed(runtime)) return false;
return name;
}
let info = this.getReadOnlyExportInfo(name[0]);
const x = info.getUsedName(name[0], runtime);
if (x === false) return false;

View File

@ -71,7 +71,7 @@ const makeSerializable = require("./util/makeSerializable");
* @property {string=} exportsArgument
* @property {boolean=} strict
* @property {string=} moduleConcatenationBailout
* @property {("default" | "namespace" | "flagged")=} exportsType
* @property {("default" | "namespace" | "flagged" | "dynamic")=} exportsType
* @property {(false | "redirect" | "redirect-warn")=} defaultObject
* @property {boolean=} strictHarmonyModule
* @property {boolean=} async
@ -263,6 +263,10 @@ class Module extends DependenciesBlock {
).getUsedExports(this, undefined);
}
/**
* @deprecated
* @returns {(string | OptimizationBailoutFunction)[]} list
*/
get optimizationBailout() {
return ModuleGraph.getModuleGraphForModule(
this,
@ -372,6 +376,7 @@ class Module extends DependenciesBlock {
}
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {boolean} strict the importing module is strict
* @returns {"namespace" | "default-only" | "default-with-named" | "dynamic"} export type
* "namespace": Exports is already a namespace object. namespace = exports.
@ -379,7 +384,7 @@ class Module extends DependenciesBlock {
* "default-only": Provide a namespace object with only default export. namespace = { default: exports }
* "default-with-named": Provide a namespace object with named and default export. namespace = { ...exports, default: exports }
*/
getExportsType(strict) {
getExportsType(moduleGraph, strict) {
switch (this.buildMeta && this.buildMeta.exportsType) {
case "flagged":
return strict ? "default-only" : "namespace";
@ -393,6 +398,44 @@ class Module extends DependenciesBlock {
default:
return "default-only";
}
case "dynamic": {
if (strict) return "default-only";
// Try to figure out value of __esModule by following reexports
const handleDefault = () => {
switch (this.buildMeta.defaultObject) {
case "redirect":
case "redirect-warn":
return "default-with-named";
default:
return "default-only";
}
};
const exportInfo = moduleGraph.getExportInfo(this, "__esModule");
if (exportInfo.provided === false) {
return handleDefault();
}
const target = exportInfo.getTarget(moduleGraph);
if (
!target ||
!target.export ||
target.export.length !== 1 ||
target.export[0] !== "__esModule"
) {
return "dynamic";
}
switch (
target.module.buildMeta &&
target.module.buildMeta.exportsType
) {
case "flagged":
case "namespace":
return "namespace";
case "default":
return handleDefault();
default:
return "dynamic";
}
}
default:
return strict ? "default-only" : "dynamic";
}

View File

@ -26,7 +26,7 @@ exports.exports = "__webpack_exports__";
exports.thisAsExports = "top-level-this-exports";
/**
* top-level this need to be the exports object
* runtime need to return the exports of the last entry module
*/
exports.returnExportsFromRuntime = "return-exports-from-runtime";

View File

@ -357,7 +357,7 @@ class RuntimeTemplate {
request,
weak
});
const exportsType = module.getExportsType(strict);
const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict);
switch (exportsType) {
case "namespace":
return this.moduleRaw({
@ -461,7 +461,7 @@ class RuntimeTemplate {
request,
weak
});
const exportsType = module.getExportsType(strict);
const exportsType = module.getExportsType(chunkGraph.moduleGraph, strict);
let fakeType = 0;
switch (exportsType) {
case "namespace":
@ -590,6 +590,7 @@ class RuntimeTemplate {
const optDeclaration = update ? "" : "var ";
const exportsType = module.getExportsType(
chunkGraph.moduleGraph,
originModule.buildMeta.strictHarmonyModule
);
runtimeRequirements.add(RuntimeGlobals.require);
@ -646,6 +647,7 @@ class RuntimeTemplate {
exportName = exportName ? [exportName] : [];
}
const exportsType = module.getExportsType(
moduleGraph,
originModule.buildMeta.strictHarmonyModule
);

View File

@ -680,7 +680,13 @@ const applyOptimizationDefaults = (
apply: compiler => {
// Lazy load the Terser plugin
const TerserPlugin = require("terser-webpack-plugin");
new TerserPlugin().apply(compiler);
new TerserPlugin({
terserOptions: {
compress: {
passes: 2
}
}
}).apply(compiler);
}
}
]);

View File

@ -0,0 +1,49 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
exports.handleDependencyBase = (depBase, module, runtimeRequirements) => {
let base = undefined;
let type;
switch (depBase) {
case "exports":
runtimeRequirements.add(RuntimeGlobals.exports);
base = module.exportsArgument;
type = "expression";
break;
case "module.exports":
runtimeRequirements.add(RuntimeGlobals.module);
base = `${module.moduleArgument}.exports`;
type = "expression";
break;
case "this":
runtimeRequirements.add(RuntimeGlobals.thisAsExports);
base = "this";
type = "expression";
break;
case "Object.defineProperty(exports)":
runtimeRequirements.add(RuntimeGlobals.exports);
base = module.exportsArgument;
type = "Object.defineProperty";
break;
case "Object.defineProperty(module.exports)":
runtimeRequirements.add(RuntimeGlobals.module);
base = `${module.moduleArgument}.exports`;
type = "Object.defineProperty";
break;
case "Object.defineProperty(this)":
runtimeRequirements.add(RuntimeGlobals.thisAsExports);
base = "this";
type = "Object.defineProperty";
break;
default:
throw new Error(`Unsupported base ${depBase}`);
}
return [type, base];
};

View File

@ -0,0 +1,356 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Dependency = require("../Dependency");
const { UsageState } = require("../ExportsInfo");
const Template = require("../Template");
const { equals } = require("../util/ArrayHelpers");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const { handleDependencyBase } = require("./CommonJsDependencyHelpers");
const ModuleDependency = require("./ModuleDependency");
const processExportInfo = require("./processExportInfo");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
const idsSymbol = Symbol("CommonJsExportRequireDependency.ids");
const EMPTY_OBJECT = {};
class CommonJsExportRequireDependency extends ModuleDependency {
constructor(range, valueRange, base, names, request, ids, resultUsed) {
super(request);
this.range = range;
this.valueRange = valueRange;
this.base = base;
this.names = names;
this.ids = ids;
this.resultUsed = resultUsed;
this.asiSafe = false;
}
get type() {
return "cjs export require";
}
/**
* @param {ModuleGraph} moduleGraph the module graph
* @returns {string[]} the imported id
*/
getIds(moduleGraph) {
return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
}
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {string[]} ids the imported ids
* @returns {void}
*/
setIds(moduleGraph, ids) {
moduleGraph.getMeta(this)[idsSymbol] = ids;
}
/**
* Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph
* @param {RuntimeSpec} runtime the runtime for which the module is analysed
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
getReferencedExports(moduleGraph, runtime) {
const ids = this.getIds(moduleGraph);
const getFullResult = () => {
if (ids.length === 0) {
return Dependency.EXPORTS_OBJECT_REFERENCED;
} else {
return [
{
name: ids,
canMangle: false
}
];
}
};
if (this.resultUsed) return getFullResult();
let exportsInfo = moduleGraph.getExportsInfo(
moduleGraph.getParentModule(this)
);
for (const name of this.names) {
const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
const used = exportInfo.getUsed(runtime);
if (used === UsageState.Unused) return Dependency.NO_EXPORTS_REFERENCED;
if (used !== UsageState.OnlyPropertiesUsed) return getFullResult();
exportsInfo = exportInfo.exportsInfo;
if (!exportsInfo) return getFullResult();
}
if (exportsInfo.otherExportsInfo.getUsed(runtime) !== UsageState.Unused) {
return getFullResult();
}
/** @type {string[][]} */
const referencedExports = [];
for (const exportInfo of exportsInfo.orderedExports) {
processExportInfo(
runtime,
referencedExports,
ids.concat(exportInfo.name),
exportInfo,
false
);
}
return referencedExports.map(name => ({
name,
canMangle: false
}));
}
/**
* Returns the exported names
* @param {ModuleGraph} moduleGraph module graph
* @returns {ExportsSpec | undefined} export names
*/
getExports(moduleGraph) {
const ids = this.getIds(moduleGraph);
if (this.names.length === 1) {
const name = this.names[0];
const from = moduleGraph.getModule(this);
return {
exports: [
{
name,
from,
export: ids.length === 0 ? null : ids,
// we can't mangle names that are in an empty object
// because one could access the prototype property
// when export isn't set yet
canMangle: !(name in EMPTY_OBJECT) && false
}
],
dependencies: [from]
};
} else if (this.names.length > 0) {
const name = this.names[0];
return {
exports: [
{
name,
// we can't mangle names that are in an empty object
// because one could access the prototype property
// when export isn't set yet
canMangle: !(name in EMPTY_OBJECT) && false
}
],
dependencies: undefined
};
} else {
const from = moduleGraph.getModule(this);
const reexportInfo = this.getStarReexports(moduleGraph, undefined, from);
if (reexportInfo) {
return {
exports: Array.from(reexportInfo.exports, name => {
return {
name,
from,
export: ids.concat(name),
canMangle: !(name in EMPTY_OBJECT) && false
};
}),
// TODO handle deep reexports
dependencies: [from]
};
} else {
return {
exports: true,
from: ids.length === 0 ? from : undefined,
canMangle: false,
dependencies: [from]
};
}
}
}
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {RuntimeSpec} runtime the runtime
* @param {Module} importedModule the imported module (optional)
* @returns {{exports?: Set<string>, checked?: Set<string>}} information
*/
getStarReexports(
moduleGraph,
runtime,
importedModule = moduleGraph.getModule(this)
) {
let importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
const ids = this.getIds(moduleGraph);
if (ids.length > 0)
importedExportsInfo = importedExportsInfo.getNestedExportsInfo(ids);
let exportsInfo = moduleGraph.getExportsInfo(
moduleGraph.getParentModule(this)
);
if (this.names.length > 0)
exportsInfo = exportsInfo.getNestedExportsInfo(this.names);
const noExtraExports =
importedExportsInfo &&
importedExportsInfo.otherExportsInfo.provided === false;
const noExtraImports =
exportsInfo &&
exportsInfo.otherExportsInfo.getUsed(runtime) === UsageState.Unused;
if (!noExtraExports && !noExtraImports) {
return;
}
const isNamespaceImport =
importedModule.getExportsType(moduleGraph, false) === "namespace";
/** @type {Set<string>} */
const exports = new Set();
/** @type {Set<string>} */
const checked = new Set();
if (noExtraImports) {
for (const exportInfo of exportsInfo.orderedExports) {
const name = exportInfo.name;
if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
if (name === "__esModule" && isNamespaceImport) {
exports.add(name);
} else if (importedExportsInfo) {
const importedExportInfo = importedExportsInfo.getReadOnlyExportInfo(
name
);
if (importedExportInfo.provided === false) continue;
exports.add(name);
if (importedExportInfo.provided === true) continue;
checked.add(name);
} else {
exports.add(name);
checked.add(name);
}
}
} else if (noExtraExports) {
for (const importedExportInfo of importedExportsInfo.orderedExports) {
const name = importedExportInfo.name;
if (importedExportInfo.provided === false) continue;
if (exportsInfo) {
const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
}
exports.add(name);
if (importedExportInfo.provided === true) continue;
checked.add(name);
}
if (isNamespaceImport) {
exports.add("__esModule");
checked.delete("__esModule");
}
}
return { exports, checked };
}
serialize(context) {
const { write } = context;
write(this.range);
write(this.valueRange);
write(this.base);
write(this.names);
write(this.ids);
write(this.resultUsed);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this.range = read();
this.valueRange = read();
this.base = read();
this.names = read();
this.ids = read();
this.resultUsed = read();
super.deserialize(context);
}
}
makeSerializable(
CommonJsExportRequireDependency,
"webpack/lib/dependencies/CommonJsExportRequireDependency"
);
CommonJsExportRequireDependency.Template = class CommonJsExportRequireDependencyTemplate extends ModuleDependency.Template {
/**
* @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified
* @param {DependencyTemplateContext} templateContext the context object
* @returns {void}
*/
apply(
dependency,
source,
{
module,
runtimeTemplate,
chunkGraph,
moduleGraph,
runtimeRequirements,
runtime
}
) {
const dep = /** @type {CommonJsExportRequireDependency} */ (dependency);
const used = moduleGraph
.getExportsInfo(module)
.getUsedName(dep.names, runtime);
const [type, base] = handleDependencyBase(
dep.base,
module,
runtimeRequirements
);
const importedModule = moduleGraph.getModule(dep);
let requireExpr = runtimeTemplate.moduleExports({
module: importedModule,
chunkGraph,
request: dep.request,
weak: dep.weak,
runtimeRequirements
});
const ids = dep.getIds(moduleGraph);
const usedImported = moduleGraph
.getExportsInfo(importedModule)
.getUsedName(ids, runtime);
if (usedImported) {
const comment = equals(usedImported, ids)
? ""
: Template.toNormalComment(propertyAccess(ids)) + " ";
requireExpr += `${comment}${propertyAccess(usedImported)}`;
}
switch (type) {
case "expression":
source.replace(
dep.range[0],
dep.range[1] - 1,
used
? `${base}${propertyAccess(used)} = ${requireExpr}`
: `/* unused reexport */ ${requireExpr}`
);
return;
case "Object.defineProperty":
throw new Error("TODO");
default:
throw new Error("Unexpected type");
}
}
};
module.exports = CommonJsExportRequireDependency;

View File

@ -6,9 +6,9 @@
"use strict";
const InitFragment = require("../InitFragment");
const RuntimeGlobals = require("../RuntimeGlobals");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const { handleDependencyBase } = require("./CommonJsDependencyHelpers");
const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
@ -94,42 +94,11 @@ CommonJsExportsDependency.Template = class CommonJsExportsDependencyTemplate ext
.getExportsInfo(module)
.getUsedName(dep.names, runtime);
let base = undefined;
let type;
switch (dep.base) {
case "exports":
runtimeRequirements.add(RuntimeGlobals.exports);
base = module.exportsArgument;
type = "expression";
break;
case "module.exports":
runtimeRequirements.add(RuntimeGlobals.module);
base = `${module.moduleArgument}.exports`;
type = "expression";
break;
case "this":
runtimeRequirements.add(RuntimeGlobals.thisAsExports);
base = "this";
type = "expression";
break;
case "Object.defineProperty(exports)":
runtimeRequirements.add(RuntimeGlobals.exports);
base = module.exportsArgument;
type = "Object.defineProperty";
break;
case "Object.defineProperty(module.exports)":
runtimeRequirements.add(RuntimeGlobals.module);
base = `${module.moduleArgument}.exports`;
type = "Object.defineProperty";
break;
case "Object.defineProperty(this)":
runtimeRequirements.add(RuntimeGlobals.thisAsExports);
base = "this";
type = "Object.defineProperty";
break;
default:
throw new Error(`Unsupported base ${dep.base}`);
}
const [type, base] = handleDependencyBase(
dep.base,
module,
runtimeRequirements
);
switch (type) {
case "expression":

View File

@ -6,19 +6,21 @@
"use strict";
const RuntimeGlobals = require("../RuntimeGlobals");
const formatLocation = require("../formatLocation");
const { evaluateToString } = require("../javascript/JavascriptParserHelpers");
const propertyAccess = require("../util/propertyAccess");
const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
const CommonJsExportsDependency = require("./CommonJsExportsDependency");
const CommonJsSelfReferenceDependency = require("./CommonJsSelfReferenceDependency");
const DynamicExports = require("./DynamicExports");
const HarmonyExports = require("./HarmonyExports");
const ModuleDecoratorDependency = require("./ModuleDecoratorDependency");
/** @typedef {import("estree").Expression} ExpressionNode */
/** @typedef {import("../NormalModule")} NormalModule */
/** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
/** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
/** @type {WeakMap<NormalModule, boolean>} */
const moduleExportsState = new WeakMap();
const getValueOfPropertyDescription = expr => {
if (expr.type !== "ObjectExpression") return;
for (const property of expr.properties) {
@ -49,14 +51,43 @@ const isFalsyLiteral = expr => {
return false;
};
class CommonJsExportsParserPlugin {
static bailout(module) {
const value = moduleExportsState.get(module);
moduleExportsState.set(module, false);
if (value === true) {
module.buildMeta.exportsType = undefined;
module.buildMeta.defaultObject = false;
/**
* @param {JavascriptParser} parser the parser
* @param {ExpressionNode} expr expression
* @returns {{ argument: BasicEvaluatedExpression, ids: string[] } | undefined} parsed call
*/
const parseRequireCall = (parser, expr) => {
const ids = [];
while (expr.type === "MemberExpression") {
if (expr.object.type === "Super") return;
if (!expr.property) return;
const prop = expr.property;
if (expr.computed) {
if (prop.type !== "Literal") return;
ids.push(`${prop.value}`);
} else {
if (prop.type !== "Identifier") return;
ids.push(prop.name);
}
expr = expr.object;
}
if (expr.type !== "CallExpression" || expr.arguments.length !== 1) return;
const callee = expr.callee;
if (
callee.type !== "Identifier" ||
parser.getVariableInfo(callee.name) !== "require"
) {
return;
}
const arg = expr.arguments[0];
if (arg.type === "SpreadElement") return;
const argValue = parser.evaluateExpression(arg);
return { argument: argValue, ids: ids.reverse() };
};
class CommonJsExportsParserPlugin {
constructor(moduleGraph) {
this.moduleGraph = moduleGraph;
}
/**
@ -66,18 +97,24 @@ class CommonJsExportsParserPlugin {
const enableStructuredExports = () => {
DynamicExports.enable(parser.state);
};
const checkNamespace = (members, valueExpr) => {
const checkNamespace = (topLevel, members, valueExpr) => {
if (!DynamicExports.isEnabled(parser.state)) return;
if (members.length > 0 && members[0] === "__esModule") {
if (isTruthyLiteral(valueExpr)) {
if (isTruthyLiteral(valueExpr) && topLevel) {
DynamicExports.setFlagged(parser.state);
} else {
DynamicExports.bailout(parser.state);
DynamicExports.setDynamic(parser.state);
}
}
};
const bailout = () => {
const bailout = reason => {
DynamicExports.bailout(parser.state);
if (reason) bailoutHint(reason);
};
const bailoutHint = reason => {
this.moduleGraph
.getOptimizationBailout(parser.state.module)
.push(`CommonJS bailout: ${reason}`);
};
// metadata //
@ -89,60 +126,74 @@ class CommonJsExportsParserPlugin {
.tap("CommonJsPlugin", evaluateToString("object"));
// exporting //
const handleAssignExport = (expr, base, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
// Handle reexporting
const requireCall = parseRequireCall(parser, expr.right);
if (
requireCall &&
requireCall.argument.isString() &&
(members.length === 0 || members[0] !== "__esModule")
) {
enableStructuredExports();
// It's possible to reexport __esModule, so we must convert to a dynamic module
if (members.length === 0) DynamicExports.setDynamic(parser.state);
const dep = new CommonJsExportRequireDependency(
expr.range,
null,
base,
members,
requireCall.argument.string,
requireCall.ids,
!parser.isStatementLevelExpression(expr)
);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.module.addDependency(dep);
return true;
}
if (members.length === 0) return;
enableStructuredExports();
const remainingMembers = members;
checkNamespace(
parser.statementPath.length === 1 &&
parser.isStatementLevelExpression(expr),
remainingMembers,
expr.right
);
const dep = new CommonJsExportsDependency(
expr.left.range,
null,
base,
remainingMembers
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
parser.walkExpression(expr.right);
return true;
};
parser.hooks.assignMemberChain
.for("exports")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
null,
"exports",
members
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
return handleAssignExport(expr, "exports", members);
});
parser.hooks.assignMemberChain
.for("this")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (!parser.scope.topLevelScope) return;
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
null,
"this",
members
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
return handleAssignExport(expr, "this", members);
});
parser.hooks.assignMemberChain
.for("module")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (members[0] !== "exports" || members.length <= 1) return;
enableStructuredExports();
checkNamespace(members, expr.right);
const dep = new CommonJsExportsDependency(
expr.left.range,
null,
"module.exports",
members.slice(1)
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
if (members[0] !== "exports") return;
return handleAssignExport(expr, "module.exports", members.slice(1));
});
parser.hooks.call
.for("Object.defineProperty")
.tap("CommonJsExportsParserPlugin", expression => {
const expr = /** @type {import("estree").CallExpression} */ (expression);
if (!parser.isStatementLevelExpression(expr)) return;
if (expr.arguments.length !== 3) return;
if (expr.arguments[0].type === "SpreadElement") return;
if (expr.arguments[1].type === "SpreadElement") return;
@ -162,7 +213,11 @@ class CommonJsExportsParserPlugin {
if (typeof property !== "string") return;
enableStructuredExports();
const descArg = expr.arguments[2];
checkNamespace([property], getValueOfPropertyDescription(descArg));
checkNamespace(
parser.statementPath.length === 1,
[property],
getValueOfPropertyDescription(descArg)
);
const dep = new CommonJsExportsDependency(
expr.range,
expr.arguments[2].range,
@ -177,44 +232,87 @@ class CommonJsExportsParserPlugin {
});
// Self reference //
const handleAccessExport = (expr, base, members, call = undefined) => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (members.length === 0) {
bailout(`${base} is used directly at ${formatLocation(expr.loc)}`);
}
if (call && members.length === 1) {
bailoutHint(
`${base}${propertyAccess(
members
)}(...) prevents optimization as ${base} is passed as call context as ${formatLocation(
expr.loc
)}`
);
}
const dep = new CommonJsSelfReferenceDependency(
expr.range,
base,
members,
call
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
if (call) {
parser.walkExpressions(call.arguments);
}
return true;
};
parser.hooks.callMemberChain
.for("exports")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
return handleAccessExport(expr.callee, "exports", members, expr);
});
parser.hooks.expressionMemberChain
.for("exports")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
return handleAccessExport(expr, "exports", members);
});
parser.hooks.expression
.for("exports")
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state)) return;
bailout();
const dep = new CommonJsSelfReferenceDependency(
expr.range,
"exports",
[]
return handleAccessExport(expr, "exports", []);
});
parser.hooks.callMemberChain
.for("module")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (members[0] !== "exports") return;
return handleAccessExport(
expr.callee,
"module.exports",
members.slice(1),
expr
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
});
parser.hooks.expressionMemberChain
.for("module")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (members[0] !== "exports") return;
return handleAccessExport(expr, "module.exports", members.slice(1));
});
parser.hooks.expression
.for("module.exports")
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state)) return;
bailout();
const dep = new CommonJsSelfReferenceDependency(
expr.range,
"module.exports",
[]
);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
return handleAccessExport(expr, "module.exports", []);
});
parser.hooks.callMemberChain
.for("this")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (!parser.scope.topLevelScope) return;
return handleAccessExport(expr.callee, "this", members, expr);
});
parser.hooks.expressionMemberChain
.for("this")
.tap("CommonJsExportsParserPlugin", (expr, members) => {
if (!parser.scope.topLevelScope) return;
return handleAccessExport(expr, "this", members);
});
parser.hooks.expression
.for("this")
.tap("CommonJsExportsParserPlugin", expr => {
if (HarmonyExports.isEnabled(parser.state)) return;
if (!parser.scope.topLevelScope) return;
bailout();
const dep = new CommonJsSelfReferenceDependency(expr.range, "this", []);
dep.loc = expr.loc;
parser.state.module.addDependency(dep);
return true;
return handleAccessExport(expr, "this", []);
});
// Bailouts //

View File

@ -5,7 +5,10 @@
"use strict";
const Template = require("../Template");
const { equals } = require("../util/ArrayHelpers");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
@ -40,7 +43,7 @@ class CommonJsFullRequireDependency extends ModuleDependency {
const importedModule = moduleGraph.getModule(this);
if (
!importedModule ||
importedModule.getExportsType(false) !== "namespace"
importedModule.getExportsType(moduleGraph, false) !== "namespace"
) {
return [this.names.slice(0, -1)];
}
@ -96,29 +99,24 @@ CommonJsFullRequireDependency.Template = class CommonJsFullRequireDependencyTemp
const dep = /** @type {CommonJsFullRequireDependency} */ (dependency);
if (!dep.range) return;
const importedModule = moduleGraph.getModule(dep);
const exports = runtimeTemplate.moduleExports({
let requireExpr = runtimeTemplate.moduleExports({
module: importedModule,
chunkGraph,
request: dep.request,
weak: dep.weak,
runtimeRequirements
});
const exportExpr = runtimeTemplate.exportFromImport({
moduleGraph,
module: importedModule,
request: dep.request,
exportName: dep.names,
originModule: module,
asiSafe: dep.asiSafe,
isCall: dep.call,
callContext: undefined,
defaultInterop: false,
importVar: exports,
initFragments,
runtime,
runtimeRequirements
});
source.replace(dep.range[0], dep.range[1] - 1, exportExpr);
const ids = dep.names;
const usedImported = moduleGraph
.getExportsInfo(importedModule)
.getUsedName(ids, runtime);
if (usedImported) {
const comment = equals(usedImported, ids)
? ""
: Template.toNormalComment(propertyAccess(ids)) + " ";
requireExpr += `${comment}${propertyAccess(usedImported)}`;
}
source.replace(dep.range[0], dep.range[1] - 1, requireExpr);
}
};

View File

@ -28,6 +28,7 @@ const {
evaluateToIdentifier,
toConstantDependency
} = require("../javascript/JavascriptParserHelpers");
const CommonJsExportRequireDependency = require("./CommonJsExportRequireDependency");
class CommonJsPlugin {
constructor(options) {
@ -99,6 +100,15 @@ class CommonJsPlugin {
new CommonJsExportsDependency.Template()
);
compilation.dependencyFactories.set(
CommonJsExportRequireDependency,
normalModuleFactory
);
compilation.dependencyTemplates.set(
CommonJsExportRequireDependency,
new CommonJsExportRequireDependency.Template()
);
const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
compilation.dependencyFactories.set(
@ -203,7 +213,9 @@ class CommonJsPlugin {
);
new CommonJsImportsParserPlugin(options).apply(parser);
new CommonJsExportsParserPlugin().apply(parser);
new CommonJsExportsParserPlugin(compilation.moduleGraph).apply(
parser
);
};
normalModuleFactory.hooks.parser

View File

@ -5,8 +5,8 @@
"use strict";
const { UsageState } = require("../ExportsInfo");
const RuntimeGlobals = require("../RuntimeGlobals");
const { equals } = require("../util/ArrayHelpers");
const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const NullDependency = require("./NullDependency");
@ -20,11 +20,12 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
class CommonJsSelfReferenceDependency extends NullDependency {
constructor(range, base, names) {
constructor(range, base, names, call) {
super();
this.range = range;
this.base = base;
this.names = names;
this.call = call;
}
get type() {
@ -49,7 +50,7 @@ class CommonJsSelfReferenceDependency extends NullDependency {
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
getReferencedExports(moduleGraph, runtime) {
return [this.names];
return [this.call ? this.names.slice(0, -1) : this.names];
}
serialize(context) {
@ -57,6 +58,7 @@ class CommonJsSelfReferenceDependency extends NullDependency {
write(this.range);
write(this.base);
write(this.names);
write(this.call);
super.serialize(context);
}
@ -65,6 +67,7 @@ class CommonJsSelfReferenceDependency extends NullDependency {
this.range = read();
this.base = read();
this.names = read();
this.call = read();
super.deserialize(context);
}
}
@ -90,13 +93,6 @@ CommonJsSelfReferenceDependency.Template = class CommonJsSelfReferenceDependency
let used;
if (dep.names.length === 0) {
used = dep.names;
} else if (module.buildMeta && module.buildMeta.exportsType === "default") {
const defaultInfo = moduleGraph.getExportInfo(module, "default");
if (defaultInfo.getUsed(runtime) === UsageState.Used) {
used = dep.names;
} else {
used = defaultInfo.exportsInfo.getUsedName(dep.names, runtime);
}
} else {
used = moduleGraph.getExportsInfo(module).getUsedName(dep.names, runtime);
}
@ -124,7 +120,7 @@ CommonJsSelfReferenceDependency.Template = class CommonJsSelfReferenceDependency
throw new Error(`Unsupported base ${dep.base}`);
}
if (base === dep.base && used.join() === dep.names.join()) {
if (base === dep.base && equals(used, dep.names)) {
// Nothing has to be changed
// We don't use a replacement for compat reasons
// for plugins that update `module._source` which they
@ -135,7 +131,7 @@ CommonJsSelfReferenceDependency.Template = class CommonJsSelfReferenceDependency
source.replace(
dep.range[0],
dep.range[1] - 1,
`/* self exports access */ ${base}${propertyAccess(used)}`
`${base}${propertyAccess(used)}`
);
}
};

View File

@ -44,7 +44,19 @@ exports.enable = parserState => {
exports.setFlagged = parserState => {
const value = parserStateExportsState.get(parserState);
if (value !== true) return;
parserState.module.buildMeta.exportsType = "flagged";
const buildMeta = parserState.module.buildMeta;
if (buildMeta.exportsType === "dynamic") return;
buildMeta.exportsType = "flagged";
};
/**
* @param {ParserState} parserState parser state
* @returns {void}
*/
exports.setDynamic = parserState => {
const value = parserStateExportsState.get(parserState);
if (value !== true) return;
parserState.module.buildMeta.exportsType = "dynamic";
};
/**

View File

@ -5,6 +5,7 @@
"use strict";
const { UsageState } = require("../ExportsInfo");
const InitFragment = require("../InitFragment");
const RuntimeGlobals = require("../RuntimeGlobals");
const makeSerializable = require("../util/makeSerializable");
@ -45,9 +46,11 @@ HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate
runtime
}
) {
// TODO avoid getUsedExports
const usedExports = moduleGraph.getUsedExports(module, runtime);
if (usedExports === true || usedExports === null) {
const exportsInfo = moduleGraph.getExportsInfo(module);
if (
exportsInfo.getReadOnlyExportInfo("__esModule").getUsed(runtime) !==
UsageState.Unused
) {
const content = runtimeTemplate.defineEsModuleFlagStatement({
exportsArgument: module.exportsArgument,
runtimeRequirements
@ -63,17 +66,15 @@ HarmonyCompatibilityDependency.Template = class HarmonyExportDependencyTemplate
}
if (moduleGraph.isAsync(module)) {
runtimeRequirements.add(RuntimeGlobals.module);
if (usedExports !== false)
runtimeRequirements.add(RuntimeGlobals.exports);
const used = exportsInfo.isUsed(runtime);
if (used) runtimeRequirements.add(RuntimeGlobals.exports);
initFragments.push(
new InitFragment(
`${module.moduleArgument}.exports = (async () => {\n`,
InitFragment.STAGE_ASYNC_BOUNDARY,
0,
undefined,
usedExports !== false
? `\nreturn ${module.exportsArgument};\n})();`
: "\n})();"
used ? `\nreturn ${module.exportsArgument};\n})();` : "\n})();"
)
);
}

View File

@ -15,6 +15,7 @@ const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess");
const HarmonyExportInitFragment = require("./HarmonyExportInitFragment");
const HarmonyImportDependency = require("./HarmonyImportDependency");
const processExportInfo = require("./processExportInfo");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
@ -37,54 +38,6 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
const idsSymbol = Symbol("HarmonyExportImportedSpecifierDependency.ids");
/**
* @param {RuntimeSpec} runtime the runtime
* @param {string[][]} referencedExports list of referenced exports, will be added to
* @param {string[]} prefix export prefix
* @param {ExportInfo=} exportInfo the export info
* @param {boolean} defaultPointsToSelf when true, using default will reference itself
* @param {Set<ExportInfo>} alreadyVisited already visited export info (to handle circular reexports)
*/
const processExportInfo = (
runtime,
referencedExports,
prefix,
exportInfo,
defaultPointsToSelf = false,
alreadyVisited = new Set()
) => {
if (!exportInfo) {
referencedExports.push(prefix);
return;
}
if (alreadyVisited.has(exportInfo)) return;
alreadyVisited.add(exportInfo);
const used = exportInfo.getUsed(runtime);
if (used === UsageState.Unused) return;
if (
used !== UsageState.OnlyPropertiesUsed ||
!exportInfo.exportsInfo ||
exportInfo.exportsInfo.otherExportsInfo.getUsed(runtime) !==
UsageState.Unused
) {
referencedExports.push(prefix);
return;
}
const exportsInfo = exportInfo.exportsInfo;
for (const exportInfo of exportsInfo.orderedExports) {
processExportInfo(
runtime,
referencedExports,
defaultPointsToSelf && exportInfo.name === "default"
? prefix
: prefix.concat(exportInfo.name),
exportInfo,
false,
alreadyVisited
);
}
};
class NormalReexportItem {
/**
* @param {string} name export name
@ -229,6 +182,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
}
const importedExportsType = importedModule.getExportsType(
moduleGraph,
parentModule.buildMeta.strictHarmonyModule
);
@ -403,7 +357,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
if (exportInfo.getUsed(runtime) === UsageState.Unused) continue;
exports.add(name);
if (importedExportInfo.provided === true) continue;
checked.add(exportInfo.name);
checked.add(name);
}
}

View File

@ -104,6 +104,7 @@ class HarmonyImportDependency extends ModuleDependency {
const parentModule = moduleGraph.getParentModule(this);
const exportsType = importedModule.getExportsType(
moduleGraph,
parentModule.buildMeta.strictHarmonyModule
);
switch (exportsType) {
@ -199,16 +200,18 @@ class HarmonyImportDependency extends ModuleDependency {
*/
updateHash(hash, context) {
const { chunkGraph } = context;
const { moduleGraph } = chunkGraph;
super.updateHash(hash, context);
const importedModule = chunkGraph.moduleGraph.getModule(this);
const importedModule = moduleGraph.getModule(this);
if (importedModule) {
const parentModule = chunkGraph.moduleGraph.getParentModule(this);
const parentModule = moduleGraph.getParentModule(this);
hash.update(
importedModule.getExportsType(
moduleGraph,
parentModule.buildMeta && parentModule.buildMeta.strictHarmonyModule
)
);
if (chunkGraph.moduleGraph.isAsync(importedModule)) hash.update("async");
if (moduleGraph.isAsync(importedModule)) hash.update("async");
}
hash.update(`${this.sourceOrder}`);
}

View File

@ -113,7 +113,10 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
const selfModule = moduleGraph.getParentModule(this);
const importedModule = moduleGraph.getModule(this);
switch (
importedModule.getExportsType(selfModule.buildMeta.strictHarmonyModule)
importedModule.getExportsType(
moduleGraph,
selfModule.buildMeta.strictHarmonyModule
)
) {
case "default-only":
case "default-with-named":

View File

@ -0,0 +1,65 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const { UsageState } = require("../ExportsInfo");
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
/**
* @param {RuntimeSpec} runtime the runtime
* @param {string[][]} referencedExports list of referenced exports, will be added to
* @param {string[]} prefix export prefix
* @param {ExportInfo=} exportInfo the export info
* @param {boolean} defaultPointsToSelf when true, using default will reference itself
* @param {Set<ExportInfo>} alreadyVisited already visited export info (to handle circular reexports)
*/
const processExportInfo = (
runtime,
referencedExports,
prefix,
exportInfo,
defaultPointsToSelf = false,
alreadyVisited = new Set()
) => {
if (!exportInfo) {
referencedExports.push(prefix);
return;
}
const used = exportInfo.getUsed(runtime);
if (used === UsageState.Unused) return;
if (alreadyVisited.has(exportInfo)) {
referencedExports.push(prefix);
return;
}
alreadyVisited.add(exportInfo);
if (
used !== UsageState.OnlyPropertiesUsed ||
!exportInfo.exportsInfo ||
exportInfo.exportsInfo.otherExportsInfo.getUsed(runtime) !==
UsageState.Unused
) {
alreadyVisited.delete(exportInfo);
referencedExports.push(prefix);
return;
}
const exportsInfo = exportInfo.exportsInfo;
for (const exportInfo of exportsInfo.orderedExports) {
processExportInfo(
runtime,
referencedExports,
defaultPointsToSelf && exportInfo.name === "default"
? prefix
: prefix.concat(exportInfo.name),
exportInfo,
false,
alreadyVisited
);
}
alreadyVisited.delete(exportInfo);
};
module.exports = processExportInfo;

View File

@ -17,6 +17,7 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
/** @typedef {import("estree").ArrayExpression} ArrayExpressionNode */
/** @typedef {import("estree").BinaryExpression} BinaryExpressionNode */
/** @typedef {import("estree").BlockStatement} BlockStatementNode */
/** @typedef {import("estree").SequenceExpression} SequenceExpressionNode */
/** @typedef {import("estree").CallExpression} CallExpressionNode */
/** @typedef {import("estree").ClassDeclaration} ClassDeclarationNode */
/** @typedef {import("estree").ClassExpression} ClassExpressionNode */
@ -235,7 +236,7 @@ class JavascriptParser extends Parser {
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
call: new HookMap(() => new SyncBailHook(["expression"])),
/** Something like "a.b()" */
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
/** @type {HookMap<SyncBailHook<[CallExpressionNode, string[]], boolean | void>>} */
callMemberChain: new HookMap(
() => new SyncBailHook(["expression", "members"])
),
@ -294,6 +295,7 @@ class JavascriptParser extends Parser {
this.state = undefined;
this.comments = undefined;
this.semicolons = undefined;
/** @type {(StatementNode|ExpressionNode)[]} */
this.statementPath = undefined;
this.prevStatement = undefined;
this.currentTagData = undefined;
@ -983,7 +985,7 @@ class JavascriptParser extends Parser {
.setSideEffects(param.couldHaveSideEffects())
.setRange(expr.range);
});
["substr", "substring"].forEach(fn => {
["substr", "substring", "slice"].forEach(fn => {
this.hooks.evaluateCallExpressionMember
.for(fn)
.tap("JavascriptParser", (expr, param) => {
@ -2242,8 +2244,29 @@ class JavascriptParser extends Parser {
this.scope.topLevelScope = wasTopLevel;
}
/**
* @param {SequenceExpressionNode} expression the sequence
*/
walkSequenceExpression(expression) {
if (expression.expressions) this.walkExpressions(expression.expressions);
if (!expression.expressions) return;
// We treat sequence expressions like statements when they are one statement level
// This has some benefits for optimizations that only work on statement level
const currentStatement = this.statementPath[this.statementPath.length - 1];
if (
currentStatement === expression ||
(currentStatement.type === "ExpressionStatement" &&
currentStatement.expression === expression)
) {
const old = this.statementPath.pop();
for (const expr of expression.expressions) {
this.statementPath.push(expr);
this.walkExpression(expr);
this.statementPath.pop();
}
this.statementPath.push(old);
} else {
this.walkExpressions(expression.expressions);
}
}
walkUpdateExpression(expression) {
@ -2325,8 +2348,8 @@ class JavascriptParser extends Parser {
});
return;
}
this.walkExpression(expression.right);
if (expression.left.type.endsWith("Pattern")) {
this.walkExpression(expression.right);
this.enterPattern(expression.left, (name, decl) => {
if (!this.callHooksForName(this.hooks.assign, name, expression)) {
this.defineVariable(name);
@ -2349,8 +2372,10 @@ class JavascriptParser extends Parser {
return;
}
}
this.walkExpression(expression.right);
this.walkExpression(expression.left);
} else {
this.walkExpression(expression.right);
this.walkExpression(expression.left);
}
}
@ -2565,14 +2590,20 @@ class JavascriptParser extends Parser {
if (exprInfo) {
switch (exprInfo.type) {
case "expression": {
const result1 = this.callHooksForInfo(
this.hooks.expression,
exprInfo.name,
expression
);
if (result1 === true) return;
const members = exprInfo.getMembers();
const result = this.callHooksForInfo(
const result2 = this.callHooksForInfo(
this.hooks.expressionMemberChain,
exprInfo.rootInfo,
expression,
members
);
if (result === true) return;
if (result2 === true) return;
this.walkMemberExpressionWithExpressionName(
expression,
exprInfo.name,
@ -2616,12 +2647,6 @@ class JavascriptParser extends Parser {
members,
onUnhandled
) {
const result = this.callHooksForInfo(
this.hooks.expression,
name,
expression
);
if (result === true) return;
if (expression.object.type === "MemberExpression") {
// optimize the case where expression.object is a MemberExpression too.
// we can keep info here when calling walkMemberExpression directly
@ -2629,6 +2654,12 @@ class JavascriptParser extends Parser {
expression.property.name || `${expression.property.value}`;
name = name.slice(0, -property.length - 1);
members.pop();
const result = this.callHooksForInfo(
this.hooks.expression,
name,
expression.object
);
if (result === true) return;
this.walkMemberExpressionWithExpressionName(
expression.object,
name,
@ -3170,6 +3201,15 @@ class JavascriptParser extends Parser {
);
}
isStatementLevelExpression(expr) {
const currentStatement = this.statementPath[this.statementPath.length - 1];
return (
expr === currentStatement ||
(currentStatement.type === "ExpressionStatement" &&
currentStatement.expression === expr)
);
}
getTagData(name, tag) {
const info = this.scope.definitions.get(name);
if (info instanceof VariableInfo) {

View File

@ -256,7 +256,10 @@ const getExternalImport = (
asiSafe
) => {
let exprStart = info.name;
const exportsType = importedModule.getExportsType(strictHarmonyModule);
const exportsType = importedModule.getExportsType(
moduleGraph,
strictHarmonyModule
);
if (exportName.length === 0) {
switch (exportsType) {
case "default-only":
@ -1197,6 +1200,7 @@ class ConcatenatedModule extends Module {
if (
info.module.buildMeta.exportsType === "default" ||
info.module.buildMeta.exportsType === "flagged" ||
info.module.buildMeta.exportsType === "dynamic" ||
!info.module.buildMeta.exportsType
) {
const externalNameInterop = this.findNewName(
@ -1208,7 +1212,10 @@ class ConcatenatedModule extends Module {
allUsedNames.add(externalNameInterop);
info.interopNamespaceObjectName = externalNameInterop;
}
if (!info.module.buildMeta.exportsType) {
if (
info.module.buildMeta.exportsType === "dynamic" ||
!info.module.buildMeta.exportsType
) {
const externalNameInterop = this.findNewName(
"default",
allUsedNames,
@ -1436,6 +1443,7 @@ class ConcatenatedModule extends Module {
);
} else if (
info.module.buildMeta.exportsType === "flagged" ||
info.module.buildMeta.exportsType === "dynamic" ||
!info.module.buildMeta.exportsType
) {
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);

View File

@ -51,17 +51,23 @@ const mangleExportsInfo = (deterministic, exportsInfo, canBeArray) => {
/** @type {ExportInfo[]} */
const mangleableExports = [];
const empty = canBeArray ? ARRAY : OBJECT;
// Don't rename 1-2 char exports or exports that can't be mangled
for (const exportInfo of exportsInfo.ownedExports) {
const name = exportInfo.name;
if (!exportInfo.hasUsedName()) {
if (
// Can the export be mangled?
exportInfo.canMangle !== true ||
// Never rename 1 char exports
(name.length === 1 && /^[a-zA-Z0-9_$]/.test(name)) ||
// Don't rename 2 char exports in deterministic mode
(deterministic &&
name.length === 2 &&
/^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test(name)) ||
(exportInfo.provided !== true && exportInfo.name in empty)
// Don't rename exports that are not provided and in prototype chain of JSON
(exportInfo.provided !== true && exportInfo.name in empty) ||
// Don't rename exports that are neither provided nor used
(exportInfo.provided === false &&
exportInfo.getUsed(undefined) === UsageState.Unused)
) {
exportInfo.setUsedName(name);
usedNames.add(name);

View File

@ -47,6 +47,8 @@ module.exports = {
require("../dependencies/CachedConstDependency"),
"dependencies/CommonJsRequireContextDependency": () =>
require("../dependencies/CommonJsRequireContextDependency"),
"dependencies/CommonJsExportRequireDependency": () =>
require("../dependencies/CommonJsExportRequireDependency"),
"dependencies/CommonJsExportsDependency": () =>
require("../dependencies/CommonJsExportsDependency"),
"dependencies/CommonJsFullRequireDependency": () =>

View File

@ -521,10 +521,10 @@ exports[`StatsTestCases should print correct stats for chunks-development 1`] =
Time: X ms
Built at: 1970-04-20 12:42:42
PublicPath: (none)
asset b_js.bundle.js 901 bytes [emitted]
asset bundle.js 9.75 KiB [emitted] (name: main)
asset b_js.bundle.js 968 bytes [emitted]
asset bundle.js 9.82 KiB [emitted] (name: main)
asset c_js.bundle.js 1.1 KiB [emitted]
asset d_js-e_js.bundle.js 1.25 KiB [emitted]
asset d_js-e_js.bundle.js 1.38 KiB [emitted]
Entrypoint main = bundle.js
chunk b_js.bundle.js 22 bytes <{main}> [rendered]
> ./b ./index.js 2:0-16
@ -600,6 +600,19 @@ Entrypoint <CLR=BOLD>main</CLR> = <CLR=32>main.js</CLR>
<CLR=BOLD>./index.js</CLR> 1 bytes <CLR=32>[built]</CLR>"
`;
exports[`StatsTestCases should print correct stats for common-libs 1`] = `
"Hash: cb2d8154f4c30528e43b
Time: X ms
Built at: 1970-04-20 12:42:42
asset react.js 3.12 KiB [emitted] [minimized] (name: react)
asset react.js.LICENSE.txt 295 bytes [emitted]
Entrypoint react = react.js
./react.js 69 bytes [built]
../../../node_modules/react/index.js 190 bytes [built]
../../../node_modules/react/cjs/react.production.min.js 6.52 KiB [built]
../../../node_modules/object-assign/index.js 2.06 KiB [built]"
`;
exports[`StatsTestCases should print correct stats for commons-chunk-min-size-0 1`] = `
"Hash: b96c5a233eb1688dd315
Time: X ms
@ -2114,18 +2127,22 @@ chunk {996} (runtime: main) 996.js 22 bytes <{179}> [rendered]
ModuleConcatenation bailout: Module is not an ECMAScript module
[847] ./a.js 22 bytes {179} [depth 1] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
[996] ./b.js 22 bytes {996} [depth 1] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
[460] ./c.js 54 bytes {460} [depth 1] [built]
[used exports unknown]
ModuleConcatenation bailout: Module is not an ECMAScript module
[767] ./d.js 22 bytes {524} [depth 2] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
[390] ./e.js 22 bytes {524} [depth 2] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
webpack/runtime/ensure chunk 326 bytes {179} [runtime]
[no exports]
@ -2342,6 +2359,7 @@ chunk {179} (runtime: main) main.js (main) 73 bytes (javascript) 4.88 KiB (runti
> ./index main
[847] ./a.js 22 bytes {179} [depth 1] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
cjs self exports reference [847] ./a.js 1:0-14
cjs require ./a [10] ./index.js 1:0-14
@ -2380,12 +2398,14 @@ chunk {524} (runtime: main) 524.js 44 bytes <{460}> [rendered]
> [460] ./c.js 1:0-52
[767] ./d.js 22 bytes {524} [depth 2] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
require.ensure item ./d [460] ./c.js 1:0-52
cjs self exports reference [767] ./d.js 1:0-14
X ms [10] -> X ms [460] -> X ms (resolving: X ms, restoring: X ms, integration: X ms, building: X ms, storing: X ms)
[390] ./e.js 22 bytes {524} [depth 2] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
require.ensure item ./e [460] ./c.js 1:0-52
cjs self exports reference [390] ./e.js 1:0-14
@ -2394,6 +2414,7 @@ chunk {996} (runtime: main) 996.js 22 bytes <{179}> [rendered]
> ./b [10] ./index.js 2:0-16
[996] ./b.js 22 bytes {996} [depth 1] [built]
[used exports unknown]
CommonJS bailout: module.exports is used directly at 1:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
cjs self exports reference [996] ./b.js 1:0-14
amd require ./b [10] ./index.js 2:0-16
@ -2944,6 +2965,7 @@ Entrypoint entry = entry.js
ModuleConcatenation bailout: Cannot concat with ./ref-from-cjs.js: Module ./ref-from-cjs.js is referenced from these modules with unsupported syntax: ./cjs.js (referenced with cjs require)
./entry.js 32 bytes [built]
./cjs.js 59 bytes [built]
CommonJS bailout: module.exports is used directly at 3:0-14
ModuleConcatenation bailout: Module is not an ECMAScript module
./ref-from-cjs.js 45 bytes [built]
./eval.js 35 bytes [built]
@ -3065,9 +3087,9 @@ Entrypoint main = main.js
`;
exports[`StatsTestCases should print correct stats for side-effects-optimization 1`] = `
"Hash: d26335b5c063fdfc2a88c8cd6c316db9be4953d2
"Hash: dfef98e0ef4320bb5e7c18a1362fdec91250a8dc
Child
Hash: d26335b5c063fdfc2a88
Hash: dfef98e0ef4320bb5e7c
Time: X ms
Built at: 1970-04-20 12:42:42
asset main.js 207 bytes [emitted] [minimized] (name: main)
@ -3088,7 +3110,7 @@ Child
ModuleConcatenation bailout: Module is not an ECMAScript module
+ 4 hidden modules
Child
Hash: c8cd6c316db9be4953d2
Hash: 18a1362fdec91250a8dc
Time: X ms
Built at: 1970-04-20 12:42:42
asset main.no-side.js 1.24 KiB [emitted] [minimized] (name: main)
@ -4269,7 +4291,7 @@ Child global:
`;
exports[`StatsTestCases should print correct stats for tree-shaking 1`] = `
"Hash: e0f1716012b18c82ad97
"Hash: 9b742a666c2c28e22fdf
Time: X ms
Built at: 1970-04-20 12:42:42
asset bundle.js 7.09 KiB [emitted] (name: main)

61
types.d.ts vendored
View File

@ -3478,7 +3478,7 @@ declare abstract class JavascriptParser extends Parser {
topLevelAwait: SyncBailHook<[Expression], boolean | void>;
call: HookMap<SyncBailHook<[Expression], boolean | void>>;
callMemberChain: HookMap<
SyncBailHook<[Expression, string[]], boolean | void>
SyncBailHook<[CallExpression, string[]], boolean | void>
>;
memberChainOfCallMemberChain: HookMap<
SyncBailHook<
@ -3513,9 +3513,56 @@ declare abstract class JavascriptParser extends Parser {
state: Record<string, any> & ParserStateBase;
comments: any;
semicolons: any;
statementEndPos: any;
lastStatementEndPos: any;
statementStartPos: any;
statementPath: (
| UnaryExpression
| ThisExpression
| ArrayExpression
| ObjectExpression
| FunctionExpression
| ArrowFunctionExpression
| YieldExpression
| SimpleLiteral
| RegExpLiteral
| UpdateExpression
| BinaryExpression
| AssignmentExpression
| LogicalExpression
| MemberExpression
| ConditionalExpression
| SimpleCallExpression
| NewExpression
| SequenceExpression
| TemplateLiteral
| TaggedTemplateExpression
| ClassExpression
| MetaProperty
| Identifier
| AwaitExpression
| ImportExpression
| ChainExpression
| ExpressionStatement
| BlockStatement
| EmptyStatement
| DebuggerStatement
| WithStatement
| ReturnStatement
| LabeledStatement
| BreakStatement
| ContinueStatement
| IfStatement
| SwitchStatement
| ThrowStatement
| TryStatement
| WhileStatement
| DoWhileStatement
| ForStatement
| ForInStatement
| ForOfStatement
| FunctionDeclaration
| VariableDeclaration
| ClassDeclaration
)[];
prevStatement: any;
currentTagData: any;
initializeEvaluating(): void;
getRenameIdentifier(expr?: any): string;
@ -3584,7 +3631,7 @@ declare abstract class JavascriptParser extends Parser {
walkObjectExpression(expression?: any): void;
walkFunctionExpression(expression?: any): void;
walkArrowFunctionExpression(expression?: any): void;
walkSequenceExpression(expression?: any): void;
walkSequenceExpression(expression: SequenceExpression): void;
walkUpdateExpression(expression?: any): void;
walkUnaryExpression(expression?: any): void;
walkLeftRightExpression(expression?: any): void;
@ -3664,6 +3711,7 @@ declare abstract class JavascriptParser extends Parser {
evaluate(source?: any): BasicEvaluatedExpression;
getComments(range?: any): any;
isAsiPosition(pos?: any): any;
isStatementLevelExpression(expr?: any): boolean;
getTagData(name?: any, tag?: any): any;
tagVariable(name?: any, tag?: any, data?: any): void;
defineVariable(name?: any): void;
@ -3759,7 +3807,7 @@ declare interface KnownBuildMeta {
exportsArgument?: string;
strict?: boolean;
moduleConcatenationBailout?: string;
exportsType?: "namespace" | "default" | "flagged";
exportsType?: "namespace" | "dynamic" | "default" | "flagged";
defaultObject?: false | "redirect" | "redirect-warn";
strictHarmonyModule?: boolean;
async?: boolean;
@ -4176,6 +4224,7 @@ declare class Module extends DependenciesBlock {
readonly exportsArgument: string;
readonly moduleArgument: string;
getExportsType(
moduleGraph: ModuleGraph,
strict: boolean
): "namespace" | "default-only" | "default-with-named" | "dynamic";
addPresentationalDependency(presentationalDependency: Dependency): void;