Environment
unimport 6.4.0 (the same code is on main)
- Any bundler consuming
unimport/unplugin
Describe the bug
The unplugin transform builds its sourcemap with a bare MagicString#generateMap():
https://github.com/unjs/unimport/blob/main/src/unplugin.ts#L62-L65
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.
Environment
unimport6.4.0 (the same code is onmain)unimport/unpluginDescribe the bug
The unplugin transform builds its sourcemap with a bare
MagicString#generateMap():https://github.com/unjs/unimport/blob/main/src/unplugin.ts#L62-L65
With no options,
generateMap()defaults tohires: falseand leavessourceundefined, so the emitted map has exactly one mapping per generated line — all at column 0 — andsources: [""].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
Output:
End-to-end effect, measured on a four-branch module using one auto-imported global with a test covering a single branch:
s.generateMap()(today){ hires: 'boundary', source: id }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 tohires: truein our testing while being cheaper to compute and producing a smaller map.If you'd rather not change the default, exposing the
generateMapoptions throughUnimportPluginOptionswould 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.