From 43bc7a306e821d55bfd13c71be8cef6fdccd03ee Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 14 Mar 2019 12:06:59 +0100 Subject: [PATCH] Refactor to track nested exports Harmony dependencies track access to nested properties Flag nested exports --- lib/Dependency.js | 9 +- lib/FlagDependencyExportsPlugin.js | 36 ++- lib/FlagDependencyUsagePlugin.js | 92 ++++-- lib/FunctionModuleTemplatePlugin.js | 47 +-- lib/JavascriptParser.js | 68 +++-- lib/JsonGenerator.js | 3 +- lib/Module.js | 14 +- lib/ModuleGraph.js | 166 +++++++--- lib/RuntimeTemplate.js | 59 ++-- lib/dependencies/DependencyReference.js | 2 +- .../HarmonyExportDependencyParserPlugin.js | 4 +- ...armonyExportImportedSpecifierDependency.js | 167 +++++++--- .../HarmonyImportDependencyParserPlugin.js | 32 +- .../HarmonyImportSpecifierDependency.js | 75 +++-- lib/optimize/ConcatenatedModule.js | 287 +++++++++++------- lib/optimize/SideEffectsFlagPlugin.js | 33 +- lib/util/propertyAccess.js | 25 ++ lib/wasm/WasmFinalizeExportsPlugin.js | 5 +- lib/wasm/WebAssemblyJavascriptGenerator.js | 8 +- .../__snapshots__/StatsTestCases.test.js.snap | 80 ++--- .../parsing/harmony-deep-exports/counter.js | 8 + .../parsing/harmony-deep-exports/index.js | 24 ++ .../reexport-namespace-again.js | 2 + .../reexport-namespace.js | 2 + .../webpack.config.js | 6 +- .../remove-export/webpack.config.js | 6 +- .../parsing/harmony-this-concat/abc.js | 57 +++- .../parsing/harmony-this-concat/index.js | 52 +++- .../parsing/harmony-this-concat/issue-7213.js | 20 ++ .../harmony-this-concat/webpack.config.js | 3 +- .../parsing/harmony-this/webpack.config.js | 3 + 31 files changed, 961 insertions(+), 434 deletions(-) create mode 100644 lib/util/propertyAccess.js create mode 100644 test/cases/parsing/harmony-deep-exports/counter.js create mode 100644 test/cases/parsing/harmony-deep-exports/index.js create mode 100644 test/cases/parsing/harmony-deep-exports/reexport-namespace-again.js create mode 100644 test/cases/parsing/harmony-deep-exports/reexport-namespace.js create mode 100644 test/configCases/parsing/harmony-this-concat/issue-7213.js diff --git a/lib/Dependency.js b/lib/Dependency.js index 25bd5b19a..dcd123d03 100644 --- a/lib/Dependency.js +++ b/lib/Dependency.js @@ -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 */ diff --git a/lib/FlagDependencyExportsPlugin.js b/lib/FlagDependencyExportsPlugin.js index f1ed871e9..f71e51ffa 100644 --- a/lib/FlagDependencyExportsPlugin.js +++ b/lib/FlagDependencyExportsPlugin.js @@ -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,11 +115,35 @@ class FlagDependencyExportsPlugin { } } else if (Array.isArray(exports)) { // merge in new exports - for (const exportName of exports) { - const exportInfo = exportsInfo.getExportInfo(exportName); - if (exportInfo.provided === false) { - exportInfo.provided = true; - changed = true; + 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; + } + } } } } diff --git a/lib/FlagDependencyUsagePlugin.js b/lib/FlagDependencyUsagePlugin.js index c4940dc10..271e05e7b 100644 --- a/lib/FlagDependencyUsagePlugin.js +++ b/lib/FlagDependencyUsagePlugin.js @@ -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} */ + 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 ( - exportName === "default" && - module.buildMeta.exportsType === "named" - ) { - if (exportsInfo.setUsedAsNamedExportType()) { - changed = true; + if (usedExports.length > 0) { + for (const usedExport of usedExports) { + if (usedExport.length === 0) { + if (exportsInfo.setUsedInUnknownWay()) { + queue.enqueue(module); } } else { - const exportInfo = exportsInfo.getExportInfo(exportName); - if (exportInfo.used !== true) { - exportInfo.used = true; - changed = true; + if ( + usedExport[0] === "default" && + module.buildMeta.exportsType === "named" + ) { + 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 // This module won't be evaluated in this case if (module.factoryMeta.sideEffectFree) return; - changed = exportsInfo.setUsedForSideEffectsOnly(); - } - - if (changed) { - queue.enqueue(module); + 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} */ @@ -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); } } } diff --git a/lib/FunctionModuleTemplatePlugin.js b/lib/FunctionModuleTemplatePlugin.js index c4288aa6c..e7aacc660 100644 --- a/lib/FunctionModuleTemplatePlugin.js +++ b/lib/FunctionModuleTemplatePlugin.js @@ -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( diff --git a/lib/JavascriptParser.js b/lib/JavascriptParser.js index 2daf9c6cf..91b67bf3a 100644 --- a/lib/JavascriptParser.js +++ b/lib/JavascriptParser.js @@ -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 (expressionAnyMemberHook !== undefined) { - const result = expressionAnyMemberHook.call(expression); + if (expressionMemberChainHook !== undefined) { + const result = expressionMemberChainHook.call( + expression, + exprName.rootRaw, + exprName.members + ); 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") { - free = this.scope.topLevelScope; - exprName.push("this"); + 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 }; } diff --git a/lib/JsonGenerator.js b/lib/JsonGenerator.js index adc3e326d..fe5a639fc 100644 --- a/lib/JsonGenerator.js +++ b/lib/JsonGenerator.js @@ -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 = {}; diff --git a/lib/Module.js b/lib/Module.js index f6402ce9a..563099ed2 100644 --- a/lib/Module.js +++ b/lib/Module.js @@ -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); } /** diff --git a/lib/ModuleGraph.js b/lib/ModuleGraph.js index e3f92511b..9a398a9de 100644 --- a/lib/ModuleGraph.js +++ b/lib/ModuleGraph.js @@ -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} */ @@ -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; diff --git a/lib/RuntimeTemplate.js b/lib/RuntimeTemplate.js index fe611376b..b83144d27 100644 --- a/lib/RuntimeTemplate.js +++ b/lib/RuntimeTemplate.js @@ -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})`; } } diff --git a/lib/dependencies/DependencyReference.js b/lib/dependencies/DependencyReference.js index 28d289a19..b69b7e666 100644 --- a/lib/dependencies/DependencyReference.js +++ b/lib/dependencies/DependencyReference.js @@ -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 */ diff --git a/lib/dependencies/HarmonyExportDependencyParserPlugin.js b/lib/dependencies/HarmonyExportDependencyParserPlugin.js index b73ef1943..47df91fef 100644 --- a/lib/dependencies/HarmonyExportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyExportDependencyParserPlugin.js @@ -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(), diff --git a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js index 285c02dd0..f0d3dc2df 100644 --- a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +++ b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js @@ -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} */ +/** @type {Map} */ const EMPTY_MAP = new Map(); /** @type {Set} */ @@ -43,8 +45,10 @@ class ExportMode { this.type = type; /** @type {string|null} */ this.name = null; - /** @type {Map} */ + /** @type {Map} */ this.map = EMPTY_MAP; + /** @type {ExportsInfo} */ + this.partialNamespaceExportsInfo = undefined; /** @type {Set|null} */ this.ignored = null; /** @type {Set|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} activeExports other named exports in the module * @param {Iterable} 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} */ + /** @type {Map} */ 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,18 +532,19 @@ 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}'` - : "the namespace object"; + const exportName = + ids.length > 0 + ? `the named export ${ids.map(id => `'${id}'`).join(".")}` + : "the namespace object"; return [ new HarmonyLinkingError( @@ -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) */ " + diff --git a/lib/dependencies/HarmonyImportDependencyParserPlugin.js b/lib/dependencies/HarmonyImportDependencyParserPlugin.js index 93a5db24a..ebbd70d46 100644 --- a/lib/dependencies/HarmonyImportDependencyParserPlugin.js +++ b/lib/dependencies/HarmonyImportDependencyParserPlugin.js @@ -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 diff --git a/lib/dependencies/HarmonyImportSpecifierDependency.js b/lib/dependencies/HarmonyImportSpecifierDependency.js index e9db4ba11..bd4fe67f6 100644 --- a/lib/dependencies/HarmonyImportSpecifierDependency.js +++ b/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -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,16 +127,18 @@ 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)}'` - : "the namespace object"; + const exportName = + ids.length > 0 + ? `the named export '${ids[0]}'` + : "the namespace object"; return [ new HarmonyLinkingError( `Can't import ${exportName} from non EcmaScript module (only default export is available)` @@ -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, diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index 169295e4e..b40fc22e0 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -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} exportMap + * @property {Map} 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} 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)}` : ""; - switch (importedModule.buildMeta.exportsType) { - case "named": - if (exportName === "default") { - return info.name; - } else if (exportName === true) { + const comment = arrayEquals(used, exportName) + ? "" + : Template.toNormalComment(`${exportName.join(".")}`); + let exprStart; + if (exportName.length === 0) { + switch (importedModule.buildMeta.exportsType) { + case "named": info.interopNamespaceObjectUsed = true; - return info.interopNamespaceObjectName; - } else { + exprStart = info.interopNamespaceObjectName; break; - } - case "namespace": - if (exportName === true) { - return info.name; - } else { + case "namespace": + exprStart = info.name; break; - } - default: - if (strictHarmonyModule) { - if (exportName === "default") { - return info.name; - } else if (exportName === true) { + default: + if (strictHarmonyModule) { 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; + } + 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 (asCall) return `Object(${reference})`; - return reference; + 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} moduleToInfoMap moduleToInfoMap + * @param {RequestShortener} requestShortener the request shortener + * @param {boolean} asCall asCall + * @param {boolean} strictHarmonyModule strictHarmonyModule + * @param {Set} alreadyVisited alreadyVisited + * @returns {string} the final name + */ const getFinalName = ( moduleGraph, info, @@ -214,21 +268,25 @@ const getFinalName = ( ) => { switch (info.type) { 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 (exportName === true) { - ensureNsObjSource( - moduleGraph, - info, - moduleToInfoMap, - requestShortener, - strictHarmonyModule - ); - } else if (!info.module.isExportUsed(moduleGraph, exportName)) { - return "/* unused export */ undefined"; - } - if (info.globalExports.has(directExport)) { - return 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} */ const exportMap = new Map(); + /** @type {Map} */ 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}__`; diff --git a/lib/optimize/SideEffectsFlagPlugin.js b/lib/optimize/SideEffectsFlagPlugin.js index 7ecbacb56..5156a6539 100644 --- a/lib/optimize/SideEffectsFlagPlugin.js +++ b/lib/optimize/SideEffectsFlagPlugin.js @@ -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,15 +147,24 @@ class SideEffectsFlagPlugin { (dep instanceof HarmonyImportSpecifierDependency && !dep.namespaceObjectAsContext) ) { - const mapping = map.get(dep.id); - if (mapping) { - moduleGraph.updateModule(dep, mapping.module); - moduleGraph.addExplanation( - dep, - "(skipped side-effect-free modules)" - ); - dep.setId(moduleGraph, mapping.exportName); - continue; + // 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.setIds( + moduleGraph, + mapping.exportName + ? [mapping.exportName, ...ids.slice(1)] + : ids.slice(1) + ); + continue; + } } } } diff --git a/lib/util/propertyAccess.js b/lib/util/propertyAccess.js new file mode 100644 index 000000000..35138d48d --- /dev/null +++ b/lib/util/propertyAccess.js @@ -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; diff --git a/lib/wasm/WasmFinalizeExportsPlugin.js b/lib/wasm/WasmFinalizeExportsPlugin.js index 3758e5423..fd4a8ae4f 100644 --- a/lib/wasm/WasmFinalizeExportsPlugin.js +++ b/lib/wasm/WasmFinalizeExportsPlugin.js @@ -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( diff --git a/lib/wasm/WebAssemblyJavascriptGenerator.js b/lib/wasm/WebAssemblyJavascriptGenerator.js index 644dbc709..6b9ff4927 100644 --- a/lib/wasm/WebAssemblyJavascriptGenerator.js +++ b/lib/wasm/WebAssemblyJavascriptGenerator.js @@ -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});` : "", diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index 771c7bc4b..def7ac33a 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -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] diff --git a/test/cases/parsing/harmony-deep-exports/counter.js b/test/cases/parsing/harmony-deep-exports/counter.js new file mode 100644 index 000000000..f44c1e393 --- /dev/null +++ b/test/cases/parsing/harmony-deep-exports/counter.js @@ -0,0 +1,8 @@ +export let counter = 0; +export const increment = () => { + counter++; +}; +export function reset() { + counter = 0; +}; +export const unusedExport = 42; diff --git a/test/cases/parsing/harmony-deep-exports/index.js b/test/cases/parsing/harmony-deep-exports/index.js new file mode 100644 index 000000000..050067f37 --- /dev/null +++ b/test/cases/parsing/harmony-deep-exports/index.js @@ -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); +}); diff --git a/test/cases/parsing/harmony-deep-exports/reexport-namespace-again.js b/test/cases/parsing/harmony-deep-exports/reexport-namespace-again.js new file mode 100644 index 000000000..1ef751cd4 --- /dev/null +++ b/test/cases/parsing/harmony-deep-exports/reexport-namespace-again.js @@ -0,0 +1,2 @@ +import * as CC from "./reexport-namespace"; +export { CC }; diff --git a/test/cases/parsing/harmony-deep-exports/reexport-namespace.js b/test/cases/parsing/harmony-deep-exports/reexport-namespace.js new file mode 100644 index 000000000..7d219ddae --- /dev/null +++ b/test/cases/parsing/harmony-deep-exports/reexport-namespace.js @@ -0,0 +1,2 @@ +import * as counter from "./counter"; +export { counter }; diff --git a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js index 47d2f212d..873be6c70 100644 --- a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js +++ b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js @@ -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, diff --git a/test/configCases/deep-scope-analysis/remove-export/webpack.config.js b/test/configCases/deep-scope-analysis/remove-export/webpack.config.js index f37fd93ad..063df7033 100644 --- a/test/configCases/deep-scope-analysis/remove-export/webpack.config.js +++ b/test/configCases/deep-scope-analysis/remove-export/webpack.config.js @@ -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, diff --git a/test/configCases/parsing/harmony-this-concat/abc.js b/test/configCases/parsing/harmony-this-concat/abc.js index f9d8733a2..567c19da6 100644 --- a/test/configCases/parsing/harmony-this-concat/abc.js +++ b/test/configCases/parsing/harmony-this-concat/abc.js @@ -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; diff --git a/test/configCases/parsing/harmony-this-concat/index.js b/test/configCases/parsing/harmony-this-concat/index.js index e2b94f2eb..a8af27500 100644 --- a/test/configCases/parsing/harmony-this-concat/index.js +++ b/test/configCases/parsing/harmony-this-concat/index.js @@ -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); +}); diff --git a/test/configCases/parsing/harmony-this-concat/issue-7213.js b/test/configCases/parsing/harmony-this-concat/issue-7213.js new file mode 100644 index 000000000..0e3a36512 --- /dev/null +++ b/test/configCases/parsing/harmony-this-concat/issue-7213.js @@ -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) diff --git a/test/configCases/parsing/harmony-this-concat/webpack.config.js b/test/configCases/parsing/harmony-this-concat/webpack.config.js index 9cd2bdf56..16687213a 100644 --- a/test/configCases/parsing/harmony-this-concat/webpack.config.js +++ b/test/configCases/parsing/harmony-this-concat/webpack.config.js @@ -2,6 +2,5 @@ var webpack = require("../../../../"); module.exports = { module: { strictThisContextOnImports: true - }, - plugins: [new webpack.optimize.ModuleConcatenationPlugin()] + } }; diff --git a/test/configCases/parsing/harmony-this/webpack.config.js b/test/configCases/parsing/harmony-this/webpack.config.js index dfb1984cf..3877e9e67 100644 --- a/test/configCases/parsing/harmony-this/webpack.config.js +++ b/test/configCases/parsing/harmony-this/webpack.config.js @@ -1,5 +1,8 @@ module.exports = { module: { strictThisContextOnImports: true + }, + optimization: { + concatenateModules: false } };