Skip to content

feat: handle more expression types in isPure for better tree-shaking#20723

Merged
alexander-akait merged 1 commit into
mainfrom
claude/find-all-todos-om9SA
Apr 28, 2026
Merged

feat: handle more expression types in isPure for better tree-shaking#20723
alexander-akait merged 1 commit into
mainfrom
claude/find-all-todos-om9SA

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Mar 27, 2026

Copy link
Copy Markdown
Member

Summary

What kind of change does this PR introduce?

Add support for ArrayExpression, ObjectExpression and NewExpression in the isPure method for better tree-shaking. And also fix #20209.

Did you add tests for your changes?

Yes

Does this PR introduce a breaking change?

No

If relevant, what needs to be documented once your changes are merged or what have you already documented?

No

Use of AI

Partial

Copilot AI review requested due to automatic review settings March 27, 2026 21:00
@linux-foundation-easycla

linux-foundation-easycla Bot commented Mar 27, 2026

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: alexander-akait / name: Alexander Akait (1e36c86)
  • ✅ login: hai-x / name: Hai (1e36c86)

@changeset-bot

changeset-bot Bot commented Mar 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1e36c86

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (45b2358).

Install it locally:

  • npm
npm i -D webpack@https://pkg.pr.new/webpack@45b2358
  • yarn
yarn add -D webpack@https://pkg.pr.new/webpack@45b2358
  • pnpm
pnpm add -D webpack@https://pkg.pr.new/webpack@45b2358

@codecov

codecov Bot commented Mar 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.47368% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.40%. Comparing base (9211be0) to head (1e36c86).
⚠️ Report is 46 commits behind head on main.

Files with missing lines Patch % Lines
lib/javascript/JavascriptParser.js 86.66% 4 Missing ⚠️

❌ Your patch status has failed because the patch coverage (89.47%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your changes status has failed because you have indirect coverage changes. Learn more about Unexpected Coverage Changes and reasons for indirect coverage changes.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #20723      +/-   ##
==========================================
- Coverage   91.40%   91.40%   -0.01%     
==========================================
  Files         561      562       +1     
  Lines       55359    55439      +80     
  Branches    14610    14638      +28     
==========================================
+ Hits        50603    50676      +73     
- Misses       4756     4763       +7     
Flag Coverage Δ
integration 90.39% <89.47%> (-0.01%) ⬇️
test262 41.90% <2.63%> (-0.06%) ⬇️
unit 36.26% <2.63%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves webpack’s inner-graph/tree-shaking by teaching JavascriptParser.isPure to recognize additional AST expression types as pure, enabling dependency tracking through common “pure annotated call” patterns (e.g. object/array literals passed into /*#__PURE__*/ calls).

Changes:

  • Extend JavascriptParser.isPure to handle ArrayExpression, ObjectExpression, UnaryExpression, BinaryExpression, NewExpression, TaggedTemplateExpression, and ChainExpression.
  • Add a new inner-graph test case (isPureExpressions) and update existing inner-graph expectations (issue-12669) to reflect improved purity tracking.
  • Update stats snapshots and add a changeset entry.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/javascript/JavascriptParser.js Adds new isPure cases for more expression node types used by inner-graph analysis.
test/configCases/inner-graph/isPureExpressions/webpack.config.js New config-case expectations for used exports per dependency.
test/configCases/inner-graph/isPureExpressions/module.js New module exercising additional “pure” expression types for inner-graph.
test/configCases/inner-graph/issue-12669/webpack.config.js Updates expected used exports after improved purity recognition.
test/__snapshots__/StatsTestCases.basictest.js.snap Snapshot updates due to changed tree-shaking/stats output.
.changeset/ispure-more-expression-types.md Patch changeset documenting the tree-shaking improvement.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4902 to +4907
return false;
}
return this.isPure(
property.value,
/** @type {Range} */ (property.key.range)[1]
);

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In ObjectExpression, commentsStartPos isn’t advanced between properties, and computed keys use the incoming commentsStartPos. This can accidentally let a /*#__PURE__*/ block comment from an earlier property (or earlier in the expression) be picked up when evaluating a later computed key/value, causing false positives for purity. Consider iterating properties similarly to SequenceExpression/CallExpression args: update commentsStartPos per property (e.g., start at property.range[0] or previous property end) and advance it to property.range[1] after processing each property, so PURE annotations only apply to the immediately following expression.

