diff --git a/lib/JavascriptMetaInfoPlugin.js b/lib/JavascriptMetaInfoPlugin.js index 3eb7f48fc..e09d06749 100644 --- a/lib/JavascriptMetaInfoPlugin.js +++ b/lib/JavascriptMetaInfoPlugin.js @@ -28,7 +28,12 @@ class JavascriptMetaInfoPlugin { parser.hooks.call.for("eval").tap("JavascriptMetaInfoPlugin", () => { parser.state.module.buildInfo.moduleConcatenationBailout = "eval()"; parser.state.module.buildInfo.usingEval = true; - InnerGraph.bailout(parser.state); + const currentSymbol = InnerGraph.getTopLevelSymbol(parser.state); + if (currentSymbol) { + InnerGraph.addUsage(parser.state, null, currentSymbol); + } else { + InnerGraph.bailout(parser.state); + } }); parser.hooks.finish.tap("JavascriptMetaInfoPlugin", () => { let topLevelDeclarations = diff --git a/lib/index.js b/lib/index.js index 91146933d..dbe5cca06 100644 --- a/lib/index.js +++ b/lib/index.js @@ -394,6 +394,9 @@ module.exports = mergeExports(fn, { "DEP_WEBPACK_AGGRESSIVE_SPLITTING_PLUGIN" )(); }, + get InnerGraph() { + return require("./optimize/InnerGraph"); + }, get LimitChunkCountPlugin() { return require("./optimize/LimitChunkCountPlugin"); }, @@ -535,6 +538,9 @@ module.exports = mergeExports(fn, { get comparators() { return require("./util/comparators"); }, + get runtime() { + return require("./util/runtime"); + }, get serialization() { return require("./util/serialization"); }, diff --git a/lib/optimize/InnerGraph.js b/lib/optimize/InnerGraph.js index 413407b7f..8931bc31c 100644 --- a/lib/optimize/InnerGraph.js +++ b/lib/optimize/InnerGraph.js @@ -16,7 +16,7 @@ const { UsageState } = require("../ExportsInfo"); /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ -/** @typedef {Map | true>} InnerGraph */ +/** @typedef {Map | true>} InnerGraph */ /** @typedef {function(boolean | Set | undefined): void} UsageCallback */ /** @@ -75,7 +75,7 @@ exports.isEnabled = parserState => { /** * @param {ParserState} state parser state - * @param {TopLevelSymbol} symbol the symbol + * @param {TopLevelSymbol | null} symbol the symbol, or null for all symbols * @param {string | TopLevelSymbol | true} usage usage data * @returns {void} */ @@ -172,6 +172,26 @@ exports.inferDependencyUsage = state => { } if (isTerminal) { nonTerminal.delete(key); + + // For the global key, merge with all other keys + if (key === null) { + const globalValue = innerGraph.get(null); + if (globalValue) { + for (const [key, value] of innerGraph) { + if (key !== null && value !== true) { + if (globalValue === true) { + innerGraph.set(key, true); + } else { + const newSet = new Set(value); + for (const item of globalValue) { + newSet.add(item); + } + innerGraph.set(key, newSet); + } + } + } + } + } } } } diff --git a/test/configCases/inner-graph/eval-bailout/module.js b/test/configCases/inner-graph/eval-bailout/module.js new file mode 100644 index 000000000..ce9787c2d --- /dev/null +++ b/test/configCases/inner-graph/eval-bailout/module.js @@ -0,0 +1,15 @@ +import { a, b, c } from "./test"; + +export function x() { + a(); +} + +export function y() { + b(); + eval("x()"); +} + +export function z() { + c(); + y(); +} diff --git a/test/configCases/inner-graph/eval-bailout/webpack.config.js b/test/configCases/inner-graph/eval-bailout/webpack.config.js new file mode 100644 index 000000000..595359307 --- /dev/null +++ b/test/configCases/inner-graph/eval-bailout/webpack.config.js @@ -0,0 +1,27 @@ +const createTestCases = require("../_helpers/createTestCases"); +module.exports = createTestCases({ + nothing: { + usedExports: [], + expect: { + "./test": [] + } + }, + nonEval: { + usedExports: ["x"], + expect: { + "./test": ["a"] + } + }, + directEval: { + usedExports: ["y"], + expect: { + "./test": ["a", "b", "c"] + } + }, + indirectEval: { + usedExports: ["z"], + expect: { + "./test": ["a", "b", "c"] + } + } +}); diff --git a/types.d.ts b/types.d.ts index f44cb2bb7..0ce5c2bc8 100644 --- a/types.d.ts +++ b/types.d.ts @@ -9783,6 +9783,7 @@ declare class RuntimeChunkPlugin { */ apply(compiler: Compiler): void; } +type RuntimeCondition = undefined | string | boolean | SortableSet; declare class RuntimeModule extends Module { constructor(name: string, stage?: number); name: string; @@ -9829,7 +9830,8 @@ declare interface RuntimeRequirementsContext { codeGenerationResults: CodeGenerationResults; } type RuntimeSpec = undefined | string | SortableSet; -declare abstract class RuntimeSpecMap { +declare class RuntimeSpecMap { + constructor(clone?: RuntimeSpecMap); get(runtime: RuntimeSpec): T; has(runtime: RuntimeSpec): boolean; set(runtime?: any, value?: any): void; @@ -9840,7 +9842,8 @@ declare abstract class RuntimeSpecMap { values(): IterableIterator; readonly size?: number; } -declare abstract class RuntimeSpecSet { +declare class RuntimeSpecSet { + constructor(iterable?: any); add(runtime?: any): void; has(runtime?: any): boolean; [Symbol.iterator](): IterableIterator; @@ -11299,6 +11302,10 @@ declare interface TimestampAndHash { timestamp?: number; hash: string; } +declare class TopLevelSymbol { + constructor(name: string); + name: string; +} /** * Use a Trusted Types policy to create urls for chunks. @@ -12155,6 +12162,52 @@ declare namespace exports { }; } export namespace optimize { + export namespace InnerGraph { + export let bailout: (parserState: ParserState) => void; + export let enable: (parserState: ParserState) => void; + export let isEnabled: (parserState: ParserState) => boolean; + export let addUsage: ( + state: ParserState, + symbol: null | TopLevelSymbol, + usage: string | true | TopLevelSymbol + ) => void; + export let addVariableUsage: ( + parser: JavascriptParser, + name: string, + usage: string | true | TopLevelSymbol + ) => void; + export let inferDependencyUsage: (state: ParserState) => void; + export let onUsage: ( + state: ParserState, + onUsageCallback: (arg0?: boolean | Set) => void + ) => void; + export let setTopLevelSymbol: ( + state: ParserState, + symbol: TopLevelSymbol + ) => void; + export let getTopLevelSymbol: ( + state: ParserState + ) => void | TopLevelSymbol; + export let tagTopLevelSymbol: ( + parser: JavascriptParser, + name: string + ) => TopLevelSymbol; + export let isDependencyUsedByExports: ( + dependency: Dependency, + usedByExports: boolean | Set, + moduleGraph: ModuleGraph, + runtime: RuntimeSpec + ) => boolean; + export let getDependencyUsedByExportsCondition: ( + dependency: Dependency, + usedByExports: boolean | Set, + moduleGraph: ModuleGraph + ) => + | null + | false + | ((arg0: ModuleGraphConnection, arg1: RuntimeSpec) => ConnectionState); + export { TopLevelSymbol, topLevelSymbolTag }; + } export { AggressiveMergingPlugin, AggressiveSplittingPlugin, @@ -12280,6 +12333,59 @@ declare namespace exports { b: DependencyLocation ) => 0 | 1 | -1; } + export namespace runtime { + export let getEntryRuntime: ( + compilation: Compilation, + name: string, + options?: EntryOptions + ) => RuntimeSpec; + export let forEachRuntime: ( + runtime: RuntimeSpec, + fn: (arg0: string) => void, + deterministicOrder?: boolean + ) => void; + export let getRuntimeKey: (runtime: RuntimeSpec) => string; + export let keyToRuntime: (key: string) => RuntimeSpec; + export let runtimeToString: (runtime: RuntimeSpec) => string; + export let runtimeConditionToString: ( + runtimeCondition: RuntimeCondition + ) => string; + export let runtimeEqual: (a: RuntimeSpec, b: RuntimeSpec) => boolean; + export let compareRuntime: (a: RuntimeSpec, b: RuntimeSpec) => 0 | 1 | -1; + export let mergeRuntime: (a: RuntimeSpec, b: RuntimeSpec) => RuntimeSpec; + export let mergeRuntimeCondition: ( + a: RuntimeCondition, + b: RuntimeCondition, + runtime: RuntimeSpec + ) => RuntimeCondition; + export let mergeRuntimeConditionNonFalse: ( + a: undefined | string | true | SortableSet, + b: undefined | string | true | SortableSet, + runtime: RuntimeSpec + ) => undefined | string | true | SortableSet; + export let mergeRuntimeOwned: ( + a: RuntimeSpec, + b: RuntimeSpec + ) => RuntimeSpec; + export let intersectRuntime: ( + a: RuntimeSpec, + b: RuntimeSpec + ) => RuntimeSpec; + export let subtractRuntime: ( + a: RuntimeSpec, + b: RuntimeSpec + ) => RuntimeSpec; + export let subtractRuntimeCondition: ( + a: RuntimeCondition, + b: RuntimeCondition, + runtime: RuntimeSpec + ) => RuntimeCondition; + export let filterRuntime: ( + runtime: RuntimeSpec, + filter: (arg0: RuntimeSpec) => boolean + ) => undefined | string | boolean | SortableSet; + export { RuntimeSpecMap, RuntimeSpecSet }; + } export namespace serialization { export const register: ( Constructor: Constructor, @@ -12425,5 +12531,6 @@ declare namespace exports { LoaderContext }; } +declare const topLevelSymbolTag: unique symbol; export = exports;