# magic_string
[](https://hex.pm/packages/magic_string)
[](https://hexdocs.pm/magic_string/)
Edit a string by byte offset and get a [source map](https://tc39.es/ecma426/) out the other side. A Gleam port of Rich Harris's [magic-string](https://github.com/Rich-Harris/magic-string).
It's useful when you have some source code, you know where the bits you care about are (because a parser told you), and you want to make surgical changes without rebuilding the whole thing from an AST. The source map is a byproduct of the edit log, so you don't have to think about it.
```sh
gleam add magic_string
```
```gleam
import magic_string as ms
import magic_string/codec
pub fn main() {
let s = ms.new("const answer = 42;")
// Byte offsets are half-open: [start, end). overwrite/remove return a
// Result because they can conflict with earlier edits (see below).
let assert Ok(s) = ms.overwrite(s, 6, 12, "x")
let assert Ok(s) = ms.remove(s, 0, 6)
let s = ms.append(s, " // edited")
ms.to_string(s)
// -> "x = 42; // edited"
let map = ms.generate_map(s, "in.js", ms.default_map_options())
codec.to_json(map)
// -> {"version":3,"sources":["in.js"],"mappings":"...",...}
}
```
### Bundling multiple sources
```gleam
import magic_string as ms
let a = "export const a = 1;"
let b = "export const b = 2;"
let bundle =
ms.bundle()
|> ms.add_source("a.js", a, ms.new(a))
|> ms.add_source("b.js", b, ms.new(b))
ms.bundle_to_string(bundle)
ms.bundle_generate_map(bundle, ms.default_map_options())
```
The map has `sources` and `sourcesContent` populated for every file you added, so a debugger can show the original code.
### API
The full API lives in the [hexdocs](https://hexdocs.pm/magic_string/). The short version:
- `new`, `overwrite`, `remove`, `append_left`, `append_right`, `prepend`, `append`, `to_string`, `generate_map` for single strings
- `bundle`, `add_source`, `bundle_to_string`, `bundle_generate_map` for stitching many together
- `magic_string/codec` has the Source Map v3 type, VLQ encoding, and `to_json` / `url_comment` if you need lower level access
- `magic_string/position_index` converts byte offsets to `(line, utf16_column)` pairs, which is what source maps want
### Notes
Offsets are UTF-8 byte offsets, not character indices. On the BEAM that's what `string.byte_size` and friends give you, and it's what most parsers report. The position index handles the conversion to UTF-16 columns for the source map output.
`overwrite`, `remove`, `append_left` and `append_right` return `Result(MagicString, EditError)`. They fail when the edit conflicts with one already recorded: two ranges overlapping (`Overlap`), an insert falling inside an existing range or a new range swallowing an existing insert (`SwallowedInsert`), or offsets outside the source (`OutOfBounds`, `InvertedRange`). The error names the offsets involved, so you can trace it back to the bad span. Adjacent edits that share a boundary but no bytes are fine. `prepend`, `append`, `to_string` and `generate_map` are infallible, since by the time you reach them every recorded edit is already known to compose.
In a bundler the offsets come straight from a parser's spans, so in practice you `result.try` through a chain of edits and surface the first conflict as a build error.
Also see: my project [Arc](https://github.com/alii/arc)!