Suggested change
return false;
}
return this.isPure(
property.value,
/** @type {Range} */ (property.key.range)[1]
);
commentsStartPos = /** @type {Range} */ (property.range)[1];
return false;
}
const pureFlag = this.isPure(
property.value,
/** @type {Range} */ (property.key.range)[1]
);
commentsStartPos = /** @type {Range} */ (property.range)[1];
return pureFlag;

Copilot uses AI. Check for mistakes.
export const neg = -u;
export const sum = b + 1;
export const inst = /*#__PURE__*/ new N();
export const used = "hello";

Copilot AI Mar 27, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new isPureExpressions inner-graph test covers Array/Object literals, Unary/Binary, and annotated new, but it doesn’t exercise the newly added ChainExpression or TaggedTemplateExpression handling in JavascriptParser.isPure. Adding at least one export that uses optional chaining (to produce a ChainExpression) and one annotated tagged template would help prevent regressions for the new cases.

Suggested change
export const used = "hello";
export const used = "hello";
function pureTag(strings, ...expressions) {
return strings[0];
}
export const chained = /*#__PURE__*/ a?.foo?.();
export const tagged = /*#__PURE__*/ pureTag`tagged ${used}`;

Copilot uses AI. Check for mistakes.
@codspeed-hq

codspeed-hq Bot commented Mar 27, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 32.3%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 3 regressed benchmarks
✅ 141 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory benchmark "asset-modules-source", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 265.3 KB 391.8 KB -32.3%
Memory benchmark "lodash", scenario '{"name":"mode-development","mode":"development"}' 4.1 MB 5.3 MB -22.23%
Memory benchmark "react", scenario '{"name":"mode-development-rebuild","mode":"development","watch":true}' 645.8 KB 863.5 KB -25.21%

Comparing claude/find-all-todos-om9SA (1e36c86) with main (a653ade)

Open in CodSpeed

@hai-x

hai-x commented Mar 28, 2026

Copy link
Copy Markdown
Member

LGTM.

@alexander-akait

Copy link
Copy Markdown
Member Author

/cc @hai-x Looks like we can reuse these tests and code to improve pure logic better, we can't merge it due EasyCLA (WIP on this)

@hai-x

hai-x commented Apr 7, 2026

Copy link
Copy Markdown
Member

we can reuse these tests and code to improve pure logic better

Sorry for delay. Do you mean reuse some tests from #20467?

@alexander-akait

Copy link
Copy Markdown
Member Author

@hai-x and from there and from here, we just add step by steps new handlers to see changes and expected behaviour

@hai-x

hai-x commented Apr 8, 2026

Copy link
Copy Markdown
Member

we just add step by steps new handlers to see changes and expected behaviour

Yeah, i think we can add ArrayExpression / ObjectExpression / NewExpression first, theoretically they are used at the top level more often than others. What do you think?

@alexander-akait

Copy link
Copy Markdown
Member Author

@hai-x Yeah, agree, let's try it

@hai-x hai-x force-pushed the claude/find-all-todos-om9SA branch 2 times, most recently from a6e89aa to 1ca8eb4 Compare April 10, 2026 19:37
@hai-x

hai-x commented Apr 10, 2026

Copy link
Copy Markdown
Member

we can't merge it due EasyCLA (WIP on this)

I just amend the PR author, and now it's fine.

@hai-x hai-x force-pushed the claude/find-all-todos-om9SA branch from a06e103 to 2ff3663 Compare April 10, 2026 19:57

@alexander-akait alexander-akait left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's merge for the next minor release

@hai-x

hai-x commented Apr 13, 2026

Copy link
Copy Markdown
Member

It’s also worth noting that this PR optimizes the following cases:

  1. Skips rendering an entire module when it is marked as side-effect-free.
// index.js
import "./pure"

// pure.js (⚠️ module is skipped now)
let arr = []; // `ArrayExpression` is pure here
export default {}; // `ObjectExpression` is pure here
  1. Skips rendering a re-exporting module when its target module is marked as side-effect-free. And it's why output has been optimized in the test/configCases/code-generation/re-export-namespace-concat and test/configCases/code-generation/re-export-namespace.
// index.js
import { ns } from "./reexport";
console.log(ns);

// reexport.js (⚠️ re-export module is skipped now)
export * as ns from "./pure";

// pure.js
let arr = []; // `ArrayExpression` is pure here
export default {}; // `ObjectExpression` is pure here

