Skip to content

feat: emit the source map scopes field - #275

Open
alexander-akait wants to merge 5 commits into
mainfrom
feat/source-map-scopes
Open

alexander-akait wants to merge 5 commits into
mainfrom
feat/source-map-scopes

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

A bundler rewrites an imported binding into a namespace read, and a source map only maps positions, so a debugger cannot resolve the name the source actually used. This adds map({ scopes: true }), emitting the ECMA-426 Scopes field: a source declares what its names read through a new third OriginalSource argument, the chunk stream carries that beside the source content, and the field is built while the mappings are written rather than decoded back out of them. Needed by webpack/webpack#21999, which otherwise has to carry its own VLQ codec and re-parse every asset's mappings.

It also fixes a pre-existing cache bug it would otherwise trip over: CachedSource's fast-path key inspected only columns, so any two option sets differing elsewhere collided — with scopes that meant a map built without the field could be served to a caller asking for it, and vice versa.

What kind of change does this PR introduce?

feat.

Did you add tests for your changes?

Yes — test/scopes.js: the option is opt-in, bindings survive ConcatSource, ReplaceSource and a primed CachedSource, neither cache order leaks or drops the field, and the streaming encoder is asserted byte-identical to decoding a finished map.

Does this PR introduce a breaking change?

No. OnSource gains an optional fourth argument and MapOptions an optional scopes flag; without it nothing is collected or emitted, and the existing 89,895 tests and 1,373 snapshots pass unchanged.

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

map({ scopes: true }), the OriginalSource third constructor argument, and the util.scopes helpers.

Use of AI

Yes. Claude Code was used to move the encoder out of webpack, wire the option through the streaming path, and write the tests. The design — a plain boolean option with the bindings riding on the sources, rather than a callback that a cache key cannot see — was mine, and the CachedSource bugs above were found by testing that decision rather than assumed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C5WxsJzknrdkVzHV3rL1Uv


Generated by Claude Code

A bundler rewrites an imported binding into a namespace read, and a
source map only maps positions, so a debugger cannot resolve the name
the source used. The Scopes proposal answers that by naming, per scope,
the expression each variable evaluates to.

Add map({ scopes: true }). A source declares what its names read through
a new OriginalSource argument, the chunk stream carries it beside the
source content, and the field is built while the mappings are written
rather than decoded back out of them.
@changeset-bot

changeset-bot Bot commented Sep 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 941c833

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

This PR includes changesets to release 1 package
Name Type
webpack-sources Minor

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

@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.45%. Comparing base (9c2b267) to head (941c833).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #275      +/-   ##
==========================================
+ Coverage   98.29%   98.45%   +0.16%     
==========================================
  Files          25       26       +1     
  Lines        2283     2527     +244     
  Branches      732      779      +47     
==========================================
+ Hits         2244     2488     +244     
  Misses         37       37              
  Partials        2        2              
Flag Coverage Δ
integration 98.45% <100.00%> (+0.16%) ⬆️

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.

@codspeed

codspeed Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging this PR will regress 19 benchmarks

⚠️ 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

⚡ 12 improved benchmarks
❌ 19 regressed benchmarks
✅ 181 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory clear-cache memory: shared modules (no visited set — allocates per chunk) 11.4 KB 56.6 KB -79.87%
Memory replace-source memory: source() concatenates result 12.6 KB 47.6 KB -73.58%
Memory concat-source memory: source() concatenates children 156.6 KB 370.5 KB -57.74%
Memory compat-source memory: CompatSource.from() short-circuits on Source 392 B 648 B -39.51%
Memory raw-source memory: new RawSource(string) 784 B 1,104 B -28.99%
Memory cached-source memory: construct from cachedData (no warm-up) 424 B 520 B -18.46%
Memory clear-cache memory: unique tasks (no clearCache) 4 MB 4.8 MB -17.18%
Memory prefix-source memory: new PrefixSource() 1.2 KB 1.4 KB -15.91%
Simulation source-map-source: new (buffer map) 206.1 µs 244.5 µs -15.69%
Simulation source-map-source: new (object map) 206.4 µs 244.7 µs -15.63%
Simulation source-map-source: new (buffer value) 206.9 µs 245.1 µs -15.58%
Simulation source-map-source: new (string map) 206.4 µs 244.5 µs -15.58%
Memory source-map-source memory: sourceAndMap({ columns: true }) (combined inner) 2.1 MB 2.5 MB -14.98%
Memory replace-source memory: construct + 100 insertions 44.5 KB 52.2 KB -14.74%
Simulation source-map-source: buffers() (from buffer) 222.4 µs 260.5 µs -14.63%
Simulation source-map-source: sourceAndMap({columns:false}) 240.7 µs 279.3 µs -13.8%
Simulation source-map-source: sourceAndMap() 240.6 µs 278 µs -13.45%
Simulation source-map-source: source() 293 µs 331.1 µs -11.5%
Simulation source-map-source: map({columns:false}) 311.1 µs 348.5 µs -10.72%
Memory cached-source memory: warm sourceAndMap() returns cached references 13,128 B 465 B ×28
... ... ... ... ... ...

ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/source-map-scopes (941c833) with main (9c2b267)

Open in CodSpeed

Requiring it from the two map assembly helpers put the encoder and its
lookup table in the module graph of every caller, which the memory
benchmarks measured as a constant cost on sources that never emit the
field.
The streaming writer reaches none of it, so a map handed in from
elsewhere went untested: the early returns, a segment naming no source
or one the map lacks, a source resumed after another, a name the map
already carries, and a value too large for one base64 digit.
The cases required the helper module directly, so the getter that
exposes it on `util` was never run.
Also choose the mapping callback once instead of testing for the scopes
writer per segment. Interleaved against main on one machine, that test
cost OriginalSource.map({ columns: true }) a reproducible 1-3%; with the
callback chosen up front the same measurement is neutral.
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.

1 participant