Refactor to track nested exports

Harmony dependencies track access to nested properties
Flag nested exports
This commit is contained in:
Tobias Koppers 2019-03-14 12:06:59 +01:00
parent db5a8a33d3
commit 43bc7a306e
31 changed files with 961 additions and 434 deletions

View File

@ -34,9 +34,16 @@ const DependencyReference = require("./dependencies/DependencyReference");
/** @typedef {SynteticDependencyLocation|RealDependencyLocation} DependencyLocation */ /** @typedef {SynteticDependencyLocation|RealDependencyLocation} DependencyLocation */
/**
* @typedef {Object} ExportSpec
* @property {string} name the name of the export
* @property {Module=} from when reexported: from which module
* @property {string[] | null=} export when reexported: from which export
*/
/** /**
* @typedef {Object} ExportsSpec * @typedef {Object} ExportsSpec
* @property {string[] | true | null} exports exported names, true for unknown exports or null for no exports * @property {(string | ExportSpec)[] | true | null} exports exported names, true for unknown exports or null for no exports
* @property {Module[]=} dependencies module on which the result depends on * @property {Module[]=} dependencies module on which the result depends on
*/ */

View File

@ -12,6 +12,7 @@ const Queue = require("./util/Queue");
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */ /** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */
const getCacheIdentifier = (compilation, module) => { const getCacheIdentifier = (compilation, module) => {
return `${ return `${
@ -79,6 +80,7 @@ class FlagDependencyExportsPlugin {
/** @type {Module} */ /** @type {Module} */
let module; let module;
/** @type {ExportsInfo} */
let exportsInfo; let exportsInfo;
let cacheable = true; let cacheable = true;
@ -113,11 +115,35 @@ class FlagDependencyExportsPlugin {
} }
} else if (Array.isArray(exports)) { } else if (Array.isArray(exports)) {
// merge in new exports // merge in new exports
for (const exportName of exports) { for (const exportNameOrSpec of exports) {
const exportInfo = exportsInfo.getExportInfo(exportName); if (typeof exportNameOrSpec === "string") {
if (exportInfo.provided === false) { const exportInfo = exportsInfo.getExportInfo(
exportInfo.provided = true; exportNameOrSpec
changed = true; );
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
} else {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec.name
);
if (exportInfo.provided === false) {
exportInfo.provided = true;
changed = true;
}
if (exportNameOrSpec.from) {
const fromExportsInfo = moduleGraph.getExportsInfo(
exportNameOrSpec.from
);
const nestedExportsInfo = fromExportsInfo.getNestedExportsInfo(
exportNameOrSpec.export
);
if (exportInfo.exportsInfo !== nestedExportsInfo) {
exportInfo.exportsInfo = nestedExportsInfo;
changed = true;
}
}
} }
} }
} }

View File

