diff --git a/lib/FlagDependencyUsagePlugin.js b/lib/FlagDependencyUsagePlugin.js index 42dd4c0e2da..9531b4b9008 100644 --- a/lib/FlagDependencyUsagePlugin.js +++ b/lib/FlagDependencyUsagePlugin.js @@ -20,7 +20,7 @@ const isSubset = (biggerSet, subset) => { class FlagDependencyUsagePlugin { apply(compiler) { compiler.hooks.compilation.tap("FlagDependencyUsagePlugin", compilation => { - compilation.hooks.optimizeModulesAdvanced.tap( + compilation.hooks.optimizeDependencies.tap( "FlagDependencyUsagePlugin", modules => { const processModule = (module, usedExports) => { @@ -86,9 +86,9 @@ class FlagDependencyUsagePlugin { } const queue = []; - for (const chunk of compilation.chunks) { - if (chunk.entryModule) { - processModule(chunk.entryModule, true); + for (const preparedEntrypoint of compilation._preparedEntrypoints) { + if (preparedEntrypoint.module) { + processModule(preparedEntrypoint.module, true); } } diff --git a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js index b4b2f23442b..d35a2b3e683 100644 --- a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +++ b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js @@ -9,14 +9,27 @@ const HarmonyImportDependency = require("./HarmonyImportDependency"); const Template = require("../Template"); const HarmonyLinkingError = require("../HarmonyLinkingError"); +/** @typedef {import("../Module")} Module */ + +/** @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"|"safe-reexport"|"checked-reexport"|"dynamic-reexport"} ExportModeType */ + +/** @type {Map} */ const EMPTY_MAP = new Map(); class ExportMode { + /** + * @param {ExportModeType} type type of the mode + */ constructor(type) { + /** @type {ExportModeType} */ this.type = type; + /** @type {string|null} */ this.name = null; + /** @type {Map} */ this.map = EMPTY_MAP; + /** @type {Module|null} */ this.module = null; + /** @type {string|null} */ this.userRequest = null; } } @@ -51,7 +64,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { const name = this.name; const id = this.id; const used = this.originModule.isUsed(name); - const importedModule = this.module; + const importedModule = this._module; if (!importedModule) { const mode = new ExportMode("missing"); @@ -237,7 +250,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { const result = new Set(); // try to learn impossible exports from other star exports with provided exports for (const otherStarExport of this.otherStarExports) { - const otherImportedModule = otherStarExport.module; + const otherImportedModule = otherStarExport._module; if ( otherImportedModule && Array.isArray(otherImportedModule.buildMeta.providedExports) @@ -310,7 +323,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { } _getErrors() { - const importedModule = this.module; + const importedModule = this._module; if (!importedModule) { return; } @@ -354,7 +367,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency { updateHash(hash) { super.updateHash(hash); - const hashValue = this.getHashValue(this.module); + const hashValue = this.getHashValue(this._module); hash.update(hashValue); } @@ -387,7 +400,7 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS const used = dep.originModule.isUsed(dep.name); if (!used) return NaN; } else { - const importedModule = dep.module; + const importedModule = dep._module; const activeFromOtherStarExports = dep._discoverActiveExportsFromOtherStartExports(); @@ -424,7 +437,7 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS getContent(dep) { const mode = dep.getMode(false); const module = dep.originModule; - const importedModule = dep.module; + const importedModule = dep._module; const importVar = dep.getImportVar(); switch (mode.type) { diff --git a/lib/dependencies/HarmonyImportDependency.js b/lib/dependencies/HarmonyImportDependency.js index 099318072ff..162c1030037 100644 --- a/lib/dependencies/HarmonyImportDependency.js +++ b/lib/dependencies/HarmonyImportDependency.js @@ -11,32 +11,37 @@ const Template = require("../Template"); class HarmonyImportDependency extends ModuleDependency { constructor(request, originModule, sourceOrder, parserScope) { super(request); + this.redirectedModule = undefined; this.originModule = originModule; this.sourceOrder = sourceOrder; this.parserScope = parserScope; } + get _module() { + return this.redirectedModule || this.module; + } + getReference() { - if (!this.module) return null; - return new DependencyReference(this.module, false, this.weak); + if (!this._module) return null; + return new DependencyReference(this._module, false, this.weak); } getImportVar() { let importVarMap = this.parserScope.importVarMap; if (!importVarMap) this.parserScope.importVarMap = importVarMap = new Map(); - let importVar = importVarMap.get(this.module); + let importVar = importVarMap.get(this._module); if (importVar) return importVar; importVar = `${Template.toIdentifier( `${this.userRequest}` )}__WEBPACK_IMPORTED_MODULE_${importVarMap.size}__`; - importVarMap.set(this.module, importVar); + importVarMap.set(this._module, importVar); return importVar; } getImportStatement(update, runtime) { return runtime.importStatement({ update, - module: this.module, + module: this._module, importVar: this.getImportVar(), request: this.request, originModule: this.originModule @@ -45,7 +50,7 @@ class HarmonyImportDependency extends ModuleDependency { updateHash(hash) { super.updateHash(hash); - const importedModule = this.module; + const importedModule = this._module; hash.update( (importedModule && (!importedModule.buildMeta || importedModule.buildMeta.exportsType)) + @@ -53,6 +58,11 @@ class HarmonyImportDependency extends ModuleDependency { ); hash.update((importedModule && importedModule.id) + ""); } + + disconnect() { + super.disconnect(); + this.redirectedModule = undefined; + } } module.exports = HarmonyImportDependency; @@ -71,7 +81,7 @@ HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate { static isImportEmitted(dep, source) { let sourceInfo = importEmittedMap.get(source); if (!sourceInfo) return false; - const key = dep.module || dep.request; + const key = dep._module || dep.request; return key && sourceInfo.emittedImports.get(key); } @@ -85,7 +95,7 @@ HarmonyImportDependency.Template = class HarmonyImportDependencyTemplate { }) ); } - const key = dep.module || dep.request; + const key = dep._module || dep.request; if (key && sourceInfo.emittedImports.get(key)) return; sourceInfo.emittedImports.set(key, true); const content = dep.getImportStatement(false, runtime); diff --git a/lib/dependencies/HarmonyImportSideEffectDependency.js b/lib/dependencies/HarmonyImportSideEffectDependency.js index dcfb52499a8..a41c1b95bc6 100644 --- a/lib/dependencies/HarmonyImportSideEffectDependency.js +++ b/lib/dependencies/HarmonyImportSideEffectDependency.js @@ -11,7 +11,7 @@ class HarmonyImportSideEffectDependency extends HarmonyImportDependency { } getReference() { - if (this.module && this.module.factoryMeta.sideEffectFree) return null; + if (this._module && this._module.factoryMeta.sideEffectFree) return null; return super.getReference(); } @@ -23,7 +23,7 @@ class HarmonyImportSideEffectDependency extends HarmonyImportDependency { HarmonyImportSideEffectDependency.Template = class HarmonyImportSideEffectDependencyTemplate extends HarmonyImportDependency.Template { getHarmonyInitOrder(dep) { - if (dep.module && dep.module.factoryMeta.sideEffectFree) return NaN; + if (dep._module && dep._module.factoryMeta.sideEffectFree) return NaN; return super.getHarmonyInitOrder(dep); } }; diff --git a/lib/dependencies/HarmonyImportSpecifierDependency.js b/lib/dependencies/HarmonyImportSpecifierDependency.js index b24ffe4fb2a..3a9ba5ae4ef 100644 --- a/lib/dependencies/HarmonyImportSpecifierDependency.js +++ b/lib/dependencies/HarmonyImportSpecifierDependency.js @@ -21,6 +21,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { ) { super(request, originModule, sourceOrder, parserScope); this.id = id === null ? null : `${id}`; + this.redirectedId = undefined; this.name = name; this.range = range; this.strictExportPresence = strictExportPresence; @@ -35,11 +36,15 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { return "harmony import specifier"; } + get _id() { + return this.redirectedId || this.id; + } + getReference() { - if (!this.module) return null; + if (!this._module) return null; return new DependencyReference( - this.module, - this.id && !this.namespaceObjectAsContext ? [this.id] : true, + this._module, + this._id && !this.namespaceObjectAsContext ? [this._id] : true, false ); } @@ -65,7 +70,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { } _getErrors() { - const importedModule = this.module; + const importedModule = this._module; if (!importedModule) { return; } @@ -74,11 +79,11 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { // It's not an harmony module if ( this.originModule.buildMeta.strictHarmonyModule && - this.id !== "default" + this._id !== "default" ) { // In strict harmony modules we only support the default export - const exportName = this.id - ? `the named export '${this.id}'` + const exportName = this._id + ? `the named export '${this._id}'` : "the namespace object"; return [ new HarmonyLinkingError( @@ -89,20 +94,20 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { return; } - if (!this.id) { + if (!this._id) { return; } - if (importedModule.isProvided(this.id) !== false) { + if (importedModule.isProvided(this._id) !== false) { // It's provided or we are not sure return; } // We are sure that it's not provided const idIsNotNameMessage = - this.id !== this.name ? ` (imported as '${this.name}')` : ""; + this._id !== this.name ? ` (imported as '${this.name}')` : ""; const errorMessage = `"export '${ - this.id + this._id }'${idIsNotNameMessage} was not found in '${this.userRequest}'`; return [new HarmonyLinkingError(errorMessage)]; } @@ -114,10 +119,10 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { updateHash(hash) { super.updateHash(hash); - const importedModule = this.module; - hash.update((importedModule && this.id) + ""); + const importedModule = this._module; + hash.update((importedModule && this._id) + ""); hash.update( - (importedModule && this.id && importedModule.isUsed(this.id)) + "" + (importedModule && this._id && importedModule.isUsed(this._id)) + "" ); hash.update( (importedModule && @@ -129,6 +134,11 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency { importedModule.used + JSON.stringify(importedModule.usedExports)) + "" ); } + + disconnect() { + super.disconnect(); + this.redirectedId = undefined; + } } HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependencyTemplate extends HarmonyImportDependency.Template { @@ -140,9 +150,9 @@ HarmonyImportSpecifierDependency.Template = class HarmonyImportSpecifierDependen getContent(dep, runtime) { const exportExpr = runtime.exportFromImport({ - module: dep.module, + module: dep._module, request: dep.request, - exportName: dep.id, + exportName: dep._id, originModule: dep.originModule, asiSafe: dep.shorthand, isCall: dep.call, diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index 5b744261aaa..32774a94982 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -308,7 +308,7 @@ class ConcatenatedModule extends Module { for (const d of m.dependencies.filter( dep => !(dep instanceof HarmonyImportDependency) || - !modulesSet.has(dep.module) + !modulesSet.has(dep._module) )) { this.dependencies.push(d); } @@ -459,7 +459,7 @@ class ConcatenatedModule extends Module { ) { const exportName = dep.name; const importName = dep.id; - const importedModule = dep.module; + const importedModule = dep._module; if (exportName && importName) { if (!reexportMap.has(exportName)) { reexportMap.set(exportName, { @@ -1145,7 +1145,7 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate { } getHarmonyInitOrder(dep) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { return this.originalTemplate.getHarmonyInitOrder(dep); @@ -1154,7 +1154,7 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate { } harmonyInit(dep, source, runtimeTemplate, dependencyTemplates) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { this.originalTemplate.harmonyInit( @@ -1168,7 +1168,7 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate { } apply(dep, source, runtime, dependencyTemplates) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { this.originalTemplate.apply(dep, source, runtime, dependencyTemplates); @@ -1179,14 +1179,14 @@ class HarmonyImportSpecifierDependencyConcatenatedTemplate { const strictFlag = dep.originModule.buildMeta.strictHarmonyModule ? "_strict" : ""; - if (dep.id === null) { + if (dep._id === null) { content = `__WEBPACK_MODULE_REFERENCE__${info.index}_ns${strictFlag}__`; } else if (dep.namespaceObjectAsContext) { content = `__WEBPACK_MODULE_REFERENCE__${ info.index - }_ns${strictFlag}__[${JSON.stringify(dep.id)}]`; + }_ns${strictFlag}__[${JSON.stringify(dep._id)}]`; } else { - const exportData = Buffer.from(dep.id, "utf-8").toString("hex"); + const exportData = Buffer.from(dep._id, "utf-8").toString("hex"); content = `__WEBPACK_MODULE_REFERENCE__${ info.index }_${exportData}${callFlag}${strictFlag}__`; @@ -1205,7 +1205,7 @@ class HarmonyImportSideEffectDependencyConcatenatedTemplate { } getHarmonyInitOrder(dep) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { return this.originalTemplate.getHarmonyInitOrder(dep); @@ -1214,7 +1214,7 @@ class HarmonyImportSideEffectDependencyConcatenatedTemplate { } harmonyInit(dep, source, runtime, dependencyTemplates) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { this.originalTemplate.harmonyInit( @@ -1228,7 +1228,7 @@ class HarmonyImportSideEffectDependencyConcatenatedTemplate { } apply(dep, source, runtime, dependencyTemplates) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { this.originalTemplate.apply(dep, source, runtime, dependencyTemplates); @@ -1302,7 +1302,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate { } getExports(dep) { - const importModule = dep.module; + const importModule = dep._module; if (dep.id) { // export { named } from "module" return [ @@ -1336,7 +1336,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate { } getHarmonyInitOrder(dep) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { return this.originalTemplate.getHarmonyInitOrder(dep); @@ -1345,7 +1345,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate { } harmonyInit(dep, source, runtime, dependencyTemplates) { - const module = dep.module; + const module = dep._module; const info = this.modulesMap.get(module); if (!info) { this.originalTemplate.harmonyInit( @@ -1360,7 +1360,7 @@ class HarmonyExportImportedSpecifierDependencyConcatenatedTemplate { apply(dep, source, runtime, dependencyTemplates) { if (dep.originModule === this.rootModule) { - if (this.modulesMap.get(dep.module)) { + if (this.modulesMap.get(dep._module)) { const exportDefs = this.getExports(dep); for (const def of exportDefs) { const info = this.modulesMap.get(def.module); diff --git a/lib/optimize/SideEffectsFlagPlugin.js b/lib/optimize/SideEffectsFlagPlugin.js index e503262a6ea..a38eb2f8a08 100644 --- a/lib/optimize/SideEffectsFlagPlugin.js +++ b/lib/optimize/SideEffectsFlagPlugin.js @@ -9,6 +9,15 @@ const HarmonyExportImportedSpecifierDependency = require("../dependencies/Harmon const HarmonyImportSideEffectDependency = require("../dependencies/HarmonyImportSideEffectDependency"); const HarmonyImportSpecifierDependency = require("../dependencies/HarmonyImportSpecifierDependency"); +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../Dependency")} Dependency */ + +/** + * @typedef {Object} ExportInModule + * @property {Module} module the module + * @property {string} exportName the name of the export + */ + class SideEffectsFlagPlugin { apply(compiler) { compiler.hooks.normalModuleFactory.tap("SideEffectsFlagPlugin", nmf => { @@ -42,10 +51,12 @@ class SideEffectsFlagPlugin { compilation.hooks.optimizeDependencies.tap( "SideEffectsFlagPlugin", modules => { + /** @type {Map>} */ const reexportMaps = new Map(); // Capture reexports of sideEffectFree modules for (const module of modules) { + /** @type {Dependency[]} */ const removeDependencies = []; for (const dep of module.dependencies) { if (dep instanceof HarmonyImportSideEffectDependency) { @@ -72,12 +83,6 @@ class SideEffectsFlagPlugin { } } } - for (const dep of removeDependencies) { - module.removeDependency(dep); - dep.module.reasons = dep.module.reasons.filter( - r => r.dependency !== dep - ); - } } // Flatten reexports @@ -97,7 +102,6 @@ class SideEffectsFlagPlugin { } // Update imports along the reexports from sideEffectFree modules - const updates = []; for (const pair of reexportMaps) { const module = pair[0]; const map = pair[1]; @@ -106,28 +110,12 @@ class SideEffectsFlagPlugin { if (dep instanceof HarmonyImportSpecifierDependency) { const mapping = map.get(dep.id); if (mapping) { - updates.push({ - dep, - mapping, - module, - reason - }); + dep.redirectedModule = mapping.module; + dep.redirectedId = mapping.exportName; } } } } - - // Execute updates - for (const update of updates) { - const dep = update.dep; - const mapping = update.mapping; - const module = update.module; - const reason = update.reason; - dep.module = mapping.module; - dep.id = mapping.exportName; - module.removeReason(reason.module, dep); - mapping.module.addReason(reason.module, dep); - } } ); }); diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index 6d359b7f07c..e898e401754 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -680,15 +680,15 @@ exports[`StatsTestCases should print correct stats for concat-and-sideeffects 1` | ModuleConcatenation bailout: Module is an entry point | ./node_modules/pmodule/a.js 49 bytes [built] | ./node_modules/pmodule/aa.js 24 bytes [built] -[1] ./node_modules/pmodule/b.js 49 bytes [built] +[1] ./node_modules/pmodule/index.js 63 bytes [built] ModuleConcatenation bailout: Module is not in any chunk -[2] ./node_modules/pmodule/bb.js 24 bytes [built] +[2] ./node_modules/pmodule/b.js 49 bytes [built] ModuleConcatenation bailout: Module is not in any chunk -[3] ./node_modules/pmodule/c.js 49 bytes [built] +[3] ./node_modules/pmodule/bb.js 24 bytes [built] ModuleConcatenation bailout: Module is not in any chunk -[4] ./node_modules/pmodule/cc.js 24 bytes [built] +[4] ./node_modules/pmodule/c.js 49 bytes [built] ModuleConcatenation bailout: Module is not in any chunk -[5] ./node_modules/pmodule/index.js 63 bytes [built] +[5] ./node_modules/pmodule/cc.js 24 bytes [built] ModuleConcatenation bailout: Module is not in any chunk" `; diff --git a/test/watchCases/side-effects/issue-7400/0/index.js b/test/watchCases/side-effects/issue-7400/0/index.js new file mode 100644 index 00000000000..d8337fc74ad --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/0/index.js @@ -0,0 +1,6 @@ +import { doStuff } from "./require-me"; + +it("should compile correctly", () => { + expect(doStuff()).toEqual(42); + expect(WATCH_STEP).toEqual("0"); +}); \ No newline at end of file diff --git a/test/watchCases/side-effects/issue-7400/0/my-module/index.js b/test/watchCases/side-effects/issue-7400/0/my-module/index.js new file mode 100644 index 00000000000..f4cbd0d4ca1 --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/0/my-module/index.js @@ -0,0 +1 @@ +export { default as myFunction } from './myFunction' diff --git a/test/watchCases/side-effects/issue-7400/0/my-module/myFunction.js b/test/watchCases/side-effects/issue-7400/0/my-module/myFunction.js new file mode 100644 index 00000000000..2433d166ffa --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/0/my-module/myFunction.js @@ -0,0 +1,3 @@ +export default function myFunction() { + return 42; +} diff --git a/test/watchCases/side-effects/issue-7400/0/my-module/package.json b/test/watchCases/side-effects/issue-7400/0/my-module/package.json new file mode 100644 index 00000000000..6cb57d98708 --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/0/my-module/package.json @@ -0,0 +1,4 @@ +{ + "module": "index.js", + "sideEffects": false +} diff --git a/test/watchCases/side-effects/issue-7400/0/require-me.js b/test/watchCases/side-effects/issue-7400/0/require-me.js new file mode 100644 index 00000000000..b2eaf2c6b3d --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/0/require-me.js @@ -0,0 +1,5 @@ +import { myFunction } from "./my-module"; + +export function doStuff() { + return myFunction(); +} diff --git a/test/watchCases/side-effects/issue-7400/1/index.js b/test/watchCases/side-effects/issue-7400/1/index.js new file mode 100644 index 00000000000..080df54a9c0 --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/1/index.js @@ -0,0 +1,6 @@ +import { doStuff } from "./require-me"; + +it("should compile correctly", () => { + expect(doStuff()).toEqual(42); + expect(WATCH_STEP).toEqual("1"); +}); \ No newline at end of file diff --git a/test/watchCases/side-effects/issue-7400/webpack.config.js b/test/watchCases/side-effects/issue-7400/webpack.config.js new file mode 100644 index 00000000000..2bd35aa7c7e --- /dev/null +++ b/test/watchCases/side-effects/issue-7400/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + optimization: { + sideEffects: true + } +};