Repository navigation
Description
Environment
unimport6.4.0 (the same code is onmain)- Any bundler consuming
unimport/unplugin
Describe the bug
The unplugin transform builds its sourcemap with a bare MagicString#generateMap():
return {
code: s.toString(),
map: s.generateMap(),
}With no options, generateMap() defaults to hires: false and leaves source undefined, so the emitted map has exactly one mapping per generated line — all at column 0 — and sources: [""].
Auto-imports are injected at the top of the file, so line numbers still line up and most things look fine. But every column collapses to 0, which breaks anything that consumes columns.
The concrete failure we hit is V8 code coverage (@vitest/coverage-v8): a module that references even one auto-imported global drops out of the coverage report entirely and is scored 100% (0/0 statements). Because the number goes up, this fails silently — coverage thresholds keep passing while real coverage rots.
Reproduction
import MagicString from 'magic-string'
import { createUnimport } from 'unimport'
const ctx = createUnimport({ presets: [{ from: 'my-lib', imports: ['helper'] }] })
await ctx.init()
const code = [
'export function classify(n) {',
' const h = helper();',
' if (n > 10) return h.big;',
' if (n > 0) return h.small;',
' return h.zero;',
'}',
'',
].join('\n')
const run = async (opts) => {
const s = new MagicString(code)
await ctx.injectImports(s, '/src/a.ts')
return s.generateMap(opts)
}
const segments = (m) => m.mappings.split(';').map((l) => (l ? l.split(',').length : 0))
const plain = await run(undefined) // what unplugin emits today
const hires = await run({ hires: 'boundary', source: '/src/a.ts' })
console.log(plain.sources, segments(plain))
console.log(hires.sources, segments(hires))Output:
current (s.generateMap()):
sources : [""]
segments per line: [0,1,1,1,1,1,1,0]
mappings : ";AAAA;AACA;AACA;AACA;AACA;AACA;"
with hires: 'boundary' + source:
sources : ["/src/a.ts"]
segments per line: [0,10,12,18,18,8,1,0]
mappings : ";AAAA,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5B,..."
End-to-end effect, measured on a four-branch module using one auto-imported global with a test covering a single branch:
| Statements | Branches | |
|---|---|---|
s.generateMap() (today) |
100% (0/0) — file absent from the report | 100% (0/0) |
{ hires: 'boundary', source: id } |
37.5% (3/8) | 16.66% (1/6) |
Expected behaviour
The transform emits a sourcemap with column-level mappings and a populated source, so downstream consumers can map positions back accurately.
Suggested fix
return {
code: s.toString(),
- map: s.generateMap(),
+ map: s.generateMap({ hires: 'boundary', source: id }),
}hires: 'boundary' measured identical to hires: true in our testing while being cheaper to compute and producing a smaller map.
If you'd rather not change the default, exposing the generateMap options through UnimportPluginOptions would let consumers opt in — though a correct map seems like the better default.
Happy to open a PR for whichever you prefer.
Downstream context
wxt-dev/wxt#2604 — WXT is currently working around this by owning the transform so it can pass the options itself.
Activity
build(deps): upgrade unimport to 6.5.0 and drop the sourcemap patch (#…