@ -5,6 +5,7 @@
"use strict"; "use strict";
const { UsageState } = require("./ModuleGraph");
const { STAGE_DEFAULT } = require("./OptimizationStages"); const { STAGE_DEFAULT } = require("./OptimizationStages");
const Queue = require("./util/Queue"); const Queue = require("./util/Queue");
@ -12,6 +13,10 @@ const Queue = require("./util/Queue");
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */ /** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */
const NS_OBJ_USED = [[]];
const NOTHING_USED = [];
class FlagDependencyUsagePlugin { class FlagDependencyUsagePlugin {
/** /**
@ -27,31 +32,60 @@ class FlagDependencyUsagePlugin {
stage: STAGE_DEFAULT stage: STAGE_DEFAULT
}, },
modules => { modules => {
/** @type {Map<ExportsInfo, Module>} */
const exportInfoToModuleMap = new Map();
/** /**
* * @typedef {string[]} StringArray
* @param {Module} module module to process * @param {Module} module module to process
* @param {boolean | string[]} usedExports list of used exports * @param {StringArray[]} usedExports list of used exports
* @returns {void} * @returns {void}
*/ */
const processModule = (module, usedExports) => { const processModule = (module, usedExports) => {
const exportsInfo = moduleGraph.getExportsInfo(module); const exportsInfo = moduleGraph.getExportsInfo(module);
let changed = false; if (usedExports.length > 0) {
if (usedExports === true) { for (const usedExport of usedExports) {
changed = exportsInfo.setUsedInUnknownWay(); if (usedExport.length === 0) {
} else if (usedExports) { if (exportsInfo.setUsedInUnknownWay()) {
for (const exportName of usedExports) { queue.enqueue(module);
if (
exportName === "default" &&
module.buildMeta.exportsType === "named"
) {
if (exportsInfo.setUsedAsNamedExportType()) {
changed = true;
} }
} else { } else {
const exportInfo = exportsInfo.getExportInfo(exportName); if (
if (exportInfo.used !== true) { usedExport[0] === "default" &&
exportInfo.used = true; module.buildMeta.exportsType === "named"
changed = true; ) {
if (exportsInfo.setUsedAsNamedExportType()) {
queue.enqueue(module);
}
} else {
let currentExportsInfo = exportsInfo;
let currentModule = module;
for (let i = 0; i < usedExport.length; i++) {
const exportName = usedExport[i];
const exportInfo = currentExportsInfo.getExportInfo(
exportName
);
const lastOne = i === usedExport.length - 1;
const nestedInfo = exportInfo.exportsInfo;
if (!nestedInfo || lastOne) {
if (exportInfo.used !== UsageState.Used) {
exportInfo.used = UsageState.Used;
if (currentModule) {
queue.enqueue(currentModule);
}
}
break;
} else {
if (exportInfo.used === UsageState.Unused) {
exportInfo.used = UsageState.OnlyPropertiesUsed;
if (currentModule) {
queue.enqueue(currentModule);
}
}
currentExportsInfo = nestedInfo;
currentModule = exportInfoToModuleMap.get(nestedInfo);
}
}
} }
} }
} }
@ -59,11 +93,9 @@ class FlagDependencyUsagePlugin {
// for a module without side effects we stop tracking usage here when no export is used // for a module without side effects we stop tracking usage here when no export is used
// This module won't be evaluated in this case // This module won't be evaluated in this case
if (module.factoryMeta.sideEffectFree) return; if (module.factoryMeta.sideEffectFree) return;
changed = exportsInfo.setUsedForSideEffectsOnly(); if (exportsInfo.setUsedForSideEffectsOnly()) {
} queue.enqueue(module);
}
if (changed) {
queue.enqueue(module);
} }
}; };
@ -89,11 +121,21 @@ class FlagDependencyUsagePlugin {
if (!reference) return; if (!reference) return;
const referenceModule = reference.module; const referenceModule = reference.module;
const importedNames = reference.importedNames; const importedNames = reference.importedNames;
processModule(referenceModule, importedNames);
processModule(
referenceModule,
importedNames === false
? NOTHING_USED
: importedNames === true
? NS_OBJ_USED
: importedNames.map(n => (Array.isArray(n) ? n : [n]))
);
}; };
for (const module of modules) { for (const module of modules) {
moduleGraph.getExportsInfo(module).setHasUseInfo(); const exportsInfo = moduleGraph.getExportsInfo(module);
exportInfoToModuleMap.set(exportsInfo, module);
exportsInfo.setHasUseInfo();
} }
/** @type {Queue<DependenciesBlock>} */ /** @type {Queue<DependenciesBlock>} */
@ -103,7 +145,7 @@ class FlagDependencyUsagePlugin {
for (const dep of deps) { for (const dep of deps) {
const module = moduleGraph.getModule(dep); const module = moduleGraph.getModule(dep);
if (module) { if (module) {
processModule(module, true); processModule(module, NS_OBJ_USED);
} }
} }
} }

View File

@ -27,6 +27,32 @@ const joinIterableWithComma = iterable => {
return str; return str;
}; };
const printExportsInfoToSource = (source, indent, exportsInfo) => {
let hasExports = false;
for (const exportInfo of exportsInfo.orderedExports) {
source.add(
Template.toComment(
`${indent}export ${
exportInfo.name
} [${exportInfo.getProvidedInfo()}] [${exportInfo.getUsedInfo()}] [${exportInfo.getRenameInfo()}]`
) + "\n"
);
if (exportInfo.exportsInfo) {
printExportsInfoToSource(source, indent + " ", exportInfo.exportsInfo);
}
hasExports = true;
}
const otherExportsInfo = exportsInfo.otherExportsInfo;
if (otherExportsInfo.provided !== false || otherExportsInfo.used !== false) {
const title = hasExports ? "other exports" : "exports";
source.add(
Template.toComment(
`${indent}${title} [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]`
) + "\n"
);
}
};
class FunctionModuleTemplatePlugin { class FunctionModuleTemplatePlugin {
constructor({ compilation }) { constructor({ compilation }) {
this.compilation = compilation; this.compilation = compilation;
@ -84,26 +110,7 @@ class FunctionModuleTemplatePlugin {
source.add(" !*** " + reqStr + " ***!\n"); source.add(" !*** " + reqStr + " ***!\n");
source.add(" \\****" + reqStrStar + "****/\n"); source.add(" \\****" + reqStrStar + "****/\n");
const exportsInfo = moduleGraph.getExportsInfo(module); const exportsInfo = moduleGraph.getExportsInfo(module);
for (const exportInfo of exportsInfo.orderedExports) { printExportsInfoToSource(source, "", exportsInfo);
source.add(
Template.toComment(
`export ${
exportInfo.name
} [${exportInfo.getProvidedInfo()}] [${exportInfo.getUsedInfo()}] [${exportInfo.getRenameInfo()}]`
) + "\n"
);
}
const otherExportsInfo = exportsInfo.otherExportsInfo;
if (
otherExportsInfo.provided !== false ||
otherExportsInfo.used !== false
) {
source.add(
Template.toComment(
`other exports [${otherExportsInfo.getProvidedInfo()}] [${otherExportsInfo.getUsedInfo()}]`
) + "\n"
);
}
source.add( source.add(
Template.toComment( Template.toComment(
`runtime requirements: ${joinIterableWithComma( `runtime requirements: ${joinIterableWithComma(

View File

@ -91,10 +91,14 @@ class JavascriptParser {
typeof: new HookMap(() => new SyncBailHook(["expression"])), typeof: new HookMap(() => new SyncBailHook(["expression"])),
importCall: new SyncBailHook(["expression"]), importCall: new SyncBailHook(["expression"]),
call: new HookMap(() => new SyncBailHook(["expression"])), call: new HookMap(() => new SyncBailHook(["expression"])),
callAnyMember: new HookMap(() => new SyncBailHook(["expression"])), callMemberChain: new HookMap(
() => new SyncBailHook(["expression", "rootRaw", "members"])
),
new: new HookMap(() => new SyncBailHook(["expression"])), new: new HookMap(() => new SyncBailHook(["expression"])),
expression: new HookMap(() => new SyncBailHook(["expression"])), expression: new HookMap(() => new SyncBailHook(["expression"])),
expressionAnyMember: new HookMap(() => new SyncBailHook(["expression"])), expressionMemberChain: new HookMap(
() => new SyncBailHook(["expression", "rootRaw", "members"])
),
expressionConditionalOperator: new SyncBailHook(["expression"]), expressionConditionalOperator: new SyncBailHook(["expression"]),
expressionLogicalOperator: new SyncBailHook(["expression"]), expressionLogicalOperator: new SyncBailHook(["expression"]),
program: new SyncBailHook(["ast", "comments"]) program: new SyncBailHook(["ast", "comments"])
@ -1831,14 +1835,17 @@ class JavascriptParser {
let result = callHook.call(expression); let result = callHook.call(expression);
if (result === true) return; if (result === true) return;
} }
// TODO replace with lastIndexOf for performance reasons }
let identifier = callee.identifier.replace(/\.[^.]+$/, ""); const exprName = this.getNameForExpression(expression.callee);
if (identifier !== callee.identifier) { if (exprName) {
const callAnyHook = this.hooks.callAnyMember.get(identifier); const hook = this.hooks.callMemberChain.get(exprName.rootName);
if (callAnyHook !== undefined) { if (hook !== undefined) {
let result = callAnyHook.call(expression); const result = hook.call(
if (result === true) return; expression,
} exprName.rootRaw,
exprName.members
);
if (result === true) return;
} }
} }
@ -1855,13 +1862,19 @@ class JavascriptParser {
const result = expressionHook.call(expression); const result = expressionHook.call(expression);
if (result === true) return; if (result === true) return;
} }
const expressionAnyMemberHook = this.hooks.expressionAnyMember.get( const expressionMemberChainHook = this.hooks.expressionMemberChain.get(
exprName.nameGeneral exprName.rootName
); );
if (expressionAnyMemberHook !== undefined) { if (expressionMemberChainHook !== undefined) {
const result = expressionAnyMemberHook.call(expression); const result = expressionMemberChainHook.call(
expression,
exprName.rootRaw,
exprName.members
);
if (result === true) return; if (result === true) return;
} }
// TODO optimize case where expression.object is a MemberExpression too.
// we can keep info here when calling walkMemberExpression directly
} }
this.walkExpression(expression.object); this.walkExpression(expression.object);
if (expression.computed === true) this.walkExpression(expression.property); if (expression.computed === true) this.walkExpression(expression.property);
@ -2189,22 +2202,27 @@ class JavascriptParser {
expr.type === "MemberExpression" && expr.type === "MemberExpression" &&
expr.property.type === (expr.computed ? "Literal" : "Identifier") expr.property.type === (expr.computed ? "Literal" : "Identifier")
) { ) {
exprName.push(expr.computed ? expr.property.value : expr.property.name); exprName.push(
expr.computed ? `${expr.property.value}` : expr.property.name
);
expr = expr.object; expr = expr.object;
} }
let free; let free;
let rootRaw;
if (expr.type === "Identifier") { if (expr.type === "Identifier") {
free = !this.scope.definitions.has(expr.name); free = !this.scope.definitions.has(expr.name);
exprName.push(this.scope.renames.get(expr.name) || expr.name); exprName.push(this.scope.renames.get(expr.name) || expr.name);
} else if ( rootRaw = expr.name;
expr.type === "ThisExpression" &&
this.scope.renames.get("this")
) {
free = true;
exprName.push(this.scope.renames.get("this"));
} else if (expr.type === "ThisExpression") { } else if (expr.type === "ThisExpression") {
free = this.scope.topLevelScope; const rename = this.scope.renames.get("this");
exprName.push("this"); if (rename) {
free = true;
exprName.push(rename);
} else {
free = this.scope.topLevelScope;
exprName.push("this");
}
rootRaw = "this";
} else { } else {
return null; return null;
} }
@ -2217,9 +2235,13 @@ class JavascriptParser {
} }
const name = prefix ? prefix + "." + exprName[0] : exprName[0]; const name = prefix ? prefix + "." + exprName[0] : exprName[0];
const nameGeneral = prefix; const nameGeneral = prefix;
const rootName = exprName.pop();
return { return {
name, name,
nameGeneral, nameGeneral,
rootName,
rootRaw,
members: exprName.reverse(),
free free
}; };
} }

View File

@ -7,6 +7,7 @@
const { ConcatSource, RawSource } = require("webpack-sources"); const { ConcatSource, RawSource } = require("webpack-sources");
const Generator = require("./Generator"); const Generator = require("./Generator");
const { UsageState } = require("./ModuleGraph");
const RuntimeGlobals = require("./RuntimeGlobals"); const RuntimeGlobals = require("./RuntimeGlobals");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
@ -64,7 +65,7 @@ class JsonGenerator extends Generator {
const providedExports = moduleGraph.getProvidedExports(module); const providedExports = moduleGraph.getProvidedExports(module);
if ( if (
Array.isArray(providedExports) && Array.isArray(providedExports) &&
!module.isExportUsed(moduleGraph, "default") module.isExportUsed(moduleGraph, "default") === UsageState.Unused
) { ) {
// Only some exports are used: We can optimize here, by only generating a part of the JSON // Only some exports are used: We can optimize here, by only generating a part of the JSON
const reducedJson = {}; const reducedJson = {};

View File

@ -18,6 +18,7 @@ const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ /** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./FileSystemInfo")} FileSystemInfo */ /** @typedef {import("./FileSystemInfo")} FileSystemInfo */
/** @typedef {import("./ModuleGraph").UsageStateType} UsageStateType */
/** @typedef {import("./RequestShortener")} RequestShortener */ /** @typedef {import("./RequestShortener")} RequestShortener */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("./WebpackError")} WebpackError */ /** @typedef {import("./WebpackError")} WebpackError */
@ -385,23 +386,22 @@ class Module extends DependenciesBlock {
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @param {string} exportName a name of an export * @param {string | string[]} exportName a name of an export
* @returns {boolean} true, if the export is used * @returns {UsageStateType} state of the export
*/ */
isExportUsed(moduleGraph, exportName) { isExportUsed(moduleGraph, exportName) {
const exportInfo = moduleGraph.getReadOnlyExportInfo(this, exportName); return moduleGraph.getExportsInfo(this).isExportUsed(exportName);
return exportInfo.used !== false;
} }
// TODO move to ModuleGraph // TODO move to ModuleGraph
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @param {string} exportName a name of an export * @param {string | string[]} exportName a name of an export
* @returns {string | false} false, when module or referenced export is unused. * @returns {string | string[] | false} false, when module or referenced export is unused.
* string, the mangled export name when used. * string, the mangled export name when used.
*/ */
getUsedName(moduleGraph, exportName) { getUsedName(moduleGraph, exportName) {
return moduleGraph.getExportInfo(this, exportName).getUsedName(); return moduleGraph.getExportsInfo(this).getUsedName(exportName);
} }
/** /**

View File

@ -21,6 +21,16 @@ const SortableSet = require("./util/SortableSet");
* @returns {string} * @returns {string}
*/ */
/** @typedef {0|1|2|3|4} UsageStateType */
const UsageState = Object.freeze({
NoInfo: /** @type {0} */ (0),
Unused: /** @type {1} */ (1),
Unknown: /** @type {2} */ (2),
OnlyPropertiesUsed: /** @type {3} */ (3),
Used: /** @type {4} */ (4)
});
class ExportsInfo { class ExportsInfo {
constructor() { constructor() {
/** @type {Map<string, ExportInfo>} */ /** @type {Map<string, ExportInfo>} */
@ -92,21 +102,21 @@ class ExportsInfo {
setHasUseInfo() { setHasUseInfo() {
for (const exportInfo of this._exports.values()) { for (const exportInfo of this._exports.values()) {
if (exportInfo.used === undefined) { if (exportInfo.used === UsageState.NoInfo) {
exportInfo.used = false; exportInfo.used = UsageState.Unused;
} }
if (exportInfo.canMangleUse === undefined) { if (exportInfo.canMangleUse === undefined) {
exportInfo.canMangleUse = true; exportInfo.canMangleUse = true;
} }
} }
if (this._otherExportsInfo.used === undefined) { if (this._otherExportsInfo.used === UsageState.NoInfo) {
this._otherExportsInfo.used = false; this._otherExportsInfo.used = UsageState.Unused;
} }
if (this._otherExportsInfo.canMangleUse === undefined) { if (this._otherExportsInfo.canMangleUse === undefined) {
this._otherExportsInfo.canMangleUse = true; this._otherExportsInfo.canMangleUse = true;
} }
if (this._sideEffectsOnlyInfo.used === undefined) { if (this._sideEffectsOnlyInfo.used === UsageState.NoInfo) {
this._sideEffectsOnlyInfo.used = false; this._sideEffectsOnlyInfo.used = UsageState.Unused;
} }
} }
@ -127,6 +137,20 @@ class ExportsInfo {
return this._otherExportsInfo; return this._otherExportsInfo;
} }
/**
* @param {string[]=} name the export name
* @returns {ExportsInfo | undefined} the nested exports info
*/
getNestedExportsInfo(name) {
if (Array.isArray(name) && name.length > 0) {
let info = this._exports.get(name[0]);
if (info === undefined) info = this._otherExportsInfo;
if (!info.exportsInfo) return undefined;
return info.exportsInfo.getNestedExportsInfo(name.slice(1));
}
return this;
}
/** /**
* @returns {boolean} true, if this call changed something * @returns {boolean} true, if this call changed something
*/ */
@ -163,8 +187,8 @@ class ExportsInfo {
changed = true; changed = true;
} }
for (const exportInfo of this._exports.values()) { for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== true && exportInfo.used !== null) { if (exportInfo.used < UsageState.Unknown) {
exportInfo.used = null; exportInfo.used = UsageState.Unknown;
changed = true; changed = true;
} }
if (exportInfo.canMangleUse !== false) { if (exportInfo.canMangleUse !== false) {
@ -172,11 +196,8 @@ class ExportsInfo {
changed = true; changed = true;
} }
} }
if ( if (this._otherExportsInfo.used < UsageState.Unknown) {
this._otherExportsInfo.used !== true && this._otherExportsInfo.used = UsageState.Unknown;
this._otherExportsInfo.used !== null
) {
this._otherExportsInfo.used = null;
changed = true; changed = true;
} }
if (this._otherExportsInfo.canMangleUse !== false) { if (this._otherExportsInfo.canMangleUse !== false) {
@ -192,10 +213,10 @@ class ExportsInfo {
this._isUsed = true; this._isUsed = true;
changed = true; changed = true;
} }
this.getExportInfo("default").used = true; this.getExportInfo("default").used = UsageState.Used;
for (const exportInfo of this._exports.values()) { for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== true && exportInfo.used !== null) { if (exportInfo.used < UsageState.Unknown) {
exportInfo.used = null; exportInfo.used = UsageState.Unknown;
changed = true; changed = true;
} }
if (exportInfo.name !== "default" && exportInfo.canMangleUse !== false) { if (exportInfo.name !== "default" && exportInfo.canMangleUse !== false) {
@ -203,11 +224,8 @@ class ExportsInfo {
changed = true; changed = true;
} }
} }
if ( if (this._otherExportsInfo.used < UsageState.Unknown) {
this._otherExportsInfo.used !== true && this._otherExportsInfo.used = UsageState.Unknown;
this._otherExportsInfo.used !== null
) {
this._otherExportsInfo.used = null;
changed = true; changed = true;
} }
if (this._otherExportsInfo.canMangleUse !== false) { if (this._otherExportsInfo.canMangleUse !== false) {
@ -218,22 +236,22 @@ class ExportsInfo {
} }
setUsedForSideEffectsOnly() { setUsedForSideEffectsOnly() {
if (this._sideEffectsOnlyInfo.used === false) { if (this._sideEffectsOnlyInfo.used === UsageState.Unused) {
this._sideEffectsOnlyInfo.used = true; this._sideEffectsOnlyInfo.used = UsageState.Used;
return true; return true;
} }
return false; return false;
} }
isUsed() { isUsed() {
if (this._otherExportsInfo.used !== false) { if (this._otherExportsInfo.used !== UsageState.Unused) {
return true; return true;
} }
if (this._sideEffectsOnlyInfo.used !== false) { if (this._sideEffectsOnlyInfo.used !== UsageState.Unused) {
return true; return true;
} }
for (const exportInfo of this._exports.values()) { for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== false) { if (exportInfo.used !== UsageState.Unused) {
return true; return true;
} }
} }
@ -242,30 +260,32 @@ class ExportsInfo {
getUsedExports() { getUsedExports() {
switch (this._otherExportsInfo.used) { switch (this._otherExportsInfo.used) {
case undefined: case UsageState.NoInfo:
return null; return null;
case null: case UsageState.Unknown:
return true; return true;
case true: case UsageState.OnlyPropertiesUsed:
case UsageState.Used:
return true; return true;
} }
const array = []; const array = [];
if (!this._exportsAreOrdered) this._sortExports(); if (!this._exportsAreOrdered) this._sortExports();
for (const exportInfo of this._exports.values()) { for (const exportInfo of this._exports.values()) {
switch (exportInfo.used) { switch (exportInfo.used) {
case undefined: case UsageState.NoInfo:
return null; return null;
case null: case UsageState.Unknown:
return true; return true;
case true: case UsageState.OnlyPropertiesUsed:
case UsageState.Used:
array.push(exportInfo.name); array.push(exportInfo.name);
} }
} }
if (array.length === 0) { if (array.length === 0) {
switch (this._sideEffectsOnlyInfo.used) { switch (this._sideEffectsOnlyInfo.used) {
case undefined: case UsageState.NoInfo:
return null; return null;
case false: case UsageState.Unused:
return false; return false;
} }
} }
@ -296,12 +316,61 @@ class ExportsInfo {
return array; return array;
} }
/**
* @param {string | string[]} name the name of the export
* @returns {boolean | undefined | null} if the export is provided
*/
isExportProvided(name) { isExportProvided(name) {
if (Array.isArray(name)) {
// TODO follow nested exports
return this.isExportProvided(name[0]);
}
let info = this._exports.get(name); let info = this._exports.get(name);
if (info === undefined) info = this._otherExportsInfo; if (info === undefined) info = this._otherExportsInfo;
return info.provided; return info.provided;
} }
/**
* @param {string | string[]} name export name
* @returns {UsageStateType} usage status
*/
isExportUsed(name) {
if (Array.isArray(name)) {
if (name.length === 0) return this.otherExportsInfo.used;
// TODO follow nested exports
return this.isExportUsed(name[0]);
}
let info = this._exports.get(name);
if (info === undefined) info = this._otherExportsInfo;
return info.used;
}
/**
* @param {string | string[]} name the export name
* @returns {string | string[] | false} the used name
*/
getUsedName(name) {
if (Array.isArray(name)) {
// TODO improve this
if (name.length === 0) return name;
let info = this._exports.get(name[0]);
if (info === undefined) info = this._otherExportsInfo;
const x = info.getUsedName();
if (!x) return false;
if (info.exportsInfo) {
const nested = info.exportsInfo.getUsedName(name.slice(1));
if (!nested) return false;
return [x].concat(nested);
} else {
return [x].concat(name.slice(1));
}
} else {
let info = this._exports.get(name);
if (info === undefined) info = this._otherExportsInfo;
return info.getUsedName();
}
}
getRestoreProvidedData() { getRestoreProvidedData() {
const otherProvided = this._otherExportsInfo.provided; const otherProvided = this._otherExportsInfo.provided;
const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide; const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide;
@ -350,14 +419,8 @@ class ExportInfo {
this.name = name; this.name = name;
/** @type {string | null} */ /** @type {string | null} */
this.usedName = initFrom ? initFrom.usedName : null; this.usedName = initFrom ? initFrom.usedName : null;
/** /** @type {UsageStateType} */
* true: it is used this.used = initFrom ? initFrom.used : UsageState.NoInfo;
* false: it is not used
* null: only the runtime knows if it is used
* undefined: it was not determined if it is used
* @type {boolean | null | undefined}
*/
this.used = initFrom ? initFrom.used : undefined;
/** /**
* true: it is provided * true: it is provided
* false: it is not provided * false: it is not provided
@ -380,6 +443,8 @@ class ExportInfo {
* @type {boolean | undefined} * @type {boolean | undefined}
*/ */
this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined; this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
/** @type {ExportsInfo=} */
this.exportsInfo = undefined;
} }
get canMangle() { get canMangle() {
@ -406,20 +471,22 @@ class ExportInfo {
} }
getUsedName() { getUsedName() {
if (this.used === false) return false; if (this.used === UsageState.Unused) return false;
return this.usedName || this.name; return this.usedName || this.name;
} }
getUsedInfo() { getUsedInfo() {
switch (this.used) { switch (this.used) {
case undefined: case UsageState.NoInfo:
return "no usage info"; return "no usage info";
case null: case UsageState.Unknown:
return "maybe used (runtime-defined)"; return "maybe used (runtime-defined)";
case true: case UsageState.Used:
return "used"; return "used";
case false: case UsageState.Unused:
return "unused"; return "unused";
case UsageState.OnlyPropertiesUsed:
return "only properties used";
} }
} }
@ -828,7 +895,7 @@ class ModuleGraph {
/** /**
* @param {Module} module the module * @param {Module} module the module
* @param {string} exportName a name of an export * @param {string | string[]} exportName a name of an export
* @returns {boolean | null} true, if the export is provided by the module. * @returns {boolean | null} true, if the export is provided by the module.
* null, if it's unknown. * null, if it's unknown.
* false, if it's not provided. * false, if it's not provided.
@ -1044,3 +1111,6 @@ const deprecateMap = new Map();
module.exports = ModuleGraph; module.exports = ModuleGraph;
module.exports.ModuleGraphConnection = ModuleGraphConnection; module.exports.ModuleGraphConnection = ModuleGraphConnection;
module.exports.ExportsInfo = ExportsInfo;
module.exports.ExportInfo = ExportInfo;
module.exports.UsageState = UsageState;

View File

@ -7,6 +7,7 @@
const RuntimeGlobals = require("./RuntimeGlobals"); const RuntimeGlobals = require("./RuntimeGlobals");
const Template = require("./Template"); const Template = require("./Template");
const propertyAccess = require("./util/propertyAccess");
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ /** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./ChunkGraph")} ChunkGraph */ /** @typedef {import("./ChunkGraph")} ChunkGraph */
@ -14,6 +15,14 @@ const Template = require("./Template");
/** @typedef {import("./ModuleGraph")} ModuleGraph */ /** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @typedef {import("./RequestShortener")} RequestShortener */ /** @typedef {import("./RequestShortener")} RequestShortener */
const arrayEquals = (a, b) => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
};
class RuntimeTemplate { class RuntimeTemplate {
/** /**
* @param {TODO} outputOptions the compilation output options * @param {TODO} outputOptions the compilation output options
@ -506,7 +515,7 @@ class RuntimeTemplate {
* @param {ModuleGraph} options.moduleGraph the module graph * @param {ModuleGraph} options.moduleGraph the module graph
* @param {Module} options.module the module * @param {Module} options.module the module
* @param {string} options.request the request * @param {string} options.request the request
* @param {string} options.exportName the export name * @param {string | string[]} options.exportName the export name
* @param {Module} options.originModule the origin module * @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.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.isCall true, if expression will be called
@ -532,24 +541,30 @@ class RuntimeTemplate {
request request
}); });
} }
if (!Array.isArray(exportName)) {
exportName = exportName ? [exportName] : [];
}
const exportsType = module.buildMeta && module.buildMeta.exportsType; const exportsType = module.buildMeta && module.buildMeta.exportsType;
if (!exportsType) { if (!exportsType) {
if (exportName === "default") { if (exportName.length > 0 && exportName[0] === "default") {
if (!originModule.buildMeta.strictHarmonyModule) { if (!originModule.buildMeta.strictHarmonyModule) {
if (isCall) { if (isCall) {
return `${importVar}_default()`; return `${importVar}_default()${propertyAccess(exportName, 1)}`;
} else if (asiSafe) { } else if (asiSafe) {
return `(${importVar}_default())`; return `(${importVar}_default()${propertyAccess(exportName, 1)})`;
} else { } else {
return `${importVar}_default.a`; return `${importVar}_default.a${propertyAccess(exportName, 1)}`;
} }
} else { } else {
return importVar; return `${importVar}${propertyAccess(exportName, 1)}`;
} }
} else if (originModule.buildMeta.strictHarmonyModule) { } else if (originModule.buildMeta.strictHarmonyModule) {
if (exportName) { if (exportName.length > 0) {
return "/* non-default import from non-esm module */undefined"; return (
"/* non-default import from non-esm module */undefined" +
propertyAccess(exportName, 1)
);
} else { } else {
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject); runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
return `/*#__PURE__*/${ return `/*#__PURE__*/${
@ -560,26 +575,30 @@ class RuntimeTemplate {
} }
if (exportsType === "named") { if (exportsType === "named") {
if (exportName === "default") { if (exportName.length > 0 && exportName[0] === "default") {
return importVar; return `${importVar}${propertyAccess(exportName, 1)}`;
} else if (!exportName) { } else if (exportName.length === 0) {
return `${importVar}_namespace`; return `${importVar}_namespace`;
} }
} }
if (exportName) { if (exportName.length > 0) {
const used = module.getUsedName(moduleGraph, exportName); const exportsInfo = moduleGraph.getExportsInfo(module);
const used = exportsInfo.getUsedName(exportName);
if (!used) { if (!used) {
const comment = Template.toNormalComment(`unused export ${exportName}`); const comment = Template.toNormalComment(
`unused export ${exportName.join(".")}`
);
return `${comment} undefined`; return `${comment} undefined`;
} }
const comment = const comment = arrayEquals(used, exportName)
used !== exportName ? Template.toNormalComment(exportName) + " " : ""; ? ""
const access = `${importVar}[${comment}${JSON.stringify(used)}]`; : Template.toNormalComment(exportName.join(".")) + " ";
if (isCall) { const access = `${importVar}${comment}${propertyAccess(used)}`;
if (callContext === false && asiSafe) { if (isCall && callContext === false) {
if (asiSafe) {
return `(0,${access})`; return `(0,${access})`;
} else if (callContext === false) { } else {
return `Object(${access})`; return `Object(${access})`;
} }
} }

View File

@ -14,7 +14,7 @@ class DependencyReference {
/** /**
* *
* @param {ModuleCallback} moduleCallback a callback to get the referenced module * @param {ModuleCallback} moduleCallback a callback to get the referenced module
* @param {string[] | boolean} importedNames imported named from the module * @param {(string | string[])[] | boolean} importedNames imported named from the module
* @param {boolean=} weak if this is a weak reference * @param {boolean=} weak if this is a weak reference
* @param {number} order the order information or NaN if don't care * @param {number} order the order information or NaN if don't care
*/ */

View File

@ -95,7 +95,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
dep = new HarmonyExportImportedSpecifierDependency( dep = new HarmonyExportImportedSpecifierDependency(
settings.source, settings.source,
settings.sourceOrder, settings.sourceOrder,
settings.id, settings.ids,
name, name,
harmonyNamedExports, harmonyNamedExports,
null, null,
@ -125,7 +125,7 @@ module.exports = class HarmonyExportDependencyParserPlugin {
const dep = new HarmonyExportImportedSpecifierDependency( const dep = new HarmonyExportImportedSpecifierDependency(
source, source,
parser.state.lastHarmonyImportOrder, parser.state.lastHarmonyImportOrder,
id, id ? [id] : [],
name, name,
harmonyNamedExports, harmonyNamedExports,
harmonyStarExports && harmonyStarExports.slice(), harmonyStarExports && harmonyStarExports.slice(),

View File

@ -7,6 +7,7 @@
const HarmonyLinkingError = require("../HarmonyLinkingError"); const HarmonyLinkingError = require("../HarmonyLinkingError");
const InitFragment = require("../InitFragment"); const InitFragment = require("../InitFragment");
const { UsageState } = require("../ModuleGraph");
const RuntimeGlobals = require("../RuntimeGlobals"); const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template"); const Template = require("../Template");
const { intersect } = require("../util/SetHelpers"); const { intersect } = require("../util/SetHelpers");
@ -21,14 +22,15 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../Module")} Module */ /** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../ModuleGraph").ExportsInfo} ExportsInfo */
/** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/createHash").Hash} Hash */ /** @typedef {import("../util/createHash").Hash} Hash */
/** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"rexport-non-harmony-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */ /** @typedef {"missing"|"unused"|"empty-star"|"reexport-non-harmony-default"|"reexport-named-default"|"reexport-namespace-object"|"reexport-partial-namespace-object"|"reexport-non-harmony-default-strict"|"reexport-fake-namespace-object"|"reexport-non-harmony-undefined"|"normal-reexport"|"dynamic-reexport"} ExportModeType */
const idSymbol = Symbol("HarmonyExportImportedSpecifierDependency.id"); const idsSymbol = Symbol("HarmonyExportImportedSpecifierDependency.ids");
/** @type {Map<string, string>} */ /** @type {Map<string, string[]>} */
const EMPTY_MAP = new Map(); const EMPTY_MAP = new Map();
/** @type {Set<string>} */ /** @type {Set<string>} */
@ -43,8 +45,10 @@ class ExportMode {
this.type = type; this.type = type;
/** @type {string|null} */ /** @type {string|null} */
this.name = null; this.name = null;
/** @type {Map<string, string>} */ /** @type {Map<string, string[]>} */
this.map = EMPTY_MAP; this.map = EMPTY_MAP;
/** @type {ExportsInfo} */
this.partialNamespaceExportsInfo = undefined;
/** @type {Set<string>|null} */ /** @type {Set<string>|null} */
this.ignored = null; this.ignored = null;
/** @type {Set<string>|null} */ /** @type {Set<string>|null} */
@ -60,7 +64,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
/** /**
* @param {string} request the request string * @param {string} request the request string
* @param {number} sourceOrder the order in the original source file * @param {number} sourceOrder the order in the original source file
* @param {string | null} id the requested export name of the imported module * @param {string[]} ids the requested export name of the imported module
* @param {string | null} name the export name of for this module * @param {string | null} name the export name of for this module
* @param {Set<string>} activeExports other named exports in the module * @param {Set<string>} activeExports other named exports in the module
* @param {Iterable<Dependency>} otherStarExports other star exports in the module * @param {Iterable<Dependency>} otherStarExports other star exports in the module
@ -69,7 +73,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
constructor( constructor(
request, request,
sourceOrder, sourceOrder,
id, ids,
name, name,
activeExports, activeExports,
otherStarExports, otherStarExports,
@ -77,32 +81,47 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
) { ) {
super(request, sourceOrder); super(request, sourceOrder);
this.id = id; this.ids = ids;
this.name = name; this.name = name;
this.activeExports = activeExports; this.activeExports = activeExports;
this.otherStarExports = otherStarExports; this.otherStarExports = otherStarExports;
this.strictExportPresence = strictExportPresence; this.strictExportPresence = strictExportPresence;
} }
// TODO webpack 6 remove
get id() {
throw new Error("id was renamed to ids and type changed to string[]");
}
// TODO webpack 6 remove
getId() {
throw new Error("id was renamed to ids and type changed to string[]");
}
// TODO webpack 6 remove
setId() {
throw new Error("id was renamed to ids and type changed to string[]");
}
get type() { get type() {
return "harmony export imported specifier"; return "harmony export imported specifier";
} }
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @returns {string} the imported id * @returns {string[]} the imported id
*/ */
getId(moduleGraph) { getIds(moduleGraph) {
return moduleGraph.getMeta(this)[idSymbol] || this.id; return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
} }
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @param {string} id the imported id * @param {string[]} ids the imported ids
* @returns {void} * @returns {void}
*/ */
setId(moduleGraph, id) { setIds(moduleGraph, ids) {
moduleGraph.getMeta(this)[idSymbol] = id; moduleGraph.getMeta(this)[idsSymbol] = ids;
} }
/** /**
@ -112,9 +131,10 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
*/ */
getMode(moduleGraph, ignoreUnused) { getMode(moduleGraph, ignoreUnused) {
const name = this.name; const name = this.name;
const id = this.getId(moduleGraph); const ids = this.getIds(moduleGraph);
const parentModule = moduleGraph.getParentModule(this); const parentModule = moduleGraph.getParentModule(this);
const importedModule = moduleGraph.getModule(this); const importedModule = moduleGraph.getModule(this);
const exportsInfo = moduleGraph.getExportsInfo(parentModule);
if (!importedModule) { if (!importedModule) {
const mode = new ExportMode("missing"); const mode = new ExportMode("missing");
@ -127,8 +147,8 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
if ( if (
!ignoreUnused && !ignoreUnused &&
(name (name
? parentModule.isExportUsed(moduleGraph, name) === false ? exportsInfo.isExportUsed(name) === UsageState.Unused
: parentModule.isModuleUsed(moduleGraph) === false) : exportsInfo.isUsed() === false)
) { ) {
const mode = new ExportMode("unused"); const mode = new ExportMode("unused");
@ -141,7 +161,12 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
// Special handling for reexporting the default export // Special handling for reexporting the default export
// from non-harmony modules // from non-harmony modules
if (name && id === "default" && importedModule.buildMeta) { if (
name &&
ids.length > 0 &&
ids[0] === "default" &&
importedModule.buildMeta
) {
if (!importedModule.buildMeta.exportsType) { if (!importedModule.buildMeta.exportsType) {
const mode = new ExportMode( const mode = new ExportMode(
strictHarmonyModule strictHarmonyModule
@ -169,15 +194,16 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
// reexporting with a fixed name // reexporting with a fixed name
if (name) { if (name) {
let mode; let mode;
const exportInfo = exportsInfo.getReadOnlyExportInfo(name);
if (id) { if (ids.length > 0) {
// export { name as name } // export { name as name }
if (isNotAHarmonyModule && strictHarmonyModule) { if (isNotAHarmonyModule && strictHarmonyModule) {
mode = new ExportMode("rexport-non-harmony-undefined"); mode = new ExportMode("reexport-non-harmony-undefined");
mode.name = name; mode.name = name;
} else { } else {
mode = new ExportMode("normal-reexport"); mode = new ExportMode("normal-reexport");
mode.map = new Map([[name, id]]); mode.map = new Map([[name, ids]]);
mode.checked = EMPTY_SET; mode.checked = EMPTY_SET;
} }
} else { } else {
@ -185,6 +211,14 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
if (isNotAHarmonyModule && strictHarmonyModule) { if (isNotAHarmonyModule && strictHarmonyModule) {
mode = new ExportMode("reexport-fake-namespace-object"); mode = new ExportMode("reexport-fake-namespace-object");
mode.name = name; mode.name = name;
} else if (
exportInfo.used === UsageState.OnlyPropertiesUsed &&
exportInfo.exportsInfo &&
exportInfo.exportsInfo.otherExportsInfo.used === UsageState.Unused
) {
mode = new ExportMode("reexport-partial-namespace-object");
mode.name = name;
mode.partialNamespaceExportsInfo = exportInfo.exportsInfo;
} else { } else {
mode = new ExportMode("reexport-namespace-object"); mode = new ExportMode("reexport-namespace-object");
mode.name = name; mode.name = name;
@ -198,12 +232,12 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
// Star reexporting // Star reexporting
const exportsInfo = moduleGraph.getExportsInfo(parentModule);
const importedExportsInfo = moduleGraph.getExportsInfo(importedModule); const importedExportsInfo = moduleGraph.getExportsInfo(importedModule);
const noExtraExports = const noExtraExports =
importedExportsInfo.otherExportsInfo.provided === false; importedExportsInfo.otherExportsInfo.provided === false;
const noExtraImports = exportsInfo.otherExportsInfo.used === false; const noExtraImports =
exportsInfo.otherExportsInfo.used === UsageState.Unused;
const ignoredExports = new Set([ const ignoredExports = new Set([
"default", "default",
@ -233,7 +267,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
if (noExtraImports) { if (noExtraImports) {
for (const exportInfo of exportsInfo.exports) { for (const exportInfo of exportsInfo.exports) {
if (ignoredExports.has(exportInfo.name)) continue; if (ignoredExports.has(exportInfo.name)) continue;
if (exportInfo.used !== false) { if (exportInfo.used !== UsageState.Unused) {
imports.add(exportInfo.name); imports.add(exportInfo.name);
} }
} }
@ -272,10 +306,10 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
return mode; return mode;
} }
/** @type {Map<string, string>} */ /** @type {Map<string, string[]>} */
const map = new Map(); const map = new Map();
for (const exportName of merged) { for (const exportName of merged) {
map.set(exportName, exportName); map.set(exportName, [exportName]);
} }
const mode = new ExportMode("normal-reexport"); const mode = new ExportMode("normal-reexport");
@ -310,10 +344,37 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
this.sourceOrder this.sourceOrder
); );
case "reexport-partial-namespace-object": {
const importedNames = [];
const processExportsInfo = (prefix, exportsInfo) => {
for (const exportInfo of exportsInfo.orderedExports) {
if (
exportInfo.used === UsageState.OnlyPropertiesUsed &&
exportInfo.exportsInfo &&
exportInfo.exportsInfo.otherExportsInfo.used === UsageState.Unused
) {
processExportsInfo(
prefix.concat(exportInfo.name),
exportInfo.exportsInfo
);
} else if (exportInfo.used !== UsageState.Unused) {
importedNames.push(prefix.concat(exportInfo.name));
}
}
};
processExportsInfo([], mode.partialNamespaceExportsInfo);
return new DependencyReference(
mode.getModule,
importedNames,
false,
this.sourceOrder
);
}
case "reexport-namespace-object": case "reexport-namespace-object":
case "reexport-non-harmony-default-strict": case "reexport-non-harmony-default-strict":
case "reexport-fake-namespace-object": case "reexport-fake-namespace-object":
case "rexport-non-harmony-undefined": case "reexport-non-harmony-undefined":
return new DependencyReference( return new DependencyReference(
mode.getModule, mode.getModule,
true, true,
@ -395,20 +456,34 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
}; };
case "normal-reexport": case "normal-reexport":
return { return {
exports: Array.from(mode.map.keys()), exports: Array.from(mode.map.keys()).map(name => ({
name,
from: mode.getModule(),
export: mode.map.get(name)
})),
dependencies: [mode.getModule()] dependencies: [mode.getModule()]
}; };
case "reexport-fake-namespace-object": case "reexport-fake-namespace-object":
case "reexport-namespace-object":
case "reexport-non-harmony-default": case "reexport-non-harmony-default":
case "reexport-non-harmony-default-strict": case "reexport-non-harmony-default-strict":
case "rexport-non-harmony-undefined": case "reexport-non-harmony-undefined":
case "reexport-named-default": case "reexport-named-default":
return { return {
exports: [mode.name], exports: [mode.name],
dependencies: [mode.getModule()] dependencies: [mode.getModule()]
}; };
case "reexport-namespace-object":
case "reexport-partial-namespace-object":
return {
exports: [
{
name: mode.name,
from: mode.getModule(),
export: null
}
],
dependencies: [mode.getModule()]
};
default: default:
throw new Error(`Unknown mode ${mode.type}`); throw new Error(`Unknown mode ${mode.type}`);
} }
@ -457,18 +532,19 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
return; return;
} }
const id = this.getId(moduleGraph); const ids = this.getIds(moduleGraph);
if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) { if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) {
// It's not an harmony module // It's not an harmony module
if ( if (
moduleGraph.getParentModule(this).buildMeta.strictHarmonyModule && moduleGraph.getParentModule(this).buildMeta.strictHarmonyModule &&
id !== "default" (ids.length === 0 || ids[0] !== "default")
) { ) {
// In strict harmony modules we only support the default export // In strict harmony modules we only support the default export
const exportName = id const exportName =
? `the named export '${id}'` ids.length > 0
: "the namespace object"; ? `the named export ${ids.map(id => `'${id}'`).join(".")}`
: "the namespace object";
return [ return [
new HarmonyLinkingError( new HarmonyLinkingError(
@ -480,21 +556,21 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
return; return;
} }
if (!id) { if (ids.length === 0) {
return; return;
} }
if (moduleGraph.isExportProvided(importedModule, id) !== false) { if (moduleGraph.isExportProvided(importedModule, ids) !== false) {
// It's provided or we are not sure // It's provided or we are not sure
return; return;
} }
// We are sure that it's not provided // We are sure that it's not provided
const idIsNotNameMessage = const idIsNotNameMessage =
id !== this.name ? ` (reexported as '${this.name}')` : ""; ids.join(".") !== this.name ? ` (reexported as '${this.name}')` : "";
const errorMessage = `"export '${id}'${idIsNotNameMessage} was not found in '${ const errorMessage = `"export ${this.ids
this.userRequest .map(id => `'${id}'`)
}'`; .join(".")}${idIsNotNameMessage} was not found in '${this.userRequest}'`;
return [new HarmonyLinkingError(errorMessage)]; return [new HarmonyLinkingError(errorMessage)];
} }
@ -513,7 +589,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
hash.update(mode.type); hash.update(mode.type);
for (const [k, v] of mode.map) { for (const [k, v] of mode.map) {
hash.update(k); hash.update(k);
hash.update(v); hash.update(v.join());
} }
if (mode.ignored) { if (mode.ignored) {
hash.update("ignored"); hash.update("ignored");
@ -527,7 +603,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
serialize(context) { serialize(context) {
const { write } = context; const { write } = context;
write(this.id); write(this.ids);
write(this.name); write(this.name);
write(this.activeExports); write(this.activeExports);
write(this.otherStarExports); write(this.otherStarExports);
@ -539,7 +615,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
deserialize(context) { deserialize(context) {
const { read } = context; const { read } = context;
this.id = read(); this.ids = read();
this.name = read(); this.name = read();
this.activeExports = read(); this.activeExports = read();
this.otherStarExports = read(); this.otherStarExports = read();
@ -685,7 +761,7 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS
); );
break; break;
case "rexport-non-harmony-undefined": case "reexport-non-harmony-undefined":
initFragments.push( initFragments.push(
new InitFragment( new InitFragment(
"/* harmony reexport (non default export from non-harmony) */ " + "/* harmony reexport (non default export from non-harmony) */ " +
@ -720,6 +796,7 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS
break; break;
case "reexport-namespace-object": case "reexport-namespace-object":
case "reexport-partial-namespace-object":
initFragments.push( initFragments.push(
new InitFragment( new InitFragment(
"/* harmony reexport (module object) */ " + "/* harmony reexport (module object) */ " +

View File

@ -44,9 +44,10 @@ module.exports = class HarmonyImportDependencyParserPlugin {
if (!parser.state.harmonySpecifier) { if (!parser.state.harmonySpecifier) {
parser.state.harmonySpecifier = new Map(); parser.state.harmonySpecifier = new Map();
} }
const ids = id === null ? [] : [id];
parser.state.harmonySpecifier.set(name, { parser.state.harmonySpecifier.set(name, {
source, source,
id, ids,
sourceOrder: parser.state.lastHarmonyImportOrder sourceOrder: parser.state.lastHarmonyImportOrder
}); });
return true; return true;
@ -60,7 +61,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
const dep = new HarmonyImportSpecifierDependency( const dep = new HarmonyImportSpecifierDependency(
settings.source, settings.source,
settings.sourceOrder, settings.sourceOrder,
settings.id, settings.ids,
name, name,
expr.range, expr.range,
this.strictExportPresence this.strictExportPresence
@ -71,45 +72,40 @@ module.exports = class HarmonyImportDependencyParserPlugin {
parser.state.module.addDependency(dep); parser.state.module.addDependency(dep);
return true; return true;
}); });
parser.hooks.expressionAnyMember parser.hooks.expressionMemberChain
.for("imported var") .for("imported var")
.tap("HarmonyImportDependencyParserPlugin", expr => { .tap("HarmonyImportDependencyParserPlugin", (expr, name, members) => {
const name = expr.object.name;
const settings = parser.state.harmonySpecifier.get(name); const settings = parser.state.harmonySpecifier.get(name);
if (settings.id !== null) return false; const ids = settings.ids.concat(members);
const dep = new HarmonyImportSpecifierDependency( const dep = new HarmonyImportSpecifierDependency(
settings.source, settings.source,
settings.sourceOrder, settings.sourceOrder,
expr.property.name || expr.property.value, ids,
name, name,
expr.range, expr.range,
this.strictExportPresence this.strictExportPresence
); );
dep.shorthand = parser.scope.inShorthand;
dep.directImport = false;
dep.loc = expr.loc; dep.loc = expr.loc;
parser.state.module.addDependency(dep); parser.state.module.addDependency(dep);
return true; return true;
}); });
if (this.strictThisContextOnImports) { if (this.strictThisContextOnImports) {
// only in case when we strictly follow the spec we need a special case here // only in case when we strictly follow the spec we need a special case here
parser.hooks.callAnyMember parser.hooks.callMemberChain
.for("imported var") .for("imported var")
.tap("HarmonyImportDependencyParserPlugin", expr => { .tap("HarmonyImportDependencyParserPlugin", (expr, name, members) => {
if (expr.callee.type !== "MemberExpression") return; if (members.length <= 0) return;
if (expr.callee.object.type !== "Identifier") return;
const name = expr.callee.object.name;
const settings = parser.state.harmonySpecifier.get(name); const settings = parser.state.harmonySpecifier.get(name);
if (settings.id !== null) return false; if (settings.ids.length > 0) return false;
const ids = settings.ids.concat(members);
const dep = new HarmonyImportSpecifierDependency( const dep = new HarmonyImportSpecifierDependency(
settings.source, settings.source,
settings.sourceOrder, settings.sourceOrder,
expr.callee.property.name || expr.callee.property.value, ids,
name, name,
expr.callee.range, expr.callee.range,
this.strictExportPresence this.strictExportPresence
); );
dep.shorthand = parser.scope.inShorthand;
dep.directImport = false; dep.directImport = false;
dep.namespaceObjectAsContext = true; dep.namespaceObjectAsContext = true;
dep.loc = expr.callee.loc; dep.loc = expr.callee.loc;
@ -129,7 +125,7 @@ module.exports = class HarmonyImportDependencyParserPlugin {
const dep = new HarmonyImportSpecifierDependency( const dep = new HarmonyImportSpecifierDependency(
settings.source, settings.source,
settings.sourceOrder, settings.sourceOrder,
settings.id, settings.ids,
name, name,
expr.range, expr.range,
this.strictExportPresence this.strictExportPresence

View File

@ -19,12 +19,12 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/createHash").Hash} Hash */ /** @typedef {import("../util/createHash").Hash} Hash */
const idSymbol = Symbol("HarmonyImportSpecifierDependency.id"); const idsSymbol = Symbol("HarmonyImportSpecifierDependency.ids");
class HarmonyImportSpecifierDependency extends HarmonyImportDependency { class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
constructor(request, sourceOrder, id, name, range, strictExportPresence) { constructor(request, sourceOrder, ids, name, range, strictExportPresence) {
super(request, sourceOrder); super(request, sourceOrder);
this.id = id === null ? null : `${id}`; this.ids = ids;
this.name = name; this.name = name;
this.range = range; this.range = range;
this.strictExportPresence = strictExportPresence; this.strictExportPresence = strictExportPresence;
@ -34,25 +34,40 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
this.shorthand = undefined; this.shorthand = undefined;
} }
// TODO webpack 6 remove
get id() {
throw new Error("id was renamed to ids and type changed to string[]");
}
// TODO webpack 6 remove
getId() {
throw new Error("id was renamed to ids and type changed to string[]");
}
// TODO webpack 6 remove
setId() {
throw new Error("id was renamed to ids and type changed to string[]");
}
get type() { get type() {
return "harmony import specifier"; return "harmony import specifier";
} }
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @returns {string} the imported id * @returns {string[]} the imported ids
*/ */
getId(moduleGraph) { getIds(moduleGraph) {
return moduleGraph.getMeta(this)[idSymbol] || this.id; return moduleGraph.getMeta(this)[idsSymbol] || this.ids;
} }
/** /**
* @param {ModuleGraph} moduleGraph the module graph * @param {ModuleGraph} moduleGraph the module graph
* @param {string} id the imported id * @param {string[]} ids the imported ids
* @returns {void} * @returns {void}
*/ */
setId(moduleGraph, id) { setIds(moduleGraph, ids) {
moduleGraph.getMeta(this)[idSymbol] = id; moduleGraph.getMeta(this)[idsSymbol] = ids;
} }
/** /**
@ -63,11 +78,10 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
getReference(moduleGraph) { getReference(moduleGraph) {
const module = moduleGraph.getModule(this); const module = moduleGraph.getModule(this);
if (!module) return null; if (!module) return null;
const ids = this.getIds(moduleGraph);
return new DependencyReference( return new DependencyReference(
() => moduleGraph.getModule(this), () => moduleGraph.getModule(this),
this.getId(moduleGraph) && !this.namespaceObjectAsContext ids.length > 0 && !this.namespaceObjectAsContext ? [ids] : true,
? [this.getId(moduleGraph)]
: true,
false, false,
this.sourceOrder this.sourceOrder
); );
@ -113,16 +127,18 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
return; return;
} }
const ids = this.getIds(moduleGraph);
if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) { if (!importedModule.buildMeta || !importedModule.buildMeta.exportsType) {
// It's not an harmony module // It's not an harmony module
if ( if (
moduleGraph.getParentModule(this).buildMeta.strictHarmonyModule && moduleGraph.getParentModule(this).buildMeta.strictHarmonyModule &&
this.getId(moduleGraph) !== "default" (ids.length === 0 || ids[0] !== "default")
) { ) {
// In strict harmony modules we only support the default export // In strict harmony modules we only support the default export
const exportName = this.getId(moduleGraph) const exportName =
? `the named export '${this.getId(moduleGraph)}'` ids.length > 0
: "the namespace object"; ? `the named export '${ids[0]}'`
: "the namespace object";
return [ return [
new HarmonyLinkingError( new HarmonyLinkingError(
`Can't import ${exportName} from non EcmaScript module (only default export is available)` `Can't import ${exportName} from non EcmaScript module (only default export is available)`
@ -132,22 +148,21 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
return; return;
} }
const id = this.getId(moduleGraph); if (ids.length === 0) {
if (!id) {
return; return;
} }
if (moduleGraph.isExportProvided(importedModule, id) !== false) { if (moduleGraph.isExportProvided(importedModule, ids) !== false) {
// It's provided or we are not sure // It's provided or we are not sure
return; return;
} }
// We are sure that it's not provided // We are sure that it's not provided
const idIsNotNameMessage = const idIsNotNameMessage =
id !== this.name ? ` (imported as '${this.name}')` : ""; ids[0] !== this.name ? ` (imported as '${this.name}')` : "";
const errorMessage = `"export '${this.getId( const errorMessage = `"export ${ids
moduleGraph .map(id => `'${id}'`)
)}'${idIsNotNameMessage} was not found in '${this.userRequest}'`; .join(".")}${idIsNotNameMessage} was not found in '${this.userRequest}'`;
return [new HarmonyLinkingError(errorMessage)]; return [new HarmonyLinkingError(errorMessage)];
} }
@ -169,10 +184,11 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
super.updateHash(hash, chunkGraph); super.updateHash(hash, chunkGraph);
const moduleGraph = chunkGraph.moduleGraph; const moduleGraph = chunkGraph.moduleGraph;
const importedModule = moduleGraph.getModule(this); const importedModule = moduleGraph.getModule(this);
const ids = this.getIds(moduleGraph);
hash.update(ids.join());
if (importedModule) { if (importedModule) {
const id = this.getId(moduleGraph); const exportsInfo = moduleGraph.getExportsInfo(importedModule);
hash.update(id + ""); hash.update(JSON.stringify(exportsInfo.getUsedName(ids)));
hash.update((id && importedModule.getUsedName(moduleGraph, id)) + "");
hash.update( hash.update(
(!importedModule.buildMeta || importedModule.buildMeta.exportsType) + "" (!importedModule.buildMeta || importedModule.buildMeta.exportsType) + ""
); );
@ -181,7 +197,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
serialize(context) { serialize(context) {
const { write } = context; const { write } = context;
write(this.id); write(this.ids);
write(this.name); write(this.name);
write(this.range); write(this.range);
write(this.strictExportPresence); write(this.strictExportPresence);
@ -194,7 +210,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
deserialize(context) { deserialize(context) {
const { read } = context; const { read } = context;
this.id = read(); this.ids = read();
this.name = read(); this.name = read();
this.range = read(); this.range = read();
this.strictExportPresence = read(); this.strictExportPresence = read();
@ -229,11 +245,12 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen
dep, dep,
{ runtimeTemplate, module, moduleGraph, runtimeRequirements } { runtimeTemplate, module, moduleGraph, runtimeRequirements }
) { ) {
const ids = dep.getIds(moduleGraph);
const exportExpr = runtimeTemplate.exportFromImport({ const exportExpr = runtimeTemplate.exportFromImport({
moduleGraph, moduleGraph,
module: moduleGraph.getModule(dep), module: moduleGraph.getModule(dep),
request: dep.request, request: dep.request,
exportName: dep.getId(moduleGraph), exportName: ids,
originModule: module, originModule: module,
asiSafe: dep.shorthand, asiSafe: dep.shorthand,
isCall: dep.call, isCall: dep.call,

View File

@ -11,6 +11,7 @@ const DependencyTemplate = require("../DependencyTemplate");
const InitFragment = require("../InitFragment"); const InitFragment = require("../InitFragment");
const JavascriptParser = require("../JavascriptParser"); const JavascriptParser = require("../JavascriptParser");
const Module = require("../Module"); const Module = require("../Module");
const { UsageState } = require("../ModuleGraph");
const RuntimeGlobals = require("../RuntimeGlobals"); const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template"); const Template = require("../Template");
const DependencyReference = require("../dependencies/DependencyReference"); const DependencyReference = require("../dependencies/DependencyReference");
@ -23,6 +24,7 @@ const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImport
const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency"); const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency");
const createHash = require("../util/createHash"); const createHash = require("../util/createHash");
const contextify = require("../util/identifier").contextify; const contextify = require("../util/identifier").contextify;
const propertyAccess = require("../util/propertyAccess");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
@ -38,23 +40,29 @@ const contextify = require("../util/identifier").contextify;
/** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../WebpackError")} WebpackError */
/** @typedef {import("../util/createHash").Hash} Hash */ /** @typedef {import("../util/createHash").Hash} Hash */
/**
* @typedef {Object} ReexportInfo
* @property {Module} module
* @property {string[]} exportName
* @property {Dependency} dependency
*/
/** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo } ModuleInfo */ /** @typedef {ConcatenatedModuleInfo | ExternalModuleInfo } ModuleInfo */
/** /**
* @typedef {Object} ConcatenatedModuleInfo * @typedef {Object} ConcatenatedModuleInfo
* @property {"concatenated"} type * @property {"concatenated"} type
* @property {Module} module * @property {Module} module
* @property {TODO} index * @property {number} index
* @property {TODO} ast * @property {Object} ast
* @property {TODO} internalSource * @property {Source} internalSource
* @property {TODO} source * @property {ReplaceSource} source
* @property {TODO} globalScope * @property {TODO} globalScope
* @property {TODO} moduleScope * @property {TODO} moduleScope
* @property {TODO} internalNames * @property {TODO} internalNames
* @property {TODO} globalExports * @property {Map<string | true, string>} exportMap
* @property {TODO} exportMap * @property {Map<string, ReexportInfo>} reexportMap
* @property {TODO} reexportMap * @property {boolean} hasNamespaceObject
* @property {TODO} hasNamespaceObject
* @property {TODO} namespaceObjectSource * @property {TODO} namespaceObjectSource
*/ */
@ -62,12 +70,12 @@ const contextify = require("../util/identifier").contextify;
* @typedef {Object} ExternalModuleInfo * @typedef {Object} ExternalModuleInfo
* @property {"external"} type * @property {"external"} type
* @property {Module} module * @property {Module} module
* @property {TODO} index * @property {number} index
* @property {TODO} name * @property {string} name
* @property {TODO} interopNamespaceObjectUsed * @property {boolean} interopNamespaceObjectUsed
* @property {TODO} interopNamespaceObjectName * @property {string} interopNamespaceObjectName
* @property {TODO} interopDefaultAccessUsed * @property {boolean} interopDefaultAccessUsed
* @property {TODO} interopDefaultAccessName * @property {string} interopDefaultAccessName
*/ */
const RESERVED_NAMES = [ const RESERVED_NAMES = [
@ -105,12 +113,27 @@ const RESERVED_NAMES = [
.join(",") .join(",")
.split(","); .split(",");
const arrayEquals = (a, b) => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
};
/** /**
* @typedef {Object} ConcatenationEntry * @typedef {Object} ConcatenationEntry
* @property {"concatenated" | "external"} type * @property {"concatenated" | "external"} type
* @property {Module} module * @property {Module} module
*/ */
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {ConcatenatedModuleInfo} info module info
* @param {Map<Module, ModuleInfo>} moduleToInfoMap moduleToInfoMap
* @param {RequestShortener} requestShortener requestShortener
* @param {boolean} strictHarmonyModule strictHarmonyModule
* @returns {void}
*/
const ensureNsObjSource = ( const ensureNsObjSource = (
moduleGraph, moduleGraph,
info, info,
@ -125,11 +148,12 @@ const ensureNsObjSource = (
`var ${name} = {};`, `var ${name} = {};`,
`${RuntimeGlobals.makeNamespaceObject}(${name});` `${RuntimeGlobals.makeNamespaceObject}(${name});`
]; ];
for (const exportName of moduleGraph.getProvidedExports(info.module)) { const exportsInfo = moduleGraph.getExportsInfo(info.module);
for (const exportInfo of exportsInfo.orderedExports) {
const finalName = getFinalName( const finalName = getFinalName(
moduleGraph, moduleGraph,
info, info,
exportName, [exportInfo.name],
moduleToInfoMap, moduleToInfoMap,
requestShortener, requestShortener,
false, false,
@ -137,7 +161,7 @@ const ensureNsObjSource = (
); );
nsObj.push( nsObj.push(
`${RuntimeGlobals.definePropertyGetter}(${name}, ${JSON.stringify( `${RuntimeGlobals.definePropertyGetter}(${name}, ${JSON.stringify(
exportName exportInfo.getUsedName()
)}, function() { return ${finalName}; });` )}, function() { return ${finalName}; });`
); );
} }
@ -145,6 +169,15 @@ const ensureNsObjSource = (
} }
}; };
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {Module} importedModule module
* @param {ExternalModuleInfo} info module info
* @param {string[]} exportName exportName
* @param {boolean} asCall asCall
* @param {boolean} strictHarmonyModule strictHarmonyModule
* @returns {string} expression to get value of external module
*/
const getExternalImport = ( const getExternalImport = (
moduleGraph, moduleGraph,
importedModule, importedModule,
@ -154,54 +187,75 @@ const getExternalImport = (
strictHarmonyModule strictHarmonyModule
) => { ) => {
const used = const used =
exportName === true || importedModule.getUsedName(moduleGraph, exportName); exportName.length === 0 ||
importedModule.getUsedName(moduleGraph, exportName);
if (!used) return "/* unused reexport */undefined"; if (!used) return "/* unused reexport */undefined";
const comment = const comment = arrayEquals(used, exportName)
used !== exportName ? ` ${Template.toNormalComment(exportName)}` : ""; ? ""
switch (importedModule.buildMeta.exportsType) { : Template.toNormalComment(`${exportName.join(".")}`);
case "named": let exprStart;
if (exportName === "default") { if (exportName.length === 0) {
return info.name; switch (importedModule.buildMeta.exportsType) {
} else if (exportName === true) { case "named":
info.interopNamespaceObjectUsed = true; info.interopNamespaceObjectUsed = true;
return info.interopNamespaceObjectName; exprStart = info.interopNamespaceObjectName;
} else {
break; break;
} case "namespace":
case "namespace": exprStart = info.name;
if (exportName === true) {
return info.name;
} else {
break; break;
} default:
default: if (strictHarmonyModule) {
if (strictHarmonyModule) {
if (exportName === "default") {
return info.name;
} else if (exportName === true) {
info.interopNamespaceObjectUsed = true; info.interopNamespaceObjectUsed = true;
return info.interopNamespaceObjectName; exprStart = info.interopNamespaceObjectName;
} else { break;
return "/* non-default import from non-esm module */undefined";
}
} else {
if (exportName === "default") {
info.interopDefaultAccessUsed = true;
return asCall
? `${info.interopDefaultAccessName}()`
: `${info.interopDefaultAccessName}.a`;
} else if (exportName === true) {
return info.name;
} else { } else {
exprStart = info.name;
break; break;
} }
} }
} else {
switch (importedModule.buildMeta.exportsType) {
case "named":
if (exportName[0] === "default") {
exprStart = info.name;
}
break;
default:
if (strictHarmonyModule) {
if (exportName[0] === "default") {
exprStart = info.name;
} else {
exprStart = "/* non-default import from non-esm module */undefined";
}
} else {
if (exportName[0] === "default") {
info.interopDefaultAccessUsed = true;
exprStart = asCall
? `${info.interopDefaultAccessName}()`
: `${info.interopDefaultAccessName}.a`;
}
}
break;
}
} }
const reference = `${info.name}[${JSON.stringify(used)}${comment}]`; if (exprStart) {
if (asCall) return `Object(${reference})`; return `${exprStart}${propertyAccess(exportName, 1)}`;
return reference; }
const reference = `${info.name}${comment}${propertyAccess(used)}`;
return asCall ? `Object(${reference})` : reference;
}; };
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {ModuleInfo} info module info
* @param {string[]} exportName exportName
* @param {Map<Module, ModuleInfo>} moduleToInfoMap moduleToInfoMap
* @param {RequestShortener} requestShortener the request shortener
* @param {boolean} asCall asCall
* @param {boolean} strictHarmonyModule strictHarmonyModule
* @param {Set<ReexportInfo>} alreadyVisited alreadyVisited
* @returns {string} the final name
*/
const getFinalName = ( const getFinalName = (
moduleGraph, moduleGraph,
info, info,
@ -214,21 +268,25 @@ const getFinalName = (
) => { ) => {
switch (info.type) { switch (info.type) {
case "concatenated": { case "concatenated": {
const directExport = info.exportMap.get(exportName); if (exportName.length === 0) {
ensureNsObjSource(
moduleGraph,
info,
moduleToInfoMap,
requestShortener,
strictHarmonyModule
);
return info.internalNames.get(info.exportMap.get(true));
}
const exportId = exportName[0];
const directExport = info.exportMap.get(exportId);
const exportsInfo = moduleGraph.getExportsInfo(info.module);
if (directExport) { if (directExport) {
if (exportName === true) { if (exportsInfo.isExportUsed(exportName) === UsageState.Unused) {
ensureNsObjSource( return `/* unused export */ undefined${propertyAccess(
moduleGraph, exportName,
info, 1
moduleToInfoMap, )}`;
requestShortener,
strictHarmonyModule
);
} else if (!info.module.isExportUsed(moduleGraph, exportName)) {
return "/* unused export */ undefined";
}
if (info.globalExports.has(directExport)) {
return directExport;
} }
const name = info.internalNames.get(directExport); const name = info.internalNames.get(directExport);
if (!name) { if (!name) {
@ -238,23 +296,23 @@ const getFinalName = (
)}" has no internal name` )}" has no internal name`
); );
} }
return name; return `${name}${propertyAccess(exportName, 1)}`;
} }
const reexport = info.reexportMap.get(exportName); const reexport = info.reexportMap.get(exportId);
if (reexport) { if (reexport) {
if (alreadyVisited.has(reexport)) { if (alreadyVisited.has(reexport)) {
throw new Error( throw new Error(
`Circular reexports ${Array.from( `Circular reexports ${Array.from(
alreadyVisited, alreadyVisited,
e => e =>
`"${e.module.readableIdentifier(requestShortener)}".${ `"${e.module.readableIdentifier(
e.exportName requestShortener
}` )}".${e.exportName.join(".")}`
).join( ).join(
" --> " " --> "
)} -(circular)-> "${reexport.module.readableIdentifier( )} -(circular)-> "${reexport.module.readableIdentifier(
requestShortener requestShortener
)}".${reexport.exportName}` )}".${reexport.exportName.join(".")}`
); );
} }
alreadyVisited.add(reexport); alreadyVisited.add(reexport);
@ -264,7 +322,7 @@ const getFinalName = (
return getFinalName( return getFinalName(
moduleGraph, moduleGraph,
refInfo, refInfo,
reexport.exportName, [...reexport.exportName, ...exportName.slice(1)],
moduleToInfoMap, moduleToInfoMap,
requestShortener, requestShortener,
asCall, asCall,
@ -281,7 +339,10 @@ const getFinalName = (
.filter(name => name !== true) .filter(name => name !== true)
.join(" ")}, ` + .join(" ")}, ` +
`known reexports: ${Array.from(info.reexportMap.keys()).join(" ")})`; `known reexports: ${Array.from(info.reexportMap.keys()).join(" ")})`;
return `${Template.toNormalComment(problem)} undefined`; return `${Template.toNormalComment(problem)} undefined${propertyAccess(
exportName,
1
)}`;
} }
case "external": { case "external": {
const importedModule = info.module; const importedModule = info.module;
@ -700,17 +761,6 @@ class ConcatenatedModule extends Module {
} }
} }
} }
// add exported globals
const variables = new Set();
for (const variable of info.moduleScope.variables) {
variables.add(variable.name);
}
for (const [, variable] of info.exportMap) {
if (!variables.has(variable)) {
info.globalExports.add(variable);
}
}
} }
} }
@ -823,10 +873,12 @@ class ConcatenatedModule extends Module {
const referencedModule = modulesWithInfo[+match[1]]; const referencedModule = modulesWithInfo[+match[1]];
let exportName; let exportName;
if (match[2] === "ns") { if (match[2] === "ns") {
exportName = true; exportName = [];
} else { } else {
const exportData = match[2]; const exportData = match[2];
exportName = Buffer.from(exportData, "hex").toString("utf-8"); exportName = JSON.parse(
Buffer.from(exportData, "hex").toString("utf-8")
);
} }
const asCall = !!match[3]; const asCall = !!match[3];
const strictHarmonyModule = !!match[4]; const strictHarmonyModule = !!match[4];
@ -850,7 +902,10 @@ class ConcatenatedModule extends Module {
const result = new ConcatSource(); const result = new ConcatSource();
// add harmony compatibility flag (must be first because of possible circular dependencies) // add harmony compatibility flag (must be first because of possible circular dependencies)
if (moduleGraph.getExportsInfo(this).otherExportsInfo.used !== false) { if (
moduleGraph.getExportsInfo(this).otherExportsInfo.used !==
UsageState.Unused
) {
result.add( result.add(
runtimeTemplate.defineEsModuleFlagStatement({ runtimeTemplate.defineEsModuleFlagStatement({
exportsArgument: this.exportsArgument, exportsArgument: this.exportsArgument,
@ -985,7 +1040,9 @@ class ConcatenatedModule extends Module {
let result; let result;
switch (info.type) { switch (info.type) {
case "concatenated": { case "concatenated": {
/** @type {Map<string | true, string>} */
const exportMap = new Map(); const exportMap = new Map();
/** @type {Map<string, ReexportInfo>} */
const reexportMap = new Map(); const reexportMap = new Map();
for (const dep of info.module.dependencies) { for (const dep of info.module.dependencies) {
if (dep instanceof HarmonyExportSpecifierDependency) { if (dep instanceof HarmonyExportSpecifierDependency) {
@ -1000,21 +1057,13 @@ class ConcatenatedModule extends Module {
dep instanceof HarmonyExportImportedSpecifierDependency dep instanceof HarmonyExportImportedSpecifierDependency
) { ) {
const exportName = dep.name; const exportName = dep.name;
const importName = dep.getId(moduleGraph); const importNames = dep.getIds(moduleGraph);
const importedModule = moduleGraph.getModule(dep); const importedModule = moduleGraph.getModule(dep);
if (exportName && importName) { if (exportName) {
if (!reexportMap.has(exportName)) { if (!reexportMap.has(exportName)) {
reexportMap.set(exportName, { reexportMap.set(exportName, {
module: importedModule, module: importedModule,
exportName: importName, exportName: importNames,
dependency: dep
});
}
} else if (exportName) {
if (!reexportMap.has(exportName)) {
reexportMap.set(exportName, {
module: importedModule,
exportName: true,
dependency: dep dependency: dep
}); });
} }
@ -1030,7 +1079,7 @@ class ConcatenatedModule extends Module {
if (!reexportMap.has(name)) { if (!reexportMap.has(name)) {
reexportMap.set(name, { reexportMap.set(name, {
module: importedModule, module: importedModule,
exportName: name, exportName: [name],
dependency: dep dependency: dep
}); });
} }
@ -1049,7 +1098,6 @@ class ConcatenatedModule extends Module {
globalScope: undefined, globalScope: undefined,
moduleScope: undefined, moduleScope: undefined,
internalNames: new Map(), internalNames: new Map(),
globalExports: new Set(),
exportMap: exportMap, exportMap: exportMap,
reexportMap: reexportMap, reexportMap: reexportMap,
hasNamespaceObject: false, hasNamespaceObject: false,
@ -1269,15 +1317,17 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate extends DependencyTem
const strictFlag = parentModule.buildMeta.strictHarmonyModule const strictFlag = parentModule.buildMeta.strictHarmonyModule
? "_strict" ? "_strict"
: ""; : "";
const id = dep.getId(moduleGraph); const ids = dep.getIds(moduleGraph);
if (id === null) { if (ids.length === 0) {
content = `__WEBPACK_MODULE_REFERENCE__${info.index}_ns${strictFlag}__`; content = `__WEBPACK_MODULE_REFERENCE__${info.index}_ns${strictFlag}__`;
} else if (dep.namespaceObjectAsContext) { } else if (dep.namespaceObjectAsContext && ids.length === 1) {
content = `__WEBPACK_MODULE_REFERENCE__${ content = `__WEBPACK_MODULE_REFERENCE__${
info.index info.index
}_ns${strictFlag}__[${JSON.stringify(id)}]`; }_ns${strictFlag}__${propertyAccess(ids)}`;
} else { } else {
const exportData = Buffer.from(id, "utf-8").toString("hex"); const exportData = Buffer.from(JSON.stringify(ids), "utf-8").toString(
"hex"
);
content = `__WEBPACK_MODULE_REFERENCE__${ content = `__WEBPACK_MODULE_REFERENCE__${
info.index info.index
}_${exportData}${callFlag}${strictFlag}__`; }_${exportData}${callFlag}${strictFlag}__`;
@ -1398,7 +1448,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
/** /**
* @typedef {Object} GetExportsResultItem * @typedef {Object} GetExportsResultItem
* @property {string} name * @property {string} name
* @property {string | true} id * @property {string[]} ids
*/ */
/** /**
@ -1408,13 +1458,13 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
*/ */
getExports(dep, { moduleGraph }) { getExports(dep, { moduleGraph }) {
const importModule = moduleGraph.getModule(dep); const importModule = moduleGraph.getModule(dep);
const id = dep.getId(moduleGraph); const ids = dep.getIds(moduleGraph);
if (id) { if (ids.length > 0) {
// export { named } from "module" // export { named } from "module"
return [ return [
{ {
name: dep.name, name: dep.name,
id ids
} }
]; ];
} }
@ -1423,7 +1473,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
return [ return [
{ {
name: dep.name, name: dep.name,
id: true ids: []
} }
]; ];
} }
@ -1435,7 +1485,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
.map(exp => { .map(exp => {
return { return {
name: exp, name: exp,
id: exp ids: [exp]
}; };
}); });
} }
@ -1465,7 +1515,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
if (!used) { if (!used) {
initFragments.push( initFragments.push(
new InitFragment( new InitFragment(
`/* unused concated harmony import ${dep.name} */\n`, `/* unused concated harmony import ${def.name} */\n`,
InitFragment.STAGE_HARMONY_EXPORTS, InitFragment.STAGE_HARMONY_EXPORTS,
1 1
) )
@ -1476,12 +1526,15 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate extends Depen
const strictFlag = module.buildMeta.strictHarmonyModule const strictFlag = module.buildMeta.strictHarmonyModule
? "_strict" ? "_strict"
: ""; : "";
if (def.id === true) { if (def.ids.length === 0) {
finalName = `__WEBPACK_MODULE_REFERENCE__${ finalName = `__WEBPACK_MODULE_REFERENCE__${
info.index info.index
}_ns${strictFlag}__`; }_ns${strictFlag}__`;
} else { } else {
const exportData = Buffer.from(def.id, "utf-8").toString("hex"); const exportData = Buffer.from(
JSON.stringify(def.ids),
"utf-8"
).toString("hex");
finalName = `__WEBPACK_MODULE_REFERENCE__${ finalName = `__WEBPACK_MODULE_REFERENCE__${
info.index info.index
}_${exportData}${strictFlag}__`; }_${exportData}${strictFlag}__`;

View File

@ -104,11 +104,11 @@ class SideEffectsFlagPlugin {
if (!map) { if (!map) {
reexportMaps.set(module, (map = new Map())); reexportMaps.set(module, (map = new Map()));
} }
for (const [key, id] of mode.map) { for (const [key, ids] of mode.map) {
if (!mode.checked.has(key)) { if (ids.length > 0 && !mode.checked.has(key)) {
map.set(key, { map.set(key, {
module: mode.getModule(), module: mode.getModule(),
exportName: id exportName: ids[0]
}); });
} }
} }
@ -147,15 +147,24 @@ class SideEffectsFlagPlugin {
(dep instanceof HarmonyImportSpecifierDependency && (dep instanceof HarmonyImportSpecifierDependency &&
!dep.namespaceObjectAsContext) !dep.namespaceObjectAsContext)
) { ) {
const mapping = map.get(dep.id); // TODO improve for nested imports
if (mapping) { const ids = dep.getIds(moduleGraph);
moduleGraph.updateModule(dep, mapping.module); if (ids.length > 0) {
moduleGraph.addExplanation( const mapping = map.get(ids[0]);
dep, if (mapping) {
"(skipped side-effect-free modules)" moduleGraph.updateModule(dep, mapping.module);
); moduleGraph.addExplanation(
dep.setId(moduleGraph, mapping.exportName); dep,
continue; "(skipped side-effect-free modules)"
);
dep.setIds(
moduleGraph,
mapping.exportName
? [mapping.exportName, ...ids.slice(1)]
: ids.slice(1)
);
continue;
}
} }
} }
} }

View File

@ -0,0 +1,25 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const SAFE_IDENTIFIER = /^[_a-zA-Z$][_a-zA-z$0-9]*$/;
const propertyAccess = (properties, start = 0) => {
let str = "";
for (let i = start; i < properties.length; i++) {
const p = properties[i];
if (`${+p}` === p) {
str += `[${p}]`;
} else if (SAFE_IDENTIFIER.test(p)) {
str += `.${p}`;
} else {
str += `[${JSON.stringify(p)}]`;
}
}
return str;
};
module.exports = propertyAccess;

View File

@ -47,7 +47,10 @@ class WasmFinalizeExportsPlugin {
const importedNames = ref.importedNames; const importedNames = ref.importedNames;
if (Array.isArray(importedNames)) { if (Array.isArray(importedNames)) {
importedNames.forEach(name => { importedNames.forEach(nameOrNames => {
const name = Array.isArray(nameOrNames)
? nameOrNames[0]
: nameOrNames;
// 3. and uses a func with an incompatible JS signature // 3. and uses a func with an incompatible JS signature
if ( if (
Object.prototype.hasOwnProperty.call( Object.prototype.hasOwnProperty.call(

View File

@ -7,6 +7,7 @@
const { RawSource } = require("webpack-sources"); const { RawSource } = require("webpack-sources");
const Generator = require("../Generator"); const Generator = require("../Generator");
const { UsageState } = require("../ModuleGraph");
const RuntimeGlobals = require("../RuntimeGlobals"); const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template"); const Template = require("../Template");
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency"); const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
@ -148,12 +149,13 @@ class WebAssemblyJavascriptGenerator extends Generator {
); );
const copyAllExports = const copyAllExports =
exportsInfo.otherExportsInfo.used === false && !needExportsCopy; exportsInfo.otherExportsInfo.used === UsageState.Unused &&
!needExportsCopy;
// need these globals // need these globals
runtimeRequirements.add(RuntimeGlobals.module); runtimeRequirements.add(RuntimeGlobals.module);
runtimeRequirements.add(RuntimeGlobals.wasmInstances); runtimeRequirements.add(RuntimeGlobals.wasmInstances);
if (exportsInfo.otherExportsInfo.used !== false) { if (exportsInfo.otherExportsInfo.used !== UsageState.Unused) {
runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject); runtimeRequirements.add(RuntimeGlobals.makeNamespaceObject);
runtimeRequirements.add(RuntimeGlobals.exports); runtimeRequirements.add(RuntimeGlobals.exports);
} }
@ -170,7 +172,7 @@ class WebAssemblyJavascriptGenerator extends Generator {
module.moduleArgument module.moduleArgument
}.i];`, }.i];`,
exportsInfo.otherExportsInfo.used !== false exportsInfo.otherExportsInfo.used !== UsageState.Unused
? `${RuntimeGlobals.makeNamespaceObject}(${module.exportsArgument});` ? `${RuntimeGlobals.makeNamespaceObject}(${module.exportsArgument});`
: "", : "",

View File

@ -430,7 +430,7 @@ Child all:
`; `;
exports[`StatsTestCases should print correct stats for chunk-module-id-range 1`] = ` exports[`StatsTestCases should print correct stats for chunk-module-id-range 1`] = `
"Hash: 72e3b32fcbbc550ae041 "Hash: 4b7df25b6465c464acfe
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
PublicPath: (none) PublicPath: (none)
@ -503,10 +503,10 @@ Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
PublicPath: (none) PublicPath: (none)
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
b_js.bundle.js 370 bytes {b_js} [emitted] b_js.bundle.js 364 bytes {b_js} [emitted]
bundle.js 8.23 KiB {main} [emitted] main bundle.js 8.22 KiB {main} [emitted] main
c_js.bundle.js 579 bytes {c_js} [emitted] c_js.bundle.js 573 bytes {c_js} [emitted]
d_js-e_js.bundle.js 772 bytes {d_js-e_js} [emitted] d_js-e_js.bundle.js 760 bytes {d_js-e_js} [emitted]
Entrypoint main = bundle.js Entrypoint main = bundle.js
chunk {b_js} b_js.bundle.js 22 bytes <{main}> [rendered] chunk {b_js} b_js.bundle.js 22 bytes <{main}> [rendered]
> ./b [./index.js] ./index.js 2:0-16 > ./b [./index.js] ./index.js 2:0-16
@ -618,26 +618,26 @@ Entrypoint entry-1 = vendor-1.js entry-1.js
`; `;
exports[`StatsTestCases should print correct stats for commons-plugin-issue-4980 1`] = ` exports[`StatsTestCases should print correct stats for commons-plugin-issue-4980 1`] = `
"Hash: 53f13412753ee8bb3d387595bab0743d3d05a312 "Hash: ada681c866c95e0132eff2c9453ed201db5580c2
Child Child
Hash: 53f13412753ee8bb3d38 Hash: ada681c866c95e0132ef
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
app.269cd87fa0cbcfa98d1b.js 6.65 KiB {143} [emitted] app app.72745ea3abc585e103e5.js 6.69 KiB {143} [emitted] app
vendor.37b166c3b85bfb1caf5f.js 616 bytes {736} [emitted] vendor vendor.22698e25646290b0dcc1.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.37b166c3b85bfb1caf5f.js app.269cd87fa0cbcfa98d1b.js Entrypoint app = vendor.22698e25646290b0dcc1.js app.72745ea3abc585e103e5.js
[117] ./entry-1.js + 2 modules 190 bytes {143} [built] [117] ./entry-1.js + 2 modules 190 bytes {143} [built]
[381] ./constants.js 87 bytes {736} [built] [381] ./constants.js 87 bytes {736} [built]
+ 4 hidden modules + 4 hidden modules
Child Child
Hash: 7595bab0743d3d05a312 Hash: f2c9453ed201db5580c2
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
app.3ec38d64835ee4418bb3.js 6.67 KiB {143} [emitted] app app.eee68844dbd56e9ed525.js 6.7 KiB {143} [emitted] app
vendor.37b166c3b85bfb1caf5f.js 616 bytes {736} [emitted] vendor vendor.22698e25646290b0dcc1.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.37b166c3b85bfb1caf5f.js app.3ec38d64835ee4418bb3.js Entrypoint app = vendor.22698e25646290b0dcc1.js app.eee68844dbd56e9ed525.js
[381] ./constants.js 87 bytes {736} [built] [381] ./constants.js 87 bytes {736} [built]
[655] ./entry-2.js + 2 modules 197 bytes {143} [built] [655] ./entry-2.js + 2 modules 197 bytes {143} [built]
+ 4 hidden modules" + 4 hidden modules"
@ -1087,7 +1087,7 @@ chunk {trees} trees.js (trees) 71 bytes [rendered]
`; `;
exports[`StatsTestCases should print correct stats for import-context-filter 1`] = ` exports[`StatsTestCases should print correct stats for import-context-filter 1`] = `
"Hash: 49f607faddf9699cbef8 "Hash: 99ab54650501715e52b3
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
@ -1453,7 +1453,7 @@ If you don't want to include a polyfill, you can use an empty module like this:
`; `;
exports[`StatsTestCases should print correct stats for module-reasons 1`] = ` exports[`StatsTestCases should print correct stats for module-reasons 1`] = `
"Hash: 9e3985f832f4d566fdbb "Hash: 9757fb3da1f7b8f77bbb
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
@ -1476,7 +1476,7 @@ Entrypoint main = main.js
[10] ./index.js 19 bytes {179} [built] [10] ./index.js 19 bytes {179} [built]
[195] ./not-existing.js 26 bytes {179} [built] [195] ./not-existing.js 26 bytes {179} [built]
[587] ./inner.js 53 bytes {179} [built] [587] ./inner.js 53 bytes {179} [built]
[636] ./parse-error.js 167 bytes {179} [built] [failed] [1 error] [636] ./parse-error.js 27 bytes {179} [built] [failed] [1 error]
ERROR in ./not-existing.js 1:0-25 ERROR in ./not-existing.js 1:0-25
Module not found: Error: Can't resolve 'does-not-exist' in 'Xdir/module-trace-disabled-in-error' Module not found: Error: Can't resolve 'does-not-exist' in 'Xdir/module-trace-disabled-in-error'
@ -1501,7 +1501,7 @@ Entrypoint main = main.js
[10] ./index.js 19 bytes {179} [built] [10] ./index.js 19 bytes {179} [built]
[195] ./not-existing.js 26 bytes {179} [built] [195] ./not-existing.js 26 bytes {179} [built]
[587] ./inner.js 53 bytes {179} [built] [587] ./inner.js 53 bytes {179} [built]
[636] ./parse-error.js 167 bytes {179} [built] [failed] [1 error] [636] ./parse-error.js 27 bytes {179} [built] [failed] [1 error]
ERROR in ./not-existing.js 1:0-25 ERROR in ./not-existing.js 1:0-25
Module not found: Error: Can't resolve 'does-not-exist' in 'Xdir/module-trace-enabled-in-error' Module not found: Error: Can't resolve 'does-not-exist' in 'Xdir/module-trace-enabled-in-error'
@ -1686,7 +1686,7 @@ exports[`StatsTestCases should print correct stats for parse-error 1`] = `
main.js 4.34 KiB {179} main main.js 4.34 KiB {179} main
Entrypoint main = main.js Entrypoint main = main.js
[535] ./index.js + 1 modules 35 bytes {179} [built] [535] ./index.js + 1 modules 35 bytes {179} [built]
[996] ./b.js 169 bytes {179} [built] [failed] [1 error] [996] ./b.js 55 bytes {179} [built] [failed] [1 error]
+ 4 hidden modules + 4 hidden modules
ERROR in ./b.js 6:7 ERROR in ./b.js 6:7
@ -2289,7 +2289,7 @@ Entrypoint e2 = runtime.js e2.js"
`; `;
exports[`StatsTestCases should print correct stats for scope-hoisting-bailouts 1`] = ` exports[`StatsTestCases should print correct stats for scope-hoisting-bailouts 1`] = `
"Hash: ac1889126d1918a36204 "Hash: b4a9cfa089ce3732efc5
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint index = index.js Entrypoint index = index.js
@ -2318,9 +2318,9 @@ Entrypoint entry = entry.js
`; `;
exports[`StatsTestCases should print correct stats for scope-hoisting-multi 1`] = ` exports[`StatsTestCases should print correct stats for scope-hoisting-multi 1`] = `
"Hash: 0b46c17cd9c6ad0fbfb2a8733d1161800e5d8cb5 "Hash: d4c3cd048bdc678bb92437745aea03a16f764c79
Child Child
Hash: 0b46c17cd9c6ad0fbfb2 Hash: d4c3cd048bdc678bb924
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint first = vendor.js first.js Entrypoint first = vendor.js first.js
@ -2338,7 +2338,7 @@ Child
[965] ./vendor.js 25 bytes {736} [built] [965] ./vendor.js 25 bytes {736} [built]
+ 10 hidden modules + 10 hidden modules
Child Child
Hash: a8733d1161800e5d8cb5 Hash: 37745aea03a16f764c79
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint first = vendor.js first.js Entrypoint first = vendor.js first.js
@ -2365,11 +2365,11 @@ Child
`; `;
exports[`StatsTestCases should print correct stats for side-effects-issue-7428 1`] = ` exports[`StatsTestCases should print correct stats for side-effects-issue-7428 1`] = `
"Hash: 8d106e0a6c9f100fb26f "Hash: 8871bd720c1e20111581
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
1.js 481 bytes {1} [emitted] 1.js 478 bytes {1} [emitted]
main.js 10.1 KiB {0} [emitted] main main.js 10.1 KiB {0} [emitted] main
Entrypoint main = main.js Entrypoint main = main.js
[0] ./main.js + 1 modules 231 bytes {0} [built] [0] ./main.js + 1 modules 231 bytes {0} [built]
@ -2414,7 +2414,7 @@ Entrypoint main = main.js
`; `;
exports[`StatsTestCases should print correct stats for side-effects-simple-unused 1`] = ` exports[`StatsTestCases should print correct stats for side-effects-simple-unused 1`] = `
"Hash: aabd6d1544eb169e22e5 "Hash: 589af6828de7c047d0c5
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
@ -2450,7 +2450,7 @@ exports[`StatsTestCases should print correct stats for simple 1`] = `
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
bundle.js 1.56 KiB {main} [emitted] main bundle.js 1.55 KiB {main} [emitted] main
Entrypoint main = bundle.js Entrypoint main = bundle.js
[./index.js] 1 bytes {main} [built]" [./index.js] 1 bytes {main} [built]"
`; `;
@ -3446,7 +3446,7 @@ chunk {794} default/async-a.js (async-a) 134 bytes <{179}> [rendered]
`; `;
exports[`StatsTestCases should print correct stats for tree-shaking 1`] = ` exports[`StatsTestCases should print correct stats for tree-shaking 1`] = `
"Hash: a74d3a7695fab0e2d9aa "Hash: 9f0d95b08ca5afeee1fe
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
@ -3507,28 +3507,28 @@ WARNING in Terser Plugin: Dropping unused function someUnRemoteUsedFunction5 [./
`; `;
exports[`StatsTestCases should print correct stats for wasm-explorer-examples-sync 1`] = ` exports[`StatsTestCases should print correct stats for wasm-explorer-examples-sync 1`] = `
"Hash: e6c683371cb4c4354398 "Hash: 48cd6bb29a775ac0f234
Time: Xms Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names Asset Size Chunks Chunk Names
00885349ebe3dd903faa.module.wasm 125 bytes {325} [emitted] 10d1272d7cf8dafe2f3a.module.wasm 225 bytes {780} [emitted]
230.bundle.js 430 bytes {230} [emitted] 230.bundle.js 430 bytes {230} [emitted]
325.bundle.js 3.93 KiB {325} [emitted] 325.bundle.js 3.9 KiB {325} [emitted]
526.bundle.js 329 bytes {526} [emitted] 526.bundle.js 329 bytes {526} [emitted]
780.bundle.js 842 bytes {780} [emitted] 780.bundle.js 842 bytes {780} [emitted]
7d7a509c17179e42766a.module.wasm 94 bytes {325} [emitted] 7eee906d414e0947ebbf.module.wasm 132 bytes {780} [emitted]
94b864234c573663c221.module.wasm 94 bytes {325} [emitted]
99.bundle.js 428 bytes {99} [emitted] 99.bundle.js 428 bytes {99} [emitted]
a160192b41d0463ac226.module.wasm 131 bytes {230} [emitted]
bundle.js 13.5 KiB {520} [emitted] main-1df31ce3 bundle.js 13.5 KiB {520} [emitted] main-1df31ce3
cfd5ebf998e67f7103a2.module.wasm 510 bytes {99} [emitted] d95036d90a8b92bc2692.module.wasm 510 bytes {99} [emitted]
e94fc8ba836bfd2c6038.module.wasm 131 bytes {230} [emitted] ff9c5233b4dbdf834f6f.module.wasm 125 bytes {325} [emitted]
f8f99fabd0c63ad1c730.module.wasm 225 bytes {780} [emitted]
fea68b3453f2f8fdfeed.module.wasm 132 bytes {780} [emitted]
Entrypoint main = bundle.js Entrypoint main = bundle.js
chunk {99} 99.bundle.js, cfd5ebf998e67f7103a2.module.wasm 100 bytes (javascript) 531 bytes (webassembly) [rendered] chunk {99} 99.bundle.js, d95036d90a8b92bc2692.module.wasm 100 bytes (javascript) 531 bytes (webassembly) [rendered]
[99] ./duff.wasm 100 bytes (javascript) 531 bytes (webassembly) {99} [built] [99] ./duff.wasm 100 bytes (javascript) 531 bytes (webassembly) {99} [built]
chunk {230} 230.bundle.js, e94fc8ba836bfd2c6038.module.wasm 100 bytes (javascript) 156 bytes (webassembly) [rendered] chunk {230} 230.bundle.js, a160192b41d0463ac226.module.wasm 100 bytes (javascript) 156 bytes (webassembly) [rendered]
[230] ./Q_rsqrt.wasm 100 bytes (javascript) 156 bytes (webassembly) {230} [built] [230] ./Q_rsqrt.wasm 100 bytes (javascript) 156 bytes (webassembly) {230} [built]
chunk {325} 325.bundle.js, 7d7a509c17179e42766a.module.wasm, 00885349ebe3dd903faa.module.wasm 1.6 KiB (javascript) 274 bytes (webassembly) [rendered] chunk {325} 325.bundle.js, 94b864234c573663c221.module.wasm, ff9c5233b4dbdf834f6f.module.wasm 1.6 KiB (javascript) 274 bytes (webassembly) [rendered]
[287] ./popcnt.wasm 100 bytes (javascript) 120 bytes (webassembly) {325} [built] [287] ./popcnt.wasm 100 bytes (javascript) 120 bytes (webassembly) {325} [built]
[325] ./tests.js 1.4 KiB {325} [built] [325] ./tests.js 1.4 KiB {325} [built]
[819] ./testFunction.wasm 100 bytes (javascript) 154 bytes (webassembly) {325} [built] [819] ./testFunction.wasm 100 bytes (javascript) 154 bytes (webassembly) {325} [built]
@ -3537,7 +3537,7 @@ chunk {520} bundle.js (main-1df31ce3) 586 bytes (javascript) 6.75 KiB (runtime)
+ 7 hidden chunk modules + 7 hidden chunk modules
chunk {526} 526.bundle.js 34 bytes [rendered] split chunk (cache group: defaultVendors) chunk {526} 526.bundle.js 34 bytes [rendered] split chunk (cache group: defaultVendors)
[526] ./node_modules/env.js 34 bytes {526} [built] [526] ./node_modules/env.js 34 bytes {526} [built]
chunk {780} 780.bundle.js, fea68b3453f2f8fdfeed.module.wasm, f8f99fabd0c63ad1c730.module.wasm 205 bytes (javascript) 444 bytes (webassembly) [rendered] chunk {780} 780.bundle.js, 7eee906d414e0947ebbf.module.wasm, 10d1272d7cf8dafe2f3a.module.wasm 205 bytes (javascript) 444 bytes (webassembly) [rendered]
[143] ./fact.wasm 100 bytes (javascript) 154 bytes (webassembly) {780} [built] [143] ./fact.wasm 100 bytes (javascript) 154 bytes (webassembly) {780} [built]
[151] ./fast-math.wasm 105 bytes (javascript) 290 bytes (webassembly) {780} [built] [151] ./fast-math.wasm 105 bytes (javascript) 290 bytes (webassembly) {780} [built]
[10] ./index.js 586 bytes {520} [built] [10] ./index.js 586 bytes {520} [built]

View File

@ -0,0 +1,8 @@
export let counter = 0;
export const increment = () => {
counter++;
};
export function reset() {
counter = 0;
};
export const unusedExport = 42;

View File

@ -0,0 +1,24 @@
import * as C from "./reexport-namespace";
import { counter } from "./reexport-namespace";
import * as C2 from "./reexport-namespace-again";
it("should allow to reexport namespaces 1", () => {
counter.reset();
expect(counter.counter).toBe(0);
counter.increment();
expect(counter.counter).toBe(1);
});
it("should allow to reexport namespaces 2", () => {
C.counter.reset();
expect(C.counter.counter).toBe(0);
C.counter.increment();
expect(C.counter.counter).toBe(1);
});
it("should allow to reexport namespaces 3", () => {
C2.CC.counter.reset();
expect(C2.CC.counter.counter).toBe(0);
C2.CC.counter.increment();
expect(C2.CC.counter.counter).toBe(1);
});

View File

@ -0,0 +1,2 @@
import * as CC from "./reexport-namespace";
export { CC };

View File

@ -0,0 +1,2 @@
import * as counter from "./counter";
export { counter };

View File

@ -15,10 +15,12 @@ module.exports = {
ref.module && ref.module &&
ref.module.identifier().endsWith("reference.js") && ref.module.identifier().endsWith("reference.js") &&
Array.isArray(ref.importedNames) && Array.isArray(ref.importedNames) &&
ref.importedNames.includes("unused") ref.importedNames.some(
names => names.length === 1 && names[0] === "unused"
)
) { ) {
const newExports = ref.importedNames.filter( const newExports = ref.importedNames.filter(
item => item !== "unused" names => names.length !== 1 || names[0] !== "unused"
); );
return new DependencyReference( return new DependencyReference(
() => ref.module, () => ref.module,

View File

@ -15,10 +15,12 @@ module.exports = {
ref.module && ref.module &&
ref.module.identifier().endsWith("reference.js") && ref.module.identifier().endsWith("reference.js") &&
Array.isArray(ref.importedNames) && Array.isArray(ref.importedNames) &&
ref.importedNames.includes("unused") ref.importedNames.some(
names => names.length === 1 && names[0] === "unused"
)
) { ) {
const newExports = ref.importedNames.filter( const newExports = ref.importedNames.filter(
item => item !== "unused" names => names.length !== 1 || names[0] !== "unused"
); );
return new DependencyReference( return new DependencyReference(
() => ref.module, () => ref.module,

View File

@ -1,14 +1,63 @@
function returnThis() { function returnThis() {
if(typeof this === "undefined") return "undefined"; if (typeof this === "undefined") return "undefined";
return this; return this;
} }
var a = returnThis; var a = returnThis;
var b = returnThis; var b = returnThis;
export { export { a, b };
a,
b export const that = this;
export const returnThisArrow = () => this;
export const returnThisMember = () => this.a;
export class C {
constructor() {
this.x = "bar";
}
foo() {
return this.x;
}
bar(x = this.x) {
return x;
}
}
export const extendThisClass = () => {
return class extends this.Buffer {};
};
export function D() {
this.prop = () => "ok";
}
// See https://github.com/webpack/webpack/issues/6379
export const E = {
x: "bar",
foo(x = this.x) {
return x;
}
};
// See https://github.com/webpack/webpack/issues/6967
export const F = function() {
return this;
}.call("ok");
export function f1(x = this.x) {
return x;
}
export const f2 = function(x = this.x) {
return x;
};
export const f3 = (x = this) => x;
export function G(x) {
this.x = x;
this.getX = (y = this.x) => y;
} }
export default returnThis; export default returnThis;

View File

@ -1,11 +1,45 @@
"use strict"; "use strict";
import d, {a, b as B} from "./abc"; import {extendThisClass, returnThisArrow, returnThisMember, that} from "./abc";
import d, {a, b as B, C as _C, D as _D, E, F, f1, f2, f3, G} from "./abc";
import {bindThis, callThis, applyThis} from "./issue-7213";
import * as abc from "./abc"; import * as abc from "./abc";
function x() { throw new Error("should not be executed"); } it("should have this = undefined on harmony modules", () => {
it("should have this = undefined on imported non-strict functions", function() { expect((typeof that)).toBe("undefined");
expect((typeof abc.that)).toBe("undefined");
expect((typeof returnThisArrow())).toBe("undefined");
expect((typeof abc.returnThisArrow())).toBe("undefined");
expect(function() {
returnThisMember();
}).toThrowError();
expect(function() {
abc.returnThisMember();
}).toThrowError();
expect(function() {
extendThisClass();
}).toThrowError();
});
it("should not break classes and functions", () => {
expect((new _C).foo()).toBe("bar");
expect((new _C).bar()).toBe("bar");
expect((new _D).prop()).toBe("ok");
expect(E.foo()).toBe("bar");
expect(F).toBe("ok");
expect(f1.call({x: "f1"})).toBe("f1");
expect(f2.call({x: "f2"})).toBe("f2");
expect(f3.call("f3")).toBe(undefined);
expect(f3()).toBe(undefined);
expect((new G("ok")).getX()).toBe("ok");
});
function x() {
throw new Error("should not be executed");
}
it("should have this = undefined on imported non-strict functions", () => {
x x
expect(d()).toBe("undefined"); expect(d()).toBe("undefined");
x x
@ -13,17 +47,17 @@ it("should have this = undefined on imported non-strict functions", function() {
x x
expect(B()).toBe("undefined"); expect(B()).toBe("undefined");
x x
expect(abc.a()).toMatchObject({}); expect(abc.a()).toBeTypeOf("object");
x x
var thing = abc.a(); var thing = abc.a();
expect(Object.keys(thing)).toEqual(["a", "b", "default"]); expect(Object.keys(thing)).toEqual(Object.keys(abc));
}); });
import C2, { C } from "./new"; import C2, { C } from "./new";
import * as New from "./new"; import * as New from "./new";
it("should be possible to use new correctly", function() { it("should be possible to use new correctly", () => {
x x
expect(new C()).toEqual({ok: true}); expect(new C()).toEqual({ok: true});
x x
@ -31,3 +65,9 @@ it("should be possible to use new correctly", function() {
x x
expect(new New.C()).toEqual({ok: true}); expect(new New.C()).toEqual({ok: true});
}); });
it("should not break Babel arrow function transform", () => {
expect(bindThis()).toBe(undefined);
expect(callThis).toBe(undefined);
expect(applyThis).toBe(undefined);
});

View File

@ -0,0 +1,20 @@
// This helper is taken from Babel
function _newArrowCheck(innerThis, boundThis) {
if (innerThis !== boundThis) {
throw new TypeError("Cannot instantiate an arrow function");
}
}
let _this = this;
export let bindThis = function() {
_newArrowCheck(this, _this);
return this
}.bind(this);
export let callThis = function() {
return this
}.call(this)
export let applyThis = function() {
return this
}.apply(this)

View File

@ -2,6 +2,5 @@ var webpack = require("../../../../");
module.exports = { module.exports = {
module: { module: {
strictThisContextOnImports: true strictThisContextOnImports: true
}, }
plugins: [new webpack.optimize.ModuleConcatenationPlugin()]
}; };

View File

@ -1,5 +1,8 @@
module.exports = { module.exports = {
module: { module: {
strictThisContextOnImports: true strictThisContextOnImports: true
},
optimization: {
concatenateModules: false
} }
}; };