Skip to content

Commit 5bd2287

Browse files
feat: add SourceMapBuilder and bundler-scale remap benchmark
Add SourceMap::builder() with iterator-consuming setters for ergonomic construction without pre-collecting into Vecs. Add bundler-scale benchmark (60K mappings across 20 sources) comparing materialized vs streaming remapping. Remove deprioritized Source Map Diagnostics and Parallel VLQ Encoding from roadmap. Mark Stage 1/2 proposals clearly.
1 parent d54f014 commit 5bd2287

3 files changed

Lines changed: 426 additions & 29 deletions

File tree

ROADMAP.md

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ Rolldown's `collapse_sourcemaps` materializes the entire token stream into `Vec<
6363
- [x] `remap_streaming()`: composition pipeline that streams through mappings without intermediate allocation
6464
- [x] Criterion benchmarks (500 / 10K / 60K mappings) — 15-20% faster than materialized `remap()`
6565

66-
Remaining nice-to-haves (low priority):
67-
- [ ] Builder pattern for `SourceMap::new()` that consumes iterators for names/sources/source_contents
68-
- [ ] Benchmark against Rolldown's current `collapse_sourcemaps`
66+
- [x] Builder pattern (`SourceMap::builder()`) that consumes iterators for names/sources/source_contents
67+
- [x] Bundler-scale benchmark (60K mappings across 20 sources, materialized vs streaming)
6968

7069
---
7170

@@ -184,31 +183,6 @@ srcmap already parses `debugId` from source map JSON. What's missing is extracti
184183

185184
---
186185

187-
## Source Map Diagnostics
188-
189-
Beyond basic validation, a deeper analysis mode for debugging broken source maps.
190-
191-
### What to implement
192-
193-
- [ ] **Mapping coverage** — what percentage of generated code has source mappings?
194-
- [ ] **Redundant mapping detection** — consecutive mappings to the same original position ([sentry/rust-sourcemap#72](https://github.com/nicolo-ribaudo/ecma-426/blob/main/proposals/range-mappings.md))
195-
- [ ] **Composition chain validation** — given a chain of source maps, verify the composed result is correct
196-
- [ ] **Size analysis** — breakdown of source map size by component (mappings, sourcesContent, names)
197-
- [ ] **Mapping density** — mappings per line, identifying over/under-mapped regions
198-
- [ ] CLI: `srcmap diagnose bundle.js.map` with `--json` output
199-
200-
---
201-
202-
## Parallel VLQ Encoding
203-
204-
Split the mappings string into chunks and encode VLQ segments in parallel using rayon. This is `oxc_sourcemap`'s main performance differentiator for generation workloads.
205-
206-
### What to implement
207-
208-
- [ ] Parallel VLQ encoding in `srcmap-generator` behind a `rayon` feature flag
209-
- [ ] Benchmark against `oxc_sourcemap` parallel encoding
210-
- [ ] Thread count configuration
211-
212186
---
213187

214188
## Mappings v2 Encoding `[Discussion — not implementing yet]`

crates/remapping/benches/remap.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,76 @@ fn bench_remap(c: &mut Criterion) {
5757
}
5858
}
5959

60-
criterion_group!(benches, bench_remap);
60+
/// Simulate a bundler workload: multiple source files each with their own
61+
/// source map, composed through a single bundler output map.
62+
fn bench_remap_bundler(c: &mut Criterion) {
63+
let source_count = 20;
64+
let mappings_per_source = 3000; // 60K total
65+
66+
// Build inner maps (one per source file, simulating TS → JS transforms)
67+
let inner_maps: Vec<(String, SourceMap)> = (0..source_count)
68+
.map(|s| {
69+
let source_name = format!("src/module_{s}.ts");
70+
let intermediate_name = format!("dist/module_{s}.js");
71+
let mut gen = SourceMapGenerator::new(Some(intermediate_name.clone()));
72+
let src = gen.add_source(&source_name);
73+
for i in 0..mappings_per_source {
74+
let line = i / 15;
75+
let col = (i % 15) * 4;
76+
gen.add_mapping(line, col, src, line + 1, col);
77+
}
78+
(intermediate_name, gen.to_decoded_map())
79+
})
80+
.collect();
81+
82+
// Build outer map (bundler output referencing all intermediate files)
83+
let mut outer_gen = SourceMapGenerator::new(Some("bundle.js".to_string()));
84+
let src_indices: Vec<u32> = inner_maps
85+
.iter()
86+
.map(|(name, _)| outer_gen.add_source(name))
87+
.collect();
88+
for (s, &src) in src_indices.iter().enumerate() {
89+
let line_offset = (s as u32) * (mappings_per_source / 15);
90+
for i in 0..mappings_per_source {
91+
let orig_line = i / 15;
92+
let col = (i % 15) * 4;
93+
outer_gen.add_mapping(line_offset + orig_line, col, src, orig_line, col);
94+
}
95+
}
96+
let outer = outer_gen.to_decoded_map();
97+
let vlq = outer.encode_mappings();
98+
99+
c.bench_function("remap_bundler_60k_20src", |b| {
100+
b.iter(|| {
101+
black_box(remap(&outer, |source| {
102+
inner_maps
103+
.iter()
104+
.find(|(name, _)| name == source)
105+
.map(|(_, sm)| sm.clone())
106+
}));
107+
})
108+
});
109+
110+
c.bench_function("remap_streaming_bundler_60k_20src", |b| {
111+
b.iter(|| {
112+
let iter = MappingsIter::new(&vlq);
113+
black_box(remap_streaming(
114+
iter,
115+
&outer.sources,
116+
&outer.names,
117+
&outer.sources_content,
118+
&outer.ignore_list,
119+
outer.file.clone(),
120+
|source| {
121+
inner_maps
122+
.iter()
123+
.find(|(name, _)| name == source)
124+
.map(|(_, sm)| sm.clone())
125+
},
126+
));
127+
})
128+
});
129+
}
130+
131+
criterion_group!(benches, bench_remap, bench_remap_bundler);
61132
criterion_main!(benches);

0 commit comments

Comments
 (0)