| name | magic-string-sourcemap |
| description | Source string mutation with automatic source map tracking. In-place overwrite, insertion, removal, and prepend/append operations that preserve accurate sourcemap positions for patch loops and codemods. Sources: unjs/magic-string (MIT). |
/magic-string-sourcemap
When to Use
- Patch source code in a codemod/transform pipeline while keeping source maps valid
- Inject imports, decorators, or license headers at the top of a file
- Remove dead code blocks from a string without re-parsing the AST
- Bundle multiple source files into one string with accurate per-file source maps
Do NOT use for
- Heavy AST restructuring (use [[babel-ast-transform]] or [[ts-morph-refactor]])
- Non-source string manipulation (use regular string methods)
Basic mutations
import MagicString from 'magic-string'
const s = new MagicString(`export const VERSION = 'dev'`)
s.overwrite(23, 28, '1.0.0')
s.prependLeft(0, '// Auto-generated\n')
s.appendRight(s.length(), '\n\nexport default VERSION')
s.remove(30, 45)
console.log(s.toString())
Generate source map
import MagicString from 'magic-string'
const s = new MagicString(sourceCode, {
filename: 'src/config.ts',
indentExclusionRanges: [],
})
const map = s.generateMap({
source: 'src/config.ts',
file: 'dist/config.js',
includeContent: true,
hires: true,
})
writeFileSync('dist/config.js', s.toString())
writeFileSync('dist/config.js.map', map.toString())
Bundle multiple files with combined source map
import MagicString, { Bundle } from 'magic-string'
const bundle = new Bundle()
for (const { filename, source } of files) {
const s = new MagicString(source, { filename })
s.prepend(`// --- ${filename} ---\n`)
bundle.addSource({ filename, content: s })
}
const output = bundle.toString()
const map = bundle.generateMap({ includeContent: true, hires: true })
writeFileSync('bundle.js', output)
writeFileSync('bundle.js.map', map.toString())
Inject import at top of file
function injectImport(source: string, importStatement: string): string {
const s = new MagicString(source)
const firstNonComment = source.search(/^[^/\n]/)
const insertAt = firstNonComment > 0 ? firstNonComment : 0
s.prependLeft(insertAt, importStatement + '\n')
return s.toString()
}
const patched = injectImport(
readFileSync('src/app.ts', 'utf8'),
"import { logger } from './logger'"
)
Anti-Fake-Pass Checklist
❌ Overlapping overwrite ranges → MagicString throws; check ranges don't overlap before patching
❌ hires: false → line-level source maps only; column positions in debugger are wrong
❌ s.remove then s.overwrite same range → invalid state; use one operation per range
❌ Not writing .map file → source maps referenced in JS but 404 in browser devtools
❌ MagicString positions are UTF-16 code units — be careful with multi-byte Unicode
❌ bundle.addSource without filename → all sources merged under undefined in source map