Skip to content

perf: avoid parsing concatenated module codegen#14852

Draft
LingyuCoder wants to merge 9 commits into
mainfrom
seal/concat-local-ident
Draft

perf: avoid parsing concatenated module codegen#14852
LingyuCoder wants to merge 9 commits into
mainfrom
seal/concat-local-ident

Conversation

@LingyuCoder

@LingyuCoder LingyuCoder commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add the opt-in experiments.fasterModuleConcatenation switch, defaulting to false
  • preserve the previous generated-source parse, diagnostics, dependency hashes, source replacements, and init-fragment rendering when the experiment is disabled
  • when enabled, capture a compact identifier snapshot during the existing make-time parser traversal and combine it with dependency-template/init-fragment deltas during concatenated-module code generation
  • deconflict dynamically generated bindings for JavaScript, JSON, CSS, assets, externals, async imports, injected helpers, and modern-module output without reparsing generated inner-module sources

The earlier approach in #14033 added extra AST traversal and persisted high-cardinality identifier/reference maps for every eligible ESM module. That expanded make, allocation, serialization, and cache-restore work while still rebuilding name-analysis state during code generation. This version fuses collection into an existing visitor, stores range-only records, and resolves generated bindings lazily from code-generation metadata.

The CodSpeed benchmarks that exercise module concatenation explicitly enable the experiment, so the performance report compares the new path against the legacy implementation. The previous always-on head reported a 13.93% overall improvement and a 14.80% improvement in rust@concatenate_module_code_generation; the experiment-gated head will be evaluated by the new CI run.

Related links

Checklist

  • Tests updated.
  • Documentation updated.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

📦 Binary Size-limit

