Skip to content

refactor(javascript): analyze the mangler's scopes with ScopeAnalyzer - #22428

Merged
alexander-akait merged 2 commits into
mainfrom
refactor/minifier-shared-scope-analyzer
Oct 4, 2026
Merged

alexander-akait merged 2 commits into
mainfrom
refactor/minifier-shared-scope-analyzer

Conversation

@alexander-akait

@alexander-akait alexander-akait commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Summary

webpack has two scope analyzers: lib/javascript/ScopeAnalyzer.js, used by module concatenation, and a port of terser's scope analysis inside the JavaScript minifier. This is the first step toward one: the mangler's scope analysis now runs on ScopeAnalyzer.

  • ScopeAnalyzer.js gains opt-in minifier subclasses (MinifierScopeAnalyzer, MinifierScope, MinifierVariable, MinifierLabel) that apply terser's scoping rules and record what the mangler reads. The existing classes and walk are unchanged, and the subclasses reuse the shared Scope/Reference model and the shared visit for node kinds both scope the same way.
  • analyzeManglingScopes in syntax-printer.js becomes a thin adapter; the mangler's own scope, definition and label model (about 1,000 lines) is deleted.
  • An empty scope no longer allocates a Map, and a definition's kinds are a bitmask instead of an array.

The compressor's scope analysis (figureOutScope, addChildScope) still uses the terser port; moving it is the next step.

Before / after against main:

Measure main This PR
CPU, lodash mangle (callgrind, CodSpeed flags) 2,797 M 2,788 M
Scope analysis alone, typescript.js (optimized) 365 ms 312 ms
Memory held by the analysis, typescript.js 35.3 MB 31.2 MB
Min. --max-old-space-size, typescript.js mangle 199 MB 196 MB
ScopeAnalyzer on lodash for concatenation (callgrind) 68.94 M 68.91 M

Output is byte-identical to main: lodash, acorn and typescript under 10 option sets (code, source maps, nameCache, format.ast, spidermonkey output, errors), and 276,961 corpus comparisons (terser, swc, oxc) with 0 mismatches.

What kind of change does this PR introduce?

refactor

Did you add tests for your changes?

Yes: the scope parity tests now run against the new model, and one test covers it directly. The existing minifier suites and the concatenation config cases cover the rest.

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?

n/a. Internal refactor with no user-facing change, so no changeset.

Use of AI

Written with Claude Code: it made the changes, ran the differential checks against main and measured CPU and memory. The author reviews every commit.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Tgxgyb4N35ndP6cC4KcrXC


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved minified output handling for nested scopes, declarations, and name references, including legacy browser scope behavior.
  • Refactor
    • Consolidated scope handling used during name mangling.
  • Tests
    • Expanded coverage for scope relationships, bindings, block and catch scopes, and enclosed variables.
    • Updated the expected error wording for unsupported syntax.

The JS minifier's mangler had its own port of terser's `figure_out_scope`.
It now runs on `ScopeAnalyzer`: a `MinifierScopeAnalyzer` subclass reuses
the shared walk, `Scope`/`Variable` model and binding index, and adds only
terser's scoping rules. Output is byte-identical.

Claude-Session: https://claude.ai/code/session_01Tgxgyb4N35ndP6cC4KcrXC
@changeset-bot

changeset-bot Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9f8bfca

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions Bot added the area: parser JavaScript parsing and dependency analysis (lib/javascript, lib/dependencies) label Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

This PR is packaged and the instant preview is available (63f2330).

Install it locally:

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

@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: webpack/webpack/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e1bc3ca3-8ce9-42c5-bef3-aefacd883480
📥 Commits

Reviewing files that changed from the base of the PR and between 3ed2cdd and 9f8bfca.

📒 Files selected for processing (1)
  • lib/javascript/syntax-printer.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The change adds minifier-specific scope analysis to ScopeAnalyzer.js and updates syntax-printer.js to use that analysis for mangling. Tests now inspect recursive scope trees and check bindings, scope links, block and catch scopes, and enclosed variables.

Changes

Minifier scope analysis

Layer / File(s) Summary
Scope model and traversal
lib/javascript/ScopeAnalyzer.js
Adds minifier scope, variable, and label records, declaration flags, and traversal that builds scopes and bindings.
Reference resolution and analyzer exports
lib/javascript/ScopeAnalyzer.js
Resolves references, records globals and mangling metadata, and exposes the analyzer and related types and constants through analyzeScope.
Printer mangling integration
lib/javascript/syntax-printer.js
Replaces the local scope analyzer with analyzeMinifierScope and updates mangling code to use the shared scope model.
Scope-analysis tests
test/unitCases/syntax-printer.unittest.js
Updates comparisons for recursive scopes and adds checks for the minifier scope model, bindings, scope links, and enclosed variables.