Co-authored-by: Alexander Akait <4567934+alexander-akait@users.noreply.github.com>
@hai-x hai-x force-pushed the claude/find-all-todos-om9SA branch from 2ff3663 to 1e36c86 Compare April 13, 2026 18:17
@github-actions

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging claude/find-all-todos-om9SA into main will be
98.93%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.77%100%100%98.77%91
examples
   build-common.js100%100%100%100%
   buildAll.js100%100%100%100%
   examples.js100%100%100%100%
   template-common.js98.21%100%100%98.21%72
examples/custom-javascript-parser
   test.filter.js100%100%100%100%
examples/custom-javascript-parser/internals
   acorn-parse.js100%100%100%100%
   meriyah-parse.js100%100%100%100%
   oxc-parse.js91.30%100%100%91.30%140, 142–143, 145, 147, 153–154, 161, 168, 90
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/typescript
   test.filter.js50%100%100%50%5
examples/virtual-modules
   test.filter.js100%100%100%100%
examples/wasm-bindgen-esm
   test.filter.js100%100%100%100%
examples/wasm-complex
   test.filter.js100%100%100%100%
examples/wasm-simple
   test.filter.js100%100%100%100%
examples/wasm-simple-source-phase
   test.filter.js100%100%100%100%
lib
   APIPlugin.js100%100%100%100%
   AbstractMethodError.js100%100%100%100%
   AsyncDependenciesBlock.js100%100%100%100%
   AsyncDependencyToInitialChunkError.js100%100%100%100%
   AutomaticPrefetchPlugin.js100%100%100%100%
   BannerPlugin.js100%100%100%100%
   Cache.js98.21%100%100%98.21%104
   CacheFacade.js100%100%100%100%
   CaseSensitiveModulesWarning.js100%100%100%100%
   Chunk.js99.72%100%100%99.72%37
   ChunkGraph.js100%100%100%100%
   ChunkGroup.js100%100%100%100%
   ChunkRenderError.js100%100%100%100%
   ChunkTemplate.js100%100%100%100%
   CleanPlugin.js98.72%100%100%98.72%206, 226, 382
   CodeGenerationError.js100%100%100%100%
   CodeGenerationResults.js100%100%100%100%
   CommentCompilationWarning.js100%100%100%100%
   CompatibilityPlugin.js100%100%100%100%
   Compilation.js98.55%100%100%98.55%1559, 1855, 1862, 1870, 1892, 2788, 3213, 3868, 3897, 3950–3951, 3955, 3960, 3976–3977, 3991–3992, 3997–3998, 4471, 4497, 498, 503, 5205, 5286, 5301, 5326–5327, 5329, 5651, 5656, 5662, 5665, 5677, 5679, 5683, 5699, 5714, 5746, 5800, 5824, 5938, 717–718
   Compiler.js99.56%100%100%99.56%1116–1117, 1125
   ConcatenationScope.js98.59%100%100%98.59%189
   ConcurrentCompilationError.js100%100%100%100%
   ConditionalInitFragment.js100%100%100%100%
   ConstPlugin.js100%100%100%100%
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js100%100%100%100%
   ContextModuleFactory.js97.75%100%100%97.75%258, 393, 418, 443, 447, 458
   ContextReplacementPlugin.js100%100%100%100%
   CssModule.js81.32%100%100%81.32%157, 162–177
   DefinePlugin.js98.92%100%100%98.92%158–159, 175, 194, 268
   DelegatedModule.js95.24%100%100%95.24%253–257
   DelegatedModuleFactoryPlugin.js98.15%100%100%98.15%106
   DelegatedPlugin.js100%100%100%100%
   DependenciesBlock.js100%100%100%100%
   Dependency.js98.13%100%100%98.13%371, 404
   DependencyTemplate.js100%100%100%100%
   DependencyTemplates.js100%100%100%100%
   DllEntryPlugin.js100%100%100%100%
   DllModule.js100%100%100%100%
   DllModuleFactory.js100%100%100%100%
   DllPlugin.js100%100%100%100%
   DllReferencePlugin.js100%100%100%100%
   DotenvPlugin.js97.88%100%100%97.88%237, 378, 391–392
   DynamicEntryPlugin.js100%100%100%100%
   EntryOptionPlugin.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   EnvironmentNotSupportAsyncWarning.js100%100%100%100%
   EnvironmentPlugin.js97.14%100%100%97.14%49
   ErrorHelpers.js100%100%100%100%
   EvalDevToolModulePlugin.js100%100%100%100%
   EvalSourceMapDevToolPlugin.js100%100%100%100%
   ExportsInfo.js100%100%100%100%
   ExportsInfoApiPlugin.js100%100%100%100%
   ExternalModule.js98.89%100%100%98.89%399–403, 542
   ExternalModuleFactoryPlugin.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FalseIIFEUmdWarning.js100%100%100%100%
   FileSystemInfo.js99.49%100%100%99.49%182, 2213–2214, 2217, 2228, 2239, 2250, 278, 3596, 3611, 3635
   FlagAllModulesAsUsedPlugin.js100%100%100%100%
   FlagDependencyExportsPlugin.js98.74%100%100%98.74%399, 401, 405
   FlagDependencyUsagePlugin.js100%100%100%100%
   FlagEntryExportAsUsedPlugin.js100%100%100%100%
   Generator.js100%100%100%100%
   GraphHelpers.js100%100%100%100%
   HarmonyLinkingError.js100%100%100%100%
   HookWebpackError.js100%100%100%100%
   HotModuleReplacementPlugin.js100%100%100%100%
   HotUpdateChunk.js100%100%100%100%
   IgnoreErrorModuleFactory.js100%100%100%100%
   IgnorePlugin.js100%100%100%100%
   IgnoreWarningsPlugin.js100%100%100%100%
   InitFragment.js100%100%100%100%
   InvalidDependenciesModuleWarning.js100%100%100%100%
   JavascriptMetaInfoPlugin.js100%100%100%100%
   LibManifestPlugin.js97.14%100%100%97.14%117, 120
   LibraryTemplatePlugin.js100%100%100%100%
   LoaderOptionsPlugin.js100%100%100%100%
   LoaderTargetPlugin.js100%100%100%100%
   MainTemplate.js100%100%100%100%
   ManifestPlugin.js100%100%100%100%
   Module.js98.50%100%100%98.50%1303, 1308, 1369, 1383, 1445, 1454
   ModuleBuildError.js100%100%100%100%
   ModuleDependencyError.js100%100%100%100%
   ModuleDependencyWarning.js100%100%100%100%
   ModuleError.js100%100%100%100%
   ModuleFactory.js100%100%100%100%
   ModuleFilenameHelpers.js98.85%100%100%98.85%106, 108
   ModuleGraph.js99.73%100%100%99.73%1004
   ModuleGraphConnection.js100%100%100%100%
   ModuleHashingError.js100%100%100%100%
   ModuleInfoHeaderPlugin.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   ModuleParseError.js100%100%100%100%
   ModuleProfile.js100%100%100%100%
   ModuleRestoreError.js100%100%100%100%
   ModuleSourceTypeConstants.js100%100%100%100%
   ModuleStoreError.js100%100%100%100%
   ModuleTemplate.js100%100%100%100%
   ModuleTypeConstants.js100%100%100%100%
   ModuleWarning.js100%100%100%100%
   MultiCompiler.js99.69%100%100%99.69%645
   MultiStats.js100%100%100%100%
   MultiWatching.js100%100%100%100%
   NoEmitOnErrorsPlugin.js100%100%100%100%
   NoModeWarning.js100%100%100%100%
   NodeStuffInWebError.js100%100%100%100%
   NodeStuffPlugin.js100%100%100%100%
   NormalModule.js97.67%100%100%97.67%1004, 1038, 1054, 1141, 1783, 1788–1798, 214, 726, 729, 746, 763
   NormalModuleFactory.js99.47%100%100%99.47%1067, 1376, 466, 478
   NormalModuleReplacementPlugin.js100%100%100%100%
   NullFactory.js100%100%100%100%
   OptimizationStages.js100%100%100%100%
   OptionsApply.js100%100%100%100%
   Parser.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   PrefetchPlugin.js100%100%100%100%
   ProgressPlugin.js98.75%100%100%98.75%446–447, 452, 454, 518
   ProvidePlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RecordIdsPlugin.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   RequireJsStuffPlugin.js100%100%100%100%
   ResolverFactory.js100%100%100%100%
   RuntimeGlobals.js100%100%100%100%
   RuntimeModule.js100%100%100%100%
   RuntimePlugin.js100%100%100%100%
   RuntimeTemplate.js100%100%100%100%
   SelfModuleFactory.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   SizeFormatHelpers.js100%100%100%100%
   SourceMapDevToolModuleOptionsPlugin.js100%100%100%100%
   SourceMapDevToolPlugin.js99.16%100%100%99.16%267–268, 610
   Stats.js100%100%100%100%
   Template.js100%100%100%100%
   TemplatedPathPlugin.js98.86%100%100%98.86%134–135
   UnhandledSchemeError.js100%100%100%100%
   UnsupportedFeatureWarning.js100%100%100%100%
   UseStrictPlugin.js100%100%100%100%
   WarnCaseSensitiveModulesPlugin.js100%100%100%100%
   WarnDeprecatedOptionPlugin.js100%100%100%100%
   WarnNoModeSetPlugin.js100%100%100%100%
   WatchIgnorePlugin.js100%100%100%100%
   Watching.js100%100%100%100%
   WebpackError.js96.97%100%100%96.97%44
   WebpackIsIncludedPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   buildChunkGraph.js99.87%100%100%99.87%326
   cli.js98.71%100%100%98.71%117, 469, 501, 543, 813
   formatLocation.js100%100%100%100%
   index.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
   webpack.js97.22%100%100%97.22%196, 218, 220
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModulesPlugin.js97.77%100%100%97.77%285, 309, 312, 364, 40
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%71, 83, 91
   MemoryCachePlugin.js95.83%100%100%95.83%33
   MemoryWithGcCachePlugin.js93.15%100%100%93.15%106, 113–114, 122, 89
   

./objects.js X bytes [built] [code generated]
chunk (runtime: a) no-used-exports-a.js (a) X bytes (javascript) X KiB (runtime) [entry] [rendered]
runtime modules X KiB 4 modules
runtime modules X KiB 5 modules

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is interesting, need investigate why we have more modules now...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's expected, because we now add a PureExpressionDependency for unused top-level symbols which is one object or array, and these dependencies include runtime conditions. For multiple entries/runtimes, we rely on RuntimeGlobals.runtimeId(the newly added) to determine whether the current chunk's runtime actually uses the symbol.

See:

runtimeRequirements.add(RuntimeGlobals.runtimeId);

@alexander-akait alexander-akait merged commit 45b2358 into main Apr 28, 2026
94 of 98 checks passed
@alexander-akait alexander-akait deleted the claude/find-all-todos-om9SA branch April 28, 2026 18:06
alexander-akait added a commit that referenced this pull request May 20, 2026
…w on deep import chains

The recursive descent through
`HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
adds two stack frames per module and overflows V8's stack on long
chains of side-effect-free imports — surfacing as
`RangeError: Maximum call stack size exceeded` at the
HarmonyImportSideEffectDependency frame (issue #20986).

The recursive code itself wasn't introduced in 5.107.0, but #20723's
expanded `isPure` analysis (treating ArrayExpression, ObjectExpression,
NewExpression, etc. as pure) flipped many more modules to
`buildMeta.sideEffectFree`, putting them inside the recursive walk for
the first time and exposing the latent overflow.

Convert the descent into an iterative DFS using an explicit frame
stack. Same semantics as the previous recursive form: per-module
`_isEvaluatingSideEffects` reentrancy guard, bailout recording on the
parent module for the dep that hit a side-effecting import,
aggregation via `addConnectionStates`, circular contributions skipped
(not folded) — so tree-shaking results are unchanged.
alexander-akait added a commit that referenced this pull request May 20, 2026
…ow on deep import chains

The recursive descent through
`HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
adds two stack frames per module and overflows V8's stack on long
chains of side-effect-free imports — surfacing as
`RangeError: Maximum call stack size exceeded` at the
HarmonyImportSideEffectDependency frame (issue #20986).

The recursive code itself wasn't introduced in 5.107.0, but #20723's
expanded `isPure` analysis (treating ArrayExpression, ObjectExpression,
NewExpression, etc. as pure) flipped many more modules to
`buildMeta.sideEffectFree`, putting them inside the recursive walk for
the first time and exposing the latent overflow.

Move the algorithm into a generator
(`_evaluateSideEffectsConnectionState`) that `yield`s a child generator
for each side-effect-free `HarmonyImportSideEffectDependency` instead
of recursing. `getSideEffectsConnectionState` drives it via an
explicit stack — a 12-line trampoline. The generator body is the same
shape as the previous recursive code so semantics and tree-shaking
results are unchanged.
alexander-akait added a commit that referenced this pull request May 20, 2026
…ow on deep import chains

The recursive descent through
`HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
adds two stack frames per module and overflows V8's stack on long
chains of side-effect-free imports — surfacing as
`RangeError: Maximum call stack size exceeded` at the
HarmonyImportSideEffectDependency frame (issue #20986).

The recursive code itself wasn't introduced in 5.107.0, but #20723's
expanded `isPure` analysis (treating ArrayExpression, ObjectExpression,
NewExpression, etc. as pure) flipped many more modules to
`buildMeta.sideEffectFree`, putting them inside the recursive walk for
the first time and exposing the latent overflow.

Move the algorithm into a file-scope generator `walkSideEffects` that
`yield`s a child generator for each side-effect-free
HarmonyImportSideEffectDependency instead of recursing into
`getSideEffectsConnectionState`. The method itself is now a small
trampoline driving the generator stack. The generator body is the same
shape as the previous recursive code, so semantics and tree-shaking
results are unchanged.

Tests:
- Unit tests in `test/NormalModule.unittest.js` cover a 20000-deep
  chain, a cycle, and bailout propagation.
- Integration case `test/configCases/side-effects/deep-import-chain`
  mirrors the structure of the reporter's reproduction repo
  (https://github.com/abecirovic-mo/webpack-5.107.0-repro) with a
  500-module chain that fits within the per-case compile budget.
alexander-akait added a commit that referenced this pull request May 20, 2026
…ow on deep import chains

The recursive descent through
`HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
adds two stack frames per module and overflows V8's stack on long
chains of side-effect-free imports — surfacing as
`RangeError: Maximum call stack size exceeded` at the
HarmonyImportSideEffectDependency frame (issue #20986).

The recursive code itself wasn't introduced in 5.107.0, but #20723's
expanded `isPure` analysis (treating ArrayExpression, ObjectExpression,
NewExpression, etc. as pure) flipped many more modules to
`buildMeta.sideEffectFree`, putting them inside the recursive walk for
the first time and exposing the latent overflow.

Move the algorithm into a file-scope generator `walkSideEffects` that
`yield`s a child generator for each side-effect-free
HarmonyImportSideEffectDependency instead of recursing into
`getSideEffectsConnectionState`. The method itself is now a small
trampoline driving the generator stack. The generator body is the same
shape as the previous recursive code, so semantics and tree-shaking
results are unchanged.

Tests:
- Unit tests in `test/NormalModule.unittest.js` cover a 20000-deep
  chain, a cycle, and bailout propagation.
- Integration case `test/configCases/side-effects/deep-import-chain`
  mirrors the structure of the reporter's reproduction repo
  (https://github.com/abecirovic-mo/webpack-5.107.0-repro) with a
  500-module chain that fits within the per-case compile budget.
alexander-akait added a commit that referenced this pull request May 20, 2026
…ow on deep import chains

The recursive descent through
`HarmonyImportSideEffectDependency.getModuleEvaluationSideEffectsState`
adds two stack frames per module and overflows V8's stack on long
chains of side-effect-free imports — surfacing as
`RangeError: Maximum call stack size exceeded` at the
HarmonyImportSideEffectDependency frame (issue #20986).

The recursive code itself wasn't introduced in 5.107.0, but #20723's
expanded `isPure` analysis (treating ArrayExpression, ObjectExpression,
NewExpression, etc. as pure) flipped many more modules to
`buildMeta.sideEffectFree`, putting them inside the recursive walk for
the first time and exposing the latent overflow.

Move the algorithm into a file-scope generator `walkSideEffects` that
`yield`s a child generator for each side-effect-free
HarmonyImportSideEffectDependency instead of recursing into
`getSideEffectsConnectionState`. The method itself is now a small
trampoline driving the generator stack. The generator body is the same
shape as the previous recursive code, so semantics and tree-shaking
results are unchanged.

Tests:
- Unit tests in `test/NormalModule.unittest.js` cover a 20000-deep
  chain, a cycle, and bailout propagation.
- Integration case `test/configCases/side-effects/deep-import-chain`
  mirrors the structure of the reporter's reproduction repo
  (https://github.com/abecirovic-mo/webpack-5.107.0-repro) with a
  500-module chain that fits within the per-case compile budget.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unused assets left by code optimization

3 participants