Comparing c1aa22c to fix(worker): add module type when options are omitted in module output (#14874) by Elecmonkey

❌ Size increased by 152.00KB from 66.64MB to 66.79MB (⬆️0.22%)

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Rsdoctor Bundle Diff Analysis

Found 5 projects in monorepo, 0 projects with changes.

📊 Quick Summary
Project Total Size Gzip Size Change Gzip Change
popular-libs 1.7 MB 552.1 KB 0 0
react-10k 5.6 MB 1.3 MB 0 0
react-1k 823.1 KB 217.0 KB 0 0
react-5k 2.7 MB 663.8 KB 0 0
ui-components 4.9 MB 1.4 MB 0 0

Generated by Rsdoctor GitHub Action

@codspeed-hq

codspeed-hq Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will improve performance by 14.17%

⚠️ 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 improved benchmarks
✅ 40 untouched benchmarks
⏩ 47 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation bundle@threejs-production-sourcemap 192.9 ms 155 ms +24.51%
Simulation rust@concatenate_module_code_generation 45.1 ms 39.1 ms +15.33%
Simulation bundle@misc-production-sourcemap 3.7 s 3.5 s +3.65%

Tip

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


Comparing seal/concat-local-ident (c1aa22c) with main (a5dfc7e)

Open in CodSpeed

Footnotes

  1. 47 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.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
rslib ✅ success
rspress ✅ success
rstest ✅ success
examples ❌ failure
modernjs ✅ success
rsdoctor ✅ success
rsbuild ✅ success
rsbuild-rsc-plugin ✅ success
devserver ✅ success
lynx-stack ❌ failure
plugin ✅ success

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
lynx-stack ✅ success

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
rstest ✅ success
lynx-stack ✅ success
rspress ✅ success
modernjs ✅ success
rslib ✅ success
plugin ✅ success
rsdoctor ✅ success
examples ❌ failure
rsbuild ✅ success
rsbuild-rsc-plugin ✅ success
devserver ✅ success

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploying rspack with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5432a85
Status: ✅  Deploy successful!
Preview URL: https://98671a9e.rspack-v2.pages.dev
Branch Preview URL: https://seal-concat-local-ident.rspack-v2.pages.dev

View logs

@LingyuCoder

LingyuCoder commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

CodSpeed Optimization Log

Round Head Change Validation / result Decision
Always-on baseline 878ec3d1 snapshot + delta path enabled unconditionally CodSpeed: overall +13.93%, concatenate_module_code_generation +14.80%, 0 regressions Retained as historical performance signal
Experiment switch 5432a854 add default-off experiments.fasterModuleConcatenation; keep legacy behavior when disabled Full local build/tests passed; CI completed except excluded binary size Retained
Init-fragment hash 7d6ef9ec include detached init fragments in fast-path codegen hashes while preserving legacy hash byte streams Binding build; rspack_core 121 tests; JS 8822 passed / 4 skipped; workspace all-target check; two independent reviews with no P0-P3 Retained
Final c1aa22c7 enforce await-dependency fragment binding consistency in release builds and add merge invariant tests Binding + JS builds; rspack_core 124 tests + doctests; JS 8822 passed / 4 skipped; CLI 89 passed / 1 skipped; workspace all-target check; formatting/diff checks; independent review with no P0-P3 Retained; all acceptance gates complete

Final gates for exact head c1aa22c7b60315433944b6ee77fbac8d1aee88a7:

  • CodSpeed: report shows overall +14.17%, rust@concatenate_module_code_generation +15.33% (45.1 ms → 39.1 ms), 3 improved / 40 untouched / 0 regressed.
  • Standard CI: 30 success / 6 skipped. The only failures are excluded Binary Size Limit and its directly derived Test Required Check; the aggregate log reports the ninth dependency as failure, which is size-limit in .github/workflows/ci.yml.
  • Ecosystem CI: source 29736334058 succeeded. Downstream 29736348983 is baseline-equivalent: modernjs, rslib, rstest, devserver, rspress, rsbuild-rsc-plugin, rsdoctor, lynx-stack, plugin, and rsbuild all passed; only examples failed with the established Next.js 16.2.6 Not implemented cache.type filesystem baseline issue.
  • Review: Copilot rereview completed on the exact head and no unresolved review threads remain.
  • CodSpeed explanation requested in this comment.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
devserver ✅ success
modernjs ✅ success
rslib ✅ success
rstest ✅ success
lynx-stack ✅ success
examples ❌ failure
rsbuild ✅ success
plugin ✅ success
rsdoctor ✅ success
rsbuild-rsc-plugin ✅ success
rspress ✅ success

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

This PR introduces an opt-in experiment (experiments.fasterModuleConcatenation, default false) to speed up module concatenation by reusing identifier/scope information captured during parsing and avoiding a second parse during concatenated-module code generation. It propagates the experiment flag through JS/Rust config surfaces, updates concatenation/name-deconflict mechanics across multiple generators/templates, and adds targeted test/benchmark coverage for the new path.

Changes:

  • Add the experiments.fasterModuleConcatenation option end-to-end (JS config types/defaults → binding API → Rust core options/builders) and enable it in relevant benchmarks.
  • Capture and persist a compact concatenation-scope snapshot during the existing parser traversal, then use it during concatenated-module and ESM-library codegen to deconflict identifiers without reparsing.
  • Update multiple dependency templates/generators (JS/JSON/CSS/assets/externals, etc.) to cooperate with the new faster concatenation mechanism, plus new/updated test cases and snapshots.

Reviewed changes

Copilot reviewed 73 out of 74 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
xtask/benchmark/benches/groups/compilation_stages.rs Enable the new experiment in the concatenate-stage benchmark compiler configuration.
xtask/benchmark/benches/groups/bundle/util.rs Enable the new experiment in the basic benchmark compiler builder experiments config.
website/docs/zh/config/experiments.mdx Document experiments.fasterModuleConcatenation in Chinese docs (type/default/usage).
website/docs/en/config/experiments.mdx Document experiments.fasterModuleConcatenation in English docs (type/default/usage).
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/wrapper-b.js New ESM output deconflict fixture for destructured exports (wrapper B).
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/wrapper-a.js New ESM output deconflict fixture for destructured exports (wrapper A).
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/rspack.config.js New test config enabling faster concatenation experiment for modern-module output.
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/index.js New ESM output deconflict test asserting bindings don’t collide.
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/api-b.cjs New CJS fixture module B for destructured export collision test.
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/api-a.cjs New CJS fixture module A for destructured export collision test.
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/snapshots/runtimeModeSnapshot/esm.snap.txt Add runtime-mode snapshot for the new destructured export collision ESM output case.
tests/rspack-test/esmOutputCases/deconflict/destructured-export-name-collision/snapshots/esm.snap.txt Add webpack-mode snapshot for the new destructured export collision ESM output case.
tests/rspack-test/esmOutputCases/deconflict/destructured-direct-export/test.config.js Add snapshot filtering to focus on the relevant wrapper region in output.
tests/rspack-test/esmOutputCases/deconflict/destructured-direct-export/rspack.config.js New ESM output test config enabling faster concatenation experiment.
tests/rspack-test/esmOutputCases/deconflict/destructured-direct-export/index.js New ESM output test for a destructured direct export path.
tests/rspack-test/esmOutputCases/deconflict/destructured-direct-export/snapshots/runtimeModeSnapshot/esm.snap.txt Add runtime-mode snapshot for destructured direct export case.
tests/rspack-test/esmOutputCases/deconflict/destructured-direct-export/snapshots/esm.snap.txt Add webpack-mode snapshot for destructured direct export case.
tests/rspack-test/esmOutputCases/deconflict/codegen-generated-identifiers/rspack.config.js New ESM output test config enabling experiment for identifier deconflict coverage.
tests/rspack-test/esmOutputCases/deconflict/codegen-generated-identifiers/index.js New test asserting deconflict of identifiers generated by dependency templates.
tests/rspack-test/esmOutputCases/deconflict/codegen-generated-identifiers/generated-default.js New fixture module exporting a default value for snapshot assertions.
tests/rspack-test/esmOutputCases/deconflict/codegen-generated-identifiers/data.json New JSON fixture to exercise placeholder/sentinel-related behavior.
tests/rspack-test/esmOutputCases/deconflict/codegen-generated-identifiers/snapshots/esm.snap.txt Add snapshot for generated-identifier deconflict ESM output case.
tests/rspack-test/errorCases/faster-module-concatenation.js Add error-case coverage verifying legacy parse behavior is preserved when experiment is off.
tests/rspack-test/defaultsCases/experiments/faster-module-concatenation.js Add defaults-case coverage for experiments.fasterModuleConcatenation.
tests/rspack-test/defaultsCases/default/base.js Update default options snapshot to include fasterModuleConcatenation: false.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/wrapper-b.js New concatenate-modules config case fixture (wrapper B) for destructured CJS export collision.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/wrapper-a.js New concatenate-modules config case fixture (wrapper A) for destructured CJS export collision.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/rspack.config.js New config case enabling experiment + concatenation to validate deconfliction.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/package.json Add sideEffects=false fixture metadata for the concatenation config case.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/index.js New test asserting concatenated destructured bindings are deconflicted in emitted output.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/api-b.cjs New CJS fixture B for concatenation deconflict config case.
tests/rspack-test/configCases/concatenate-modules/destructured-cjs-export-collision/api-a.cjs New CJS fixture A for concatenation deconflict config case.
packages/rspack/src/config/types.ts Expose experiments.fasterModuleConcatenation in TypeScript config types.
packages/rspack/src/config/normalization.ts Add normalized experiments shape support for the new option.
packages/rspack/src/config/defaults.ts Default experiments.fasterModuleConcatenation to false.
crates/rspack/tests/snapshots/defaults__default_options.snap Update Rust defaults snapshot to include faster_module_concatenation: false.
crates/rspack/src/builder/mod.rs Add builder plumbing for faster_module_concatenation.
crates/rspack_sources/src/source.rs Extend AsAny to support Arc downcasting via into_any_arc.
crates/rspack_sources/src/replace_source.rs Add in-place update helper for owned replacement contents.
crates/rspack_plugin_rstest/src/parser_plugin.rs Gate ModulePathNameDependency range tracking on experiment flag.
crates/rspack_plugin_rstest/src/module_path_name_dependency.rs Track replacement ranges and deconflict __dirname/__filename via concatenation scope when faster path is enabled.
crates/rspack_plugin_json/src/lib.rs Register/deconflict JSON namespace export through generated top-level symbols under faster concatenation.
crates/rspack_plugin_javascript/src/visitors/semicolon.rs Collect concatenation-scope identifier snapshot during semicolon insertion traversal when needed.
crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs Expose compiler_options() to allow external plugins to query experiments.
crates/rspack_plugin_javascript/src/parser_plugin/esm_export_dependency_parser_plugin.rs Preserve range info for default class export declarations under faster concatenation.
crates/rspack_plugin_javascript/src/parser_plugin/compatibility_plugin.rs Adjust ConstDependency creation to carry concatenation identifier info under faster path.
crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs Store concatenation-scope snapshots and, for faster concatenated codegen, persist rendered init fragments separately.
crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs Deconflict provided dependency identifiers via generated top-level symbols under faster concatenation.
crates/rspack_plugin_javascript/src/dependency/esm/mod.rs Re-export NamedDeclarationInfo for faster concatenation default export handling.
crates/rspack_plugin_javascript/src/dependency/esm/import_meta_rsc_dependency.rs Deconflict ImportMeta RSC binding via generated top-level symbols when faster path is active.
crates/rspack_plugin_javascript/src/dependency/esm/esm_import_specifier_dependency.rs Deconflict import bindings / remove original ranges appropriately for faster concatenation.
crates/rspack_plugin_javascript/src/dependency/esm/esm_import_dependency.rs Deconflict import vars and async-deps binding handling for faster concatenation.
crates/rspack_plugin_javascript/src/dependency/esm/esm_export_imported_specifier_dependency.rs Handle faster concatenation by removing replaced ranges and emitting export references without reparsing.
crates/rspack_plugin_javascript/src/dependency/esm/esm_export_header_dependency.rs Remove original export header range from snapshot tracking under faster concatenation.
crates/rspack_plugin_javascript/src/dependency/esm/esm_export_expression_dependency.rs Add range-aware named default export declaration support and generated default export symbol handling.
crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_header_dependency.rs Remove original range tracking under faster concatenation when rewriting require.resolve header.
crates/rspack_plugin_javascript/src/dependency/commonjs/require_header_dependency.rs Remove original range tracking under faster concatenation when rewriting require header.
crates/rspack_plugin_esm_library/src/render.rs Render concatenated module sources with placeholder replacement without reparsing when experiment is enabled.
crates/rspack_plugin_esm_library/src/plugin.rs Pass experiment flag into concatenation scope for ESM library rendering.
crates/rspack_plugin_esm_library/src/link.rs Avoid codegen-time reparsing by populating concatenation info from snapshots when faster path is enabled.
crates/rspack_plugin_css/src/parser_and_generator/generator.rs Deconflict CSS export identifiers using generated top-level symbols under faster concatenation.
crates/rspack_plugin_asset/src/lib.rs Deconflict asset namespace export binding via generated top-level symbols under faster concatenation.
crates/rspack_core/src/utils/concatenation_scope.rs Add faster-path helpers: range removals, used-name tracking, generated symbol placeholders, and module reference placeholder tracking.
crates/rspack_core/src/utils/concatenated_module_visitor.rs Introduce cacheable concatenation scope snapshot structures for range-only identifier tracking.
crates/rspack_core/src/options/experiments/mod.rs Add faster_module_concatenation to core experiments options.
crates/rspack_core/src/module.rs Persist optional concatenation_scope_snapshot in BuildInfo.
crates/rspack_core/src/init_fragment.rs Add string-rendering helper for init fragments + binding support for await-deps fragments.
crates/rspack_core/src/external_module.rs Deconflict namespace export binding for externals under faster concatenation.
crates/rspack_core/src/dependency/const_dependency.rs Carry optional concatenation-scope identifier info and integrate placeholder/used-name tracking under faster concatenation.
crates/rspack_core/src/dependency/cached_const_dependency.rs Deconflict cached const identifiers and ensure decl symbols are tracked under faster concatenation.
crates/rspack_core/src/concatenated_module.rs Populate concatenation info from snapshots and render concatenated output with placeholder replacement instead of reparsing.
crates/rspack_core/src/artifacts/code_generation_results.rs Introduce CodeGenerationDataRenderedInitFragments to persist init fragments as strings for the faster path.
crates/rspack_binding_api/src/raw_options/raw_experiments/mod.rs Thread the new experiment flag through Rust binding raw options into core Experiments.
crates/node_binding/napi-binding.d.ts Expose fasterModuleConcatenation in the Node binding type declarations.

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

Comment thread crates/rspack_core/src/artifacts/code_generation_results.rs
@LingyuCoder
LingyuCoder requested a review from Copilot July 20, 2026 10:17
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
rstest ✅ success
rsbuild-rsc-plugin ✅ success
lynx-stack ✅ success
devserver ✅ success
modernjs ✅ success
rslib ✅ success
plugin ✅ success
rsdoctor ✅ success
rspress ✅ success
examples ❌ failure
rsbuild ✅ success

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

Copilot reviewed 74 out of 75 changed files in this pull request and generated 1 comment.

Comment thread crates/rspack_core/src/init_fragment.rs Outdated
@LingyuCoder
LingyuCoder requested a review from Copilot July 20, 2026 10:48
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

📝 Rspack Ecosystem CI: Open

suite result
modernjs ✅ success
rslib ✅ success
rstest ✅ success
devserver ✅ success
rspress ✅ success
rsbuild-rsc-plugin ✅ success
rsdoctor ✅ success
lynx-stack ✅ success
examples ❌ failure
plugin ✅ success
rsbuild ✅ success

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

Copilot reviewed 74 out of 75 changed files in this pull request and generated no new comments.

@LingyuCoder

Copy link
Copy Markdown
Contributor Author

@codspeedbot explain why this PR is faster

@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@codspeedbot explain why this PR is faster

Why this PR is faster

I compared the head run (c1aa22c) against its base (a5dfc7e) on CodSpeed, focusing on rust@concatenate_module_code_generation (Simulation, 45.1 ms → 39.1 ms, +15.33%), which drives most of the ~14% overall gain. The flame graphs show exactly what changed.

The base path re-parsed generated code during code generation

On main, inside ConcatenatedModule::code_generation, the hot sub-tree (…::{closure#1}::{closure#0}, ~18.5 ms / 41% of the benchmark) re-runs the full SWC front-end on the generated inner-module source every time it generates code:

  • ConcatSource::source → materialize the generated string
  • swc_experimental_ecma_parser::Lexer::new + Parser::new_from
  • Parser::parse_module
  • swc_experimental_ecma_semantic::legacy::resolver::resolver
  • rspack_core::concatenated_module::collect_ident

So the pipeline was: render the source, then re-lex, re-parse, re-resolve scopes, and re-walk the AST just to rebuild name-analysis state for deconfliction. Lexer::read_word_as_str_with alone was in the top-10 self-time functions (~22 calls, ~0.5 ms).

The head path resolves bindings from a snapshot instead

When experiments.fasterModuleConcatenation is enabled (as the benchmark does), that entire re-parse tree is gone from the head flame graph — no Lexer, Parser, parse_module, resolver, or collect_ident. It's replaced by lightweight snapshot/replacement work:

  • populate_info_from_snapshot — hydrate the compact identifier records captured during the existing make-time parser traversal
  • find_placeholder_replacement / apply_placeholder_replacements_in_place
  • ConcatenationScope::build_module_reference

In other words, the collection work is fused into a visitor that already runs at make time, range-only records are stored, and generated bindings are resolved lazily from code-generation metadata — so the expensive lex→parse→resolve→walk round-trip on generated sources no longer happens on the code-generation hot path. That eliminated block is essentially the full delta between the two runs.

A note on the reported numbers

CodSpeed flagged "Different runtime environments detected" on this comparison — base and head ran on different CPUs (EPYC 9V74 → 7763, with AVX-512 dropped). In Simulation mode that can shift the estimate a bit on identical source, so treat the exact percentages (e.g. the bundle@threejs-production-sourcemap +24.51%) as somewhat noisy. That said, the concatenate_module_code_generation improvement is a genuine, structural change: an entire re-parse/re-resolve subtree is removed, not a marginal instruction-count shift, so the direction and rough magnitude of the win are solid.

Summary
  • Warming up the performance wizard...
  • Handling comment — Comment handling finished successfully

Was this response helpful? React with 👍 or 👎 to help us improve CodSpeedBot.

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.

2 participants