Priority: ⬇️ Low

Change: Refactor

Merge Risk: ⚪ Minimal · up to 9f8bf

The change integrates minification with the new scope analyzer, and the available evidence identifies no concrete user-facing failure or merge-blocking risk. Normal test checks remain appropriate.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 3ed2c

The inspected path preserves dynamic-name protections and stops processing on analysis errors. No introduced security issue was established, but equivalence with the previous version could not be fully confirmed.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is source or AST input influencing scope metadata and generated JavaScript. A semantic regression could propagate into generated bundles, but the inspected integration does not establish a new network-facing or privileged execution entrypoint.

Trust Boundaries and Controls

  • observed — Scope-derived controls remain enforced by the printer: undeclared names, retained exports, and dynamic-name scopes are excluded from mangling under the corresponding options.

Resilience and Maintainability Implications

  • observed — Analysis errors propagate out of the inspected minification path before subsequent mangling and printing. Its asynchronous wrapper has no catch-and-retry path. Compressor-field release is disabled when the normal path retains or receives a caller-owned tree.
🚥 Pre-merge checks | ✅ 3 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title uses the required Conventional Commit form and accurately describes the scope-analysis refactor. The branch prefix is not provided, so the required prefix match cannot be verified. Provide the branch name or its prefix so the refactor type can be checked against it.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 ast-grep (0.45.3)
lib/javascript/syntax-printer.js

ast-grep timed out on this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Types Coverage

Coverage after merging refactor/minifier-shared-scope-analyzer into main will be
97.28%
Coverage Report…truncated

FileStmtsBranchesFuncsLinesUncovered Lines
bin
   webpack.js98.82%100%100%98.82%103
declarations
   WebpackOptions.ts99.94%100%100%99.94%4183
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.js100%100%100%100%
examples/markdown
   webpack.config.mjs100%100%100%100%
examples/module-federation
   test.filter.js100%100%100%100%
examples/reexport-components
   test.filter.js100%100%100%100%
examples/ssr
   test.filter.js100%100%100%100%
examples/typescript
   test.filter.js100%100%100%100%
examples/typescript-non-erasable
   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-emscripten
   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
   Chunk.js100%100%100%100%
   Compilation.js98.59%100%100%98.59%1757, 2078, 2085, 2093, 2115, 2118, 23, 3051, 3530–3531, 3563, 4152, 4156, 4283, 4304, 4429, 4466, 4473, 4526–4527, 4531, 4536, 4552–4553, 4567–4568, 4573–4574, 5097, 5123, 5953, 5970, 5988, 6004, 6019, 6044, 6047, 6415, 6418–6419, 6424, 6439, 6522, 6546, 6660, 837–838
   Compiler.js98.81%100%100%98.81%1116, 1153, 1156, 1336–1337, 1345, 848, 934, 975
   Dependency.js100%100%100%100%
   DependencyTemplate.js100%100%100%100%
   EntryPlugin.js100%100%100%100%
   Entrypoint.js100%100%100%100%
   ExternalsPlugin.js100%100%100%100%
   FileSystemInfo.js100%100%100%100%
   InitFragment.js100%100%100%100%
   LibraryTemplatePlugin.js100%100%100%100%
   ModuleFilenameHelpers.js100%100%100%100%
   ModuleNotFoundError.js100%100%100%100%
   MultiCompiler.js98.88%100%100%98.88%460, 532, 719, 784
   NormalModule.js100%100%100%100%
   NullFactory.js100%100%100%100%
   ProgressPlugin.js100%100%100%100%
   RawModule.js100%100%100%100%
   RequestShortener.js100%100%100%100%
   SingleEntryPlugin.js100%100%100%100%
   WebpackError.js100%100%100%100%
   index.js99.78%100%100%99.78%206
   webpack.js97.84%100%100%97.84%278, 300, 302
