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 {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
* @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
*/

View File

@ -12,6 +12,7 @@ const Queue = require("./util/Queue");
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */
const getCacheIdentifier = (compilation, module) => {
return `${
@ -79,6 +80,7 @@ class FlagDependencyExportsPlugin {
/** @type {Module} */
let module;
/** @type {ExportsInfo} */
let exportsInfo;
let cacheable = true;
@ -113,12 +115,36 @@ class FlagDependencyExportsPlugin {
}
} else if (Array.isArray(exports)) {
// merge in new exports
for (const exportName of exports) {
const exportInfo = exportsInfo.getExportInfo(exportName);
for (const exportNameOrSpec of exports) {
if (typeof exportNameOrSpec === "string") {
const exportInfo = exportsInfo.getExportInfo(
exportNameOrSpec
);
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;
}
}
}
}
}
// store dependencies

View File

@ -5,6 +5,7 @@
"use strict";
const { UsageState } = require("./ModuleGraph");
const { STAGE_DEFAULT } = require("./OptimizationStages");
const Queue = require("./util/Queue");
@ -12,6 +13,10 @@ const Queue = require("./util/Queue");
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */
const NS_OBJ_USED = [[]];
const NOTHING_USED = [];
class FlagDependencyUsagePlugin {
/**
@ -27,31 +32,60 @@ class FlagDependencyUsagePlugin {
stage: STAGE_DEFAULT
},
modules => {
/** @type {Map<ExportsInfo, Module>} */
const exportInfoToModuleMap = new Map();
/**
*
* @typedef {string[]} StringArray
* @param {Module} module module to process
* @param {boolean | string[]} usedExports list of used exports
* @param {StringArray[]} usedExports list of used exports
* @returns {void}
*/
const processModule = (module, usedExports) => {
const exportsInfo = moduleGraph.getExportsInfo(module);
let changed = false;
if (usedExports === true) {
changed = exportsInfo.setUsedInUnknownWay();
} else if (usedExports) {
for (const exportName of usedExports) {
if (usedExports.length > 0) {
for (const usedExport of usedExports) {
if (usedExport.length === 0) {
if (exportsInfo.setUsedInUnknownWay()) {
queue.enqueue(module);
}
} else {
if (
exportName === "default" &&
usedExport[0] === "default" &&
module.buildMeta.exportsType === "named"
) {
if (exportsInfo.setUsedAsNamedExportType()) {
changed = true;
queue.enqueue(module);
}
} else {
const exportInfo = exportsInfo.getExportInfo(exportName);
if (exportInfo.used !== true) {
exportInfo.used = true;
changed = true;
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,12 +93,10 @@ class FlagDependencyUsagePlugin {
// 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
if (module.factoryMeta.sideEffectFree) return;
changed = exportsInfo.setUsedForSideEffectsOnly();
}
if (changed) {
if (exportsInfo.setUsedForSideEffectsOnly()) {
queue.enqueue(module);
}
}
};
/**
@ -89,11 +121,21 @@ class FlagDependencyUsagePlugin {
if (!reference) return;
const referenceModule = reference.module;
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) {
moduleGraph.getExportsInfo(module).setHasUseInfo();
const exportsInfo = moduleGraph.getExportsInfo(module);
exportInfoToModuleMap.set(exportsInfo, module);
exportsInfo.setHasUseInfo();
}
/** @type {Queue<DependenciesBlock>} */
@ -103,7 +145,7 @@ class FlagDependencyUsagePlugin {
for (const dep of deps) {
const module = moduleGraph.getModule(dep);
if (module) {
processModule(module, true);
processModule(module, NS_OBJ_USED);
}
}
}

View File

@ -27,6 +27,32 @@ const joinIterableWithComma = iterable => {
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 {
constructor({ compilation }) {
this.compilation = compilation;
@ -84,26 +110,7 @@ class FunctionModuleTemplatePlugin {
source.add(" !*** " + reqStr + " ***!\n");
source.add(" \\****" + reqStrStar + "****/\n");
const exportsInfo = moduleGraph.getExportsInfo(module);
for (const exportInfo of exportsInfo.orderedExports) {
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"
);
}
printExportsInfoToSource(source, "", exportsInfo);
source.add(
Template.toComment(
`runtime requirements: ${joinIterableWithComma(

View File

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

View File

@ -7,6 +7,7 @@
const { ConcatSource, RawSource } = require("webpack-sources");
const Generator = require("./Generator");
const { UsageState } = require("./ModuleGraph");
const RuntimeGlobals = require("./RuntimeGlobals");
/** @typedef {import("webpack-sources").Source} Source */
@ -64,7 +65,7 @@ class JsonGenerator extends Generator {
const providedExports = moduleGraph.getProvidedExports(module);
if (
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
const reducedJson = {};

View File

@ -18,6 +18,7 @@ const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./FileSystemInfo")} FileSystemInfo */
/** @typedef {import("./ModuleGraph").UsageStateType} UsageStateType */
/** @typedef {import("./RequestShortener")} RequestShortener */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("./WebpackError")} WebpackError */
@ -385,23 +386,22 @@ class Module extends DependenciesBlock {
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {string} exportName a name of an export
* @returns {boolean} true, if the export is used
* @param {string | string[]} exportName a name of an export
* @returns {UsageStateType} state of the export
*/
isExportUsed(moduleGraph, exportName) {
const exportInfo = moduleGraph.getReadOnlyExportInfo(this, exportName);
return exportInfo.used !== false;
return moduleGraph.getExportsInfo(this).isExportUsed(exportName);
}
// TODO move to ModuleGraph
/**
* @param {ModuleGraph} moduleGraph the module graph
* @param {string} exportName a name of an export
* @returns {string | false} false, when module or referenced export is unused.
* @param {string | string[]} exportName a name of an export
* @returns {string | string[] | false} false, when module or referenced export is unused.
* string, the mangled export name when used.
*/
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}
*/
/** @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 {
constructor() {
/** @type {Map<string, ExportInfo>} */
@ -92,21 +102,21 @@ class ExportsInfo {
setHasUseInfo() {
for (const exportInfo of this._exports.values()) {
if (exportInfo.used === undefined) {
exportInfo.used = false;
if (exportInfo.used === UsageState.NoInfo) {
exportInfo.used = UsageState.Unused;
}
if (exportInfo.canMangleUse === undefined) {
exportInfo.canMangleUse = true;
}
}
if (this._otherExportsInfo.used === undefined) {
this._otherExportsInfo.used = false;
if (this._otherExportsInfo.used === UsageState.NoInfo) {
this._otherExportsInfo.used = UsageState.Unused;
}
if (this._otherExportsInfo.canMangleUse === undefined) {
this._otherExportsInfo.canMangleUse = true;
}
if (this._sideEffectsOnlyInfo.used === undefined) {
this._sideEffectsOnlyInfo.used = false;
if (this._sideEffectsOnlyInfo.used === UsageState.NoInfo) {
this._sideEffectsOnlyInfo.used = UsageState.Unused;
}
}
@ -127,6 +137,20 @@ class ExportsInfo {
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
*/
@ -163,8 +187,8 @@ class ExportsInfo {
changed = true;
}
for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== true && exportInfo.used !== null) {
exportInfo.used = null;
if (exportInfo.used < UsageState.Unknown) {
exportInfo.used = UsageState.Unknown;
changed = true;
}
if (exportInfo.canMangleUse !== false) {
@ -172,11 +196,8 @@ class ExportsInfo {
changed = true;
}
}
if (
this._otherExportsInfo.used !== true &&
this._otherExportsInfo.used !== null
) {
this._otherExportsInfo.used = null;
if (this._otherExportsInfo.used < UsageState.Unknown) {
this._otherExportsInfo.used = UsageState.Unknown;
changed = true;
}
if (this._otherExportsInfo.canMangleUse !== false) {
@ -192,10 +213,10 @@ class ExportsInfo {
this._isUsed = true;
changed = true;
}
this.getExportInfo("default").used = true;
this.getExportInfo("default").used = UsageState.Used;
for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== true && exportInfo.used !== null) {
exportInfo.used = null;
if (exportInfo.used < UsageState.Unknown) {
exportInfo.used = UsageState.Unknown;
changed = true;
}
if (exportInfo.name !== "default" && exportInfo.canMangleUse !== false) {
@ -203,11 +224,8 @@ class ExportsInfo {
changed = true;
}
}
if (
this._otherExportsInfo.used !== true &&
this._otherExportsInfo.used !== null
) {
this._otherExportsInfo.used = null;
if (this._otherExportsInfo.used < UsageState.Unknown) {
this._otherExportsInfo.used = UsageState.Unknown;
changed = true;
}
if (this._otherExportsInfo.canMangleUse !== false) {
@ -218,22 +236,22 @@ class ExportsInfo {
}
setUsedForSideEffectsOnly() {
if (this._sideEffectsOnlyInfo.used === false) {
this._sideEffectsOnlyInfo.used = true;
if (this._sideEffectsOnlyInfo.used === UsageState.Unused) {
this._sideEffectsOnlyInfo.used = UsageState.Used;
return true;
}
return false;
}
isUsed() {
if (this._otherExportsInfo.used !== false) {
if (this._otherExportsInfo.used !== UsageState.Unused) {
return true;
}
if (this._sideEffectsOnlyInfo.used !== false) {
if (this._sideEffectsOnlyInfo.used !== UsageState.Unused) {
return true;
}
for (const exportInfo of this._exports.values()) {
if (exportInfo.used !== false) {
if (exportInfo.used !== UsageState.Unused) {
return true;
}
}
@ -242,30 +260,32 @@ class ExportsInfo {
getUsedExports() {
switch (this._otherExportsInfo.used) {
case undefined:
case UsageState.NoInfo:
return null;
case null:
case UsageState.Unknown:
return true;
case true:
case UsageState.OnlyPropertiesUsed:
case UsageState.Used:
return true;
}
const array = [];
if (!this._exportsAreOrdered) this._sortExports();
for (const exportInfo of this._exports.values()) {
switch (exportInfo.used) {
case undefined:
case UsageState.NoInfo:
return null;
case null:
case UsageState.Unknown:
return true;
case true:
case UsageState.OnlyPropertiesUsed:
case UsageState.Used:
array.push(exportInfo.name);
}
}
if (array.length === 0) {
switch (this._sideEffectsOnlyInfo.used) {
case undefined:
case UsageState.NoInfo:
return null;
case false:
case UsageState.Unused:
return false;
}
}
@ -296,12 +316,61 @@ class ExportsInfo {
return array;
}
/**
* @param {string | string[]} name the name of the export
* @returns {boolean | undefined | null} if the export is provided
*/
isExportProvided(name) {
if (Array.isArray(name)) {
// TODO follow nested exports
return this.isExportProvided(name[0]);
}
let info = this._exports.get(name);
if (info === undefined) info = this._otherExportsInfo;
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() {
const otherProvided = this._otherExportsInfo.provided;
const otherCanMangleProvide = this._otherExportsInfo.canMangleProvide;
@ -350,14 +419,8 @@ class ExportInfo {
this.name = name;
/** @type {string | null} */
this.usedName = initFrom ? initFrom.usedName : null;
/**
* true: it is used
* 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;
/** @type {UsageStateType} */
this.used = initFrom ? initFrom.used : UsageState.NoInfo;
/**
* true: it is provided
* false: it is not provided
@ -380,6 +443,8 @@ class ExportInfo {
* @type {boolean | undefined}
*/
this.canMangleUse = initFrom ? initFrom.canMangleUse : undefined;
/** @type {ExportsInfo=} */
this.exportsInfo = undefined;
}
get canMangle() {
@ -406,20 +471,22 @@ class ExportInfo {
}
getUsedName() {
if (this.used === false) return false;
if (this.used === UsageState.Unused) return false;
return this.usedName || this.name;
}
getUsedInfo() {
switch (this.used) {
case undefined:
case UsageState.NoInfo:
return "no usage info";
case null:
case UsageState.Unknown:
return "maybe used (runtime-defined)";
case true:
case UsageState.Used:
return "used";
case false:
case UsageState.Unused:
return "unused";
case UsageState.OnlyPropertiesUsed:
return "only properties used";
}
}
@ -828,7 +895,7 @@ class ModuleGraph {
/**
* @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.
* null, if it's unknown.
* false, if it's not provided.
@ -1044,3 +1111,6 @@ const deprecateMap = new Map();
module.exports = ModuleGraph;
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 Template = require("./Template");
const propertyAccess = require("./util/propertyAccess");
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./ChunkGraph")} ChunkGraph */
@ -14,6 +15,14 @@ const Template = require("./Template");
/** @typedef {import("./ModuleGraph")} ModuleGraph */
/** @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 {
/**
* @param {TODO} outputOptions the compilation output options
@ -506,7 +515,7 @@ class RuntimeTemplate {
* @param {ModuleGraph} options.moduleGraph the module graph
* @param {Module} options.module the module
* @param {string} options.request the request
* @param {string} options.exportName the export name
* @param {string | string[]} options.exportName the export name
* @param {Module} options.originModule the origin module
* @param {boolean} options.asiSafe true, if location is safe for ASI, a bracket can be emitted
* @param {boolean} options.isCall true, if expression will be called
@ -532,24 +541,30 @@ class RuntimeTemplate {
request
});
}
if (!Array.isArray(exportName)) {
exportName = exportName ? [exportName] : [];
}
const exportsType = module.buildMeta && module.buildMeta.exportsType;
if (!exportsType) {
if (exportName === "default") {
if (exportName.length > 0 && exportName[0] === "default") {
if (!originModule.buildMeta.strictHarmonyModule) {
if (isCall) {
return `${importVar}_default()`;
return `${importVar}_default()${propertyAccess(exportName, 1)}`;
} else if (asiSafe) {
return `(${importVar}_default())`;
return `(${importVar}_default()${propertyAccess(exportName, 1)})`;
} else {
return `${importVar}_default.a`;
return `${importVar}_default.a${propertyAccess(exportName, 1)}`;
}
} else {
return importVar;
return `${importVar}${propertyAccess(exportName, 1)}`;
}
} else if (originModule.buildMeta.strictHarmonyModule) {
if (exportName) {
return "/* non-default import from non-esm module */undefined";
if (exportName.length > 0) {
return (
"/* non-default import from non-esm module */undefined" +
propertyAccess(exportName, 1)
);
} else {
runtimeRequirements.add(RuntimeGlobals.createFakeNamespaceObject);
return `/*#__PURE__*/${
@ -560,26 +575,30 @@ class RuntimeTemplate {
}
if (exportsType === "named") {
if (exportName === "default") {
return importVar;
} else if (!exportName) {
if (exportName.length > 0 && exportName[0] === "default") {
return `${importVar}${propertyAccess(exportName, 1)}`;
} else if (exportName.length === 0) {
return `${importVar}_namespace`;
}
}
if (exportName) {
const used = module.getUsedName(moduleGraph, exportName);
if (exportName.length > 0) {
const exportsInfo = moduleGraph.getExportsInfo(module);
const used = exportsInfo.getUsedName(exportName);
if (!used) {
const comment = Template.toNormalComment(`unused export ${exportName}`);
const comment = Template.toNormalComment(
`unused export ${exportName.join(".")}`
);
return `${comment} undefined`;
}
const comment =
used !== exportName ? Template.toNormalComment(exportName) + " " : "";
const access = `${importVar}[${comment}${JSON.stringify(used)}]`;
if (isCall) {
if (callContext === false && asiSafe) {
const comment = arrayEquals(used, exportName)
? ""
: Template.toNormalComment(exportName.join(".")) + " ";
const access = `${importVar}${comment}${propertyAccess(used)}`;
if (isCall && callContext === false) {
if (asiSafe) {
return `(0,${access})`;
} else if (callContext === false) {
} else {
return `Object(${access})`;
}
}

View File

@ -14,7 +14,7 @@ class DependencyReference {
/**
*
* @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 {number} order the order information or NaN if don't care
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -104,11 +104,11 @@ class SideEffectsFlagPlugin {
if (!map) {
reexportMaps.set(module, (map = new Map()));
}
for (const [key, id] of mode.map) {
if (!mode.checked.has(key)) {
for (const [key, ids] of mode.map) {
if (ids.length > 0 && !mode.checked.has(key)) {
map.set(key, {
module: mode.getModule(),
exportName: id
exportName: ids[0]
});
}
}
@ -147,20 +147,29 @@ class SideEffectsFlagPlugin {
(dep instanceof HarmonyImportSpecifierDependency &&
!dep.namespaceObjectAsContext)
) {
const mapping = map.get(dep.id);
// TODO improve for nested imports
const ids = dep.getIds(moduleGraph);
if (ids.length > 0) {
const mapping = map.get(ids[0]);
if (mapping) {
moduleGraph.updateModule(dep, mapping.module);
moduleGraph.addExplanation(
dep,
"(skipped side-effect-free modules)"
);
dep.setId(moduleGraph, mapping.exportName);
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;
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
if (
Object.prototype.hasOwnProperty.call(

View File

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

View File

@ -430,7 +430,7 @@ Child all:
`;
exports[`StatsTestCases should print correct stats for chunk-module-id-range 1`] = `
"Hash: 72e3b32fcbbc550ae041
"Hash: 4b7df25b6465c464acfe
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
PublicPath: (none)
@ -503,10 +503,10 @@ Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
PublicPath: (none)
Asset Size Chunks Chunk Names
b_js.bundle.js 370 bytes {b_js} [emitted]
bundle.js 8.23 KiB {main} [emitted] main
c_js.bundle.js 579 bytes {c_js} [emitted]
d_js-e_js.bundle.js 772 bytes {d_js-e_js} [emitted]
b_js.bundle.js 364 bytes {b_js} [emitted]
bundle.js 8.22 KiB {main} [emitted] main
c_js.bundle.js 573 bytes {c_js} [emitted]
d_js-e_js.bundle.js 760 bytes {d_js-e_js} [emitted]
Entrypoint main = bundle.js
chunk {b_js} b_js.bundle.js 22 bytes <{main}> [rendered]
> ./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`] = `
"Hash: 53f13412753ee8bb3d387595bab0743d3d05a312
"Hash: ada681c866c95e0132eff2c9453ed201db5580c2
Child
Hash: 53f13412753ee8bb3d38
Hash: ada681c866c95e0132ef
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names
app.269cd87fa0cbcfa98d1b.js 6.65 KiB {143} [emitted] app
vendor.37b166c3b85bfb1caf5f.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.37b166c3b85bfb1caf5f.js app.269cd87fa0cbcfa98d1b.js
app.72745ea3abc585e103e5.js 6.69 KiB {143} [emitted] app
vendor.22698e25646290b0dcc1.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.22698e25646290b0dcc1.js app.72745ea3abc585e103e5.js
[117] ./entry-1.js + 2 modules 190 bytes {143} [built]
[381] ./constants.js 87 bytes {736} [built]
+ 4 hidden modules
Child
Hash: 7595bab0743d3d05a312
Hash: f2c9453ed201db5580c2
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names
app.3ec38d64835ee4418bb3.js 6.67 KiB {143} [emitted] app
vendor.37b166c3b85bfb1caf5f.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.37b166c3b85bfb1caf5f.js app.3ec38d64835ee4418bb3.js
app.eee68844dbd56e9ed525.js 6.7 KiB {143} [emitted] app
vendor.22698e25646290b0dcc1.js 616 bytes {736} [emitted] vendor
Entrypoint app = vendor.22698e25646290b0dcc1.js app.eee68844dbd56e9ed525.js
[381] ./constants.js 87 bytes {736} [built]
[655] ./entry-2.js + 2 modules 197 bytes {143} [built]
+ 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`] = `
"Hash: 49f607faddf9699cbef8
"Hash: 99ab54650501715e52b3
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
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`] = `
"Hash: 9e3985f832f4d566fdbb
"Hash: 9757fb3da1f7b8f77bbb
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names
@ -1476,7 +1476,7 @@ Entrypoint main = main.js
[10] ./index.js 19 bytes {179} [built]
[195] ./not-existing.js 26 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
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]
[195] ./not-existing.js 26 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
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
Entrypoint main = main.js
[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
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`] = `
"Hash: ac1889126d1918a36204
"Hash: b4a9cfa089ce3732efc5
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint index = index.js
@ -2318,9 +2318,9 @@ Entrypoint entry = entry.js
`;
exports[`StatsTestCases should print correct stats for scope-hoisting-multi 1`] = `
"Hash: 0b46c17cd9c6ad0fbfb2a8733d1161800e5d8cb5
"Hash: d4c3cd048bdc678bb92437745aea03a16f764c79
Child
Hash: 0b46c17cd9c6ad0fbfb2
Hash: d4c3cd048bdc678bb924
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint first = vendor.js first.js
@ -2338,7 +2338,7 @@ Child
[965] ./vendor.js 25 bytes {736} [built]
+ 10 hidden modules
Child
Hash: a8733d1161800e5d8cb5
Hash: 37745aea03a16f764c79
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Entrypoint first = vendor.js first.js
@ -2365,11 +2365,11 @@ Child
`;
exports[`StatsTestCases should print correct stats for side-effects-issue-7428 1`] = `
"Hash: 8d106e0a6c9f100fb26f
"Hash: 8871bd720c1e20111581
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
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
Entrypoint main = main.js
[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`] = `
"Hash: aabd6d1544eb169e22e5
"Hash: 589af6828de7c047d0c5
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
Asset Size Chunks Chunk Names
@ -2450,7 +2450,7 @@ exports[`StatsTestCases should print correct stats for simple 1`] = `
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
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
[./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`] = `
"Hash: a74d3a7695fab0e2d9aa
"Hash: 9f0d95b08ca5afeee1fe
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
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`] = `
"Hash: e6c683371cb4c4354398
"Hash: 48cd6bb29a775ac0f234
Time: Xms
Built at: Thu Jan 01 1970 00:00:00 GMT
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]
325.bundle.js 3.93 KiB {325} [emitted]
325.bundle.js 3.9 KiB {325} [emitted]
526.bundle.js 329 bytes {526} [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]
a160192b41d0463ac226.module.wasm 131 bytes {230} [emitted]
bundle.js 13.5 KiB {520} [emitted] main-1df31ce3
cfd5ebf998e67f7103a2.module.wasm 510 bytes {99} [emitted]
e94fc8ba836bfd2c6038.module.wasm 131 bytes {230} [emitted]
f8f99fabd0c63ad1c730.module.wasm 225 bytes {780} [emitted]
fea68b3453f2f8fdfeed.module.wasm 132 bytes {780} [emitted]
d95036d90a8b92bc2692.module.wasm 510 bytes {99} [emitted]
ff9c5233b4dbdf834f6f.module.wasm 125 bytes {325} [emitted]
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]
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]
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]
[325] ./tests.js 1.4 KiB {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
chunk {526} 526.bundle.js 34 bytes [rendered] split chunk (cache group: defaultVendors)
[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]
[151] ./fast-math.wasm 105 bytes (javascript) 290 bytes (webassembly) {780} [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.identifier().endsWith("reference.js") &&
Array.isArray(ref.importedNames) &&
ref.importedNames.includes("unused")
ref.importedNames.some(
names => names.length === 1 && names[0] === "unused"
)
) {
const newExports = ref.importedNames.filter(
item => item !== "unused"
names => names.length !== 1 || names[0] !== "unused"
);
return new DependencyReference(
() => ref.module,

View File

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

View File

@ -1,14 +1,63 @@
function returnThis() {
if(typeof this === "undefined") return "undefined";
if (typeof this === "undefined") return "undefined";
return this;
}
var a = returnThis;
var b = returnThis;
export {
a,
b
export { 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;

View File

@ -1,11 +1,45 @@
"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";
function x() { throw new Error("should not be executed"); }
it("should have this = undefined on imported non-strict functions", function() {
it("should have this = undefined on harmony modules", () => {
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
expect(d()).toBe("undefined");
x
@ -13,17 +47,17 @@ it("should have this = undefined on imported non-strict functions", function() {
x
expect(B()).toBe("undefined");
x
expect(abc.a()).toMatchObject({});
expect(abc.a()).toBeTypeOf("object");
x
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 * as New from "./new";
it("should be possible to use new correctly", function() {
it("should be possible to use new correctly", () => {
x
expect(new C()).toEqual({ok: true});
x
@ -31,3 +65,9 @@ it("should be possible to use new correctly", function() {
x
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: {
strictThisContextOnImports: true
},
plugins: [new webpack.optimize.ModuleConcatenationPlugin()]
}
};

View File

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