lib/asset
   AssetBytesGenerator.js100%100%100%100%
   AssetBytesParser.js100%100%100%100%
   AssetGenerator.js100%100%100%100%
   AssetModule.js100%100%100%100%
   AssetModulesPlugin.js98.15%100%100%98.15%338, 362, 365, 495, 57, 62
   AssetParser.js100%100%100%100%
   AssetSourceGenerator.js100%100%100%100%
   AssetSourceParser.js100%100%100%100%
   RawDataUrlModule.js100%100%100%100%
   WebManifestGenerator.js100%100%100%100%
   WebManifestParser.js100%100%100%100%
lib/async-modules
   AsyncModuleHelpers.js100%100%100%100%
   AwaitDependenciesInitFragment.js100%100%100%100%
   InferAsyncModulesPlugin.js100%100%100%100%
   isGeneratorLowered.js100%100%100%100%
lib/bun
   BunTargetPlugin.js100%100%100%100%
lib/cache
   AddBuildDependenciesPlugin.js100%100%100%100%
   AddManagedPathsPlugin.js100%100%100%100%
   Cache.js96.49%100%100%96.49%10, 104
   CacheFacade.js100%100%100%100%
   IdleFileCachePlugin.js97.92%100%100%97.92%75, 87, 95
   MemoryCachePlugin.js92%100%100%92%33, 42
   MemoryWithGcCachePlugin.js93.42%100%100%93.42%106, 120–121, 130, 88
   PackFileCacheStrategy.js96.54%100%100%96.54%1304, 1404, 1408, 1470, 1716, 1800, 1823, 1853, 682, 701, 711–713, 715, 731–732, 737, 740, 742, 747, 752, 777, 783, 817, 823, 829, 834, 845, 854, 859–860, 862, 879, 885–886, 888
   ResolverCachePlugin.js100%100%100%100%
   getLazyHashedEtag.js100%100%100%100%
   mergeEtags.js100%100%100%100%
lib/config
   OptionsApply.js100%100%100%100%
   PlatformPlugin.js100%100%100%100%
   WebpackOptionsApply.js100%100%100%100%
   WebpackOptionsDefaulter.js100%100%100%100%
   browserslistTargetHandler.js100%100%100%100%
   cli.js98.61%100%100%98.61%116, 546, 578, 628, 9
   defaults.js99.44%100%100%99.44%1623–1625, 1633, 283, 286, 291, 295
   defineConfig.js100%100%100%100%
   getClaimedAssetTypes.js100%100%100%100%
   normalization.js99.12%100%100%99.12%279–280, 346, 361
   target.js100%100%100%100%
   validateSchema.js94.67%100%100%94.67%100, 87, 89, 98
lib/container
   ContainerEntryDependency.js100%100%100%100%
   ContainerEntryModule.js100%100%100%100%
   ContainerEntryModuleFactory.js100%100%100%100%
   ContainerExposedDependency.js100%100%100%100%
   ContainerPlugin.js100%100%100%100%
   ContainerReferencePlugin.js100%100%100%100%
   FallbackDependency.js100%100%100%100%
   FallbackItemDependency.js100%100%100%100%
   FallbackModule.js100%100%100%100%
   FallbackModuleFactory.js100%100%100%100%
   HoistContainerReferencesPlugin.js100%100%100%100%
   ModuleFederationPlugin.js100%100%100%100%
   RemoteModule.js100%100%100%100%
   RemoteRuntimeModule.js100%100%100%100%
   RemoteToExternalDependency.js100%100%100%100%
   declaredRemotes.js100%100%100%100%
   moduleFederationHooks.js100%100%100%100%
   options.js100%100%100%100%
lib/context
   ContextExclusionPlugin.js100%100%100%100%
   ContextModule.js99.88%100%100%99.88%1494
   ContextModuleFactory.js95.66%100%100%95.66%230, 257, 276, 280–281, 289, 465, 486, 491, 532, 541, 543, 547, 550, 556–557
   ContextReplacementPlugin.js100%100%100%100%
lib/css
   CssGenerator.js99.47%100%100%99.47%1085, 136, 842
   CssInjectStyleRuntimeModule.js100%100%100%100%
   CssLoadingRuntimeModule.js100%100%100%100%
   CssModule.js83.16%100%100%83.16%197, 202–210, 212–217
   CssModulesPlugin.js99.86%100%100%99.86%886
   CssParser.js99.47%100%100%99.47%1037, 1068, 2307, 24, 2941, 2944, 2950, 2991, 2994, 3033, 3036, 3373, 4907, 4910, 921
   CssServerStylesRuntimeModule.js100%100%100%100%
   conventions.js98.65%100%100%98.65%93
   cssMinify.js97.44%100%100%97.44%158, 167
   data.js99.84%100%100%99.84%229, 4150
   syntax-parser.js99.73%100%100%99.73%10242–10243, 10280, 10461, 10507, 12735, 13233, 13586, 15270, 2413, 3112, 3114, 3119, 3138, 3883, 3889, 7173, 7211, 7218, 9707–9708
   syntax-printer.js99.27%100%100%99.27%2339–2340, 2342–2343, 2345–2346, 2351, 2612–2615, 2707, 3496, 3629, 3738, 4237, 4495, 5284, 629
   syntax.js100%100%100%100%
   topologicalSort.js100%100%100%100%
lib/debug
   ProfilingPlugin.js96.22%100%100%96.22%169, 425, 554, 561, 570, 574, 583, 588, 594, 599, 92
lib/define
   DefinePlugin.js98.85%100%100%98.85%1495, 268–269, 285, 304, 378, 936, 938, 943, 967
   DotenvPlugin.js98.07%100%100%98.07%391, 439, 452–453
   EnvironmentPlugin.js97.14%100%100%97.14%49
lib/deno
   DenoTargetPlugin.js100%100%100%100%
lib/dependencies
   ConstDependency.js100%100%100%100%
   ContextElementDependency.js100%100%100%100%
   NullDependency.js100%100%100%100%
lib/dependencies/amd
   AMDDefineDependency.js100%100%100%100%
   AMDDefineDependencyParserPlugin.js100%100%100%100%
   AMDPlugin.js99.31%100%100%99.31%54
   AMDRequireArrayDependency.js100%100%100%100%
   AMDRequireContextDependency.js100%100%100%100%
   AMDRequireDependenciesBlock.js100%100%100%100%
   AMDRequireDependenciesBlockParserPlugin.js100%100%100%100%
   AMDRequireDependency.js100%100%100%100%
   AMDRequireItemDependency.js100%100%100%100%
   AMDRuntimeModules.js95.45%100%100%

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.01%. Comparing base (62f7a27) to head (9f8bfca).

Additional details and impacted files
@@           Coverage Diff            @@
##             main   #22428    +/-   ##
========================================
  Coverage   96.01%   96.01%            
========================================
  Files         772      772            
  Lines      121639   121757   +118     
  Branches    39412    39443    +31     
========================================
+ Hits       116791   116906   +115     
- Misses       4848     4851     +3     
Flag Coverage Δ
css-parsing 25.36% <6.74%> (-0.17%) ⬇️
html5lib 27.08% <6.74%> (-0.19%) ⬇️
integration 83.31% <6.74%> (-0.51%) ⬇️
minify-corpora 75.85% <98.34%> (-0.99%) ⬇️
syntax-equivalence 78.87% <ø> (ø)
test262 55.70% <86.86%> (+0.06%) ⬆️
unit 77.42% <100.00%> (+0.02%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Generated code size

Comparing base (62f7a27) to head (9f8bfca). Merging this pull request will not change the code webpack generates.

What moved Tests Assets Gzip Raw Test edit
Changed, test untouched 0 0 — — —
Changed, test edited 0 0 — — —
New 0 0 — — —
Deleted 0 0 — — —
Unchanged — 8337 — — —

Read Changed, test untouched first: the test is byte-identical on both sides, so webpack generated the difference. Changed, test edited moved partly because the test did — Test edit is how many bytes the test gained, and a bundle that grew by less is not a regression. New and Deleted are whole files, not deltas — a new asset was created, not grown, so it reads gray (⚪ added) — and adding tests cannot bury a real change. Gzip decides, because that is what users download; raw is the tiebreak, and brotli and zstd are per asset below.

No asset changed size.

No runtime gained or lost a runtime module.

Built test/configCases with the defaults a user gets: 2308 test(s), 8337 asset(s), 96 emitted nothing.

@codspeed

codspeed Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 34.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

⚡ 2 improved benchmarks
✅ 400 untouched benchmarks
⏩ 6 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Memory benchmark "css-modules", scenario '{"name":"mode-development","mode":"development"}' 12.5 MB 8.9 MB +40.39%
⚡ Memory benchmark "future-defaults", scenario '{"name":"mode-production","mode":"production"}' 20.3 MB 15.8 MB +28.48%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing refactor/minifier-shared-scope-analyzer (9f8bfca) with main (62f7a27)

Open in CodSpeed

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: parser JavaScript parsing and dependency analysis (lib/javascript, lib/dependencies)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant