Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

magic-string

rich-harris126.3mMIT0.30.17TypeScript support: included

Modify strings, generate sourcemaps

string, string manipulation, sourcemap, templating, transpilation

readme

magic-string

build status npm version license

Suppose you have some source code. You want to make some light modifications to it - replacing a few characters here and there, wrapping it with a header and footer, etc - and ideally you'd like to generate a source map at the end of it. You've thought about using something like recast (which allows you to generate an AST from some JavaScript, manipulate it, and reprint it with a sourcemap without losing your comments and formatting), but it seems like overkill for your needs (or maybe the source code isn't JavaScript).

Your requirements are, frankly, rather niche. But they're requirements that I also have, and for which I made magic-string. It's a small, fast utility for manipulating strings and generating sourcemaps.

Installation

magic-string works in both node.js and browser environments. For node, install with npm:

npm i magic-string

To use in browser, grab the magic-string.umd.js file and add it to your page:

<script src="magic-string.umd.js"></script>

(It also works with various module systems, if you prefer that sort of thing - it has a dependency on vlq.)

Usage

These examples assume you're in node.js, or something similar:

import MagicString from 'magic-string';
import fs from 'fs';

const s = new MagicString('problems = 99');

s.update(0, 8, 'answer');
s.toString(); // 'answer = 99'

s.update(11, 13, '42'); // character indices always refer to the original string
s.toString(); // 'answer = 42'

s.prepend('var ').append(';'); // most methods are chainable
s.toString(); // 'var answer = 42;'

const map = s.generateMap({
    source: 'source.js',
    file: 'converted.js.map',
    includeContent: true,
}); // generates a v3 sourcemap

fs.writeFileSync('converted.js', s.toString());
fs.writeFileSync('converted.js.map', map.toString());

You can pass an options argument:

const s = new MagicString(someCode, {
    // these options will be used if you later call `bundle.addSource( s )` - see below
    filename: 'foo.js',
    indentExclusionRanges: [
        /*...*/
    ],
    // mark source as ignore in DevTools, see below #Bundling
    ignoreList: false,
    // adjust the incoming position - see below
    offset: 0,
});

Properties

s.offset

Sets the offset property to adjust the incoming position for the following APIs: slice, update, overwrite, appendLeft, prependLeft, appendRight, prependRight, move, reset, and remove.

Example usage:

const s = new MagicString('hello world', { offset: 0 });
s.offset = 6;
s.slice() === 'world';

Methods

s.addSourcemapLocation( index )

Adds the specified character index (with respect to the original string) to sourcemap mappings, if hires is false (see below).

s.append( content )

Appends the specified content to the end of the string. Returns this.

s.appendLeft( index, content )

Appends the specified content at the index in the original string. If a range ending with index is subsequently moved, the insert will be moved with it. Returns this. See also s.prependLeft(...).

s.appendRight( index, content )

Appends the specified content at the index in the original string. If a range starting with index is subsequently moved, the insert will be moved with it. Returns this. See also s.prependRight(...).

s.clone()

Does what you'd expect.

s.generateDecodedMap( options )

Generates a sourcemap object with raw mappings in array form, rather than encoded as a string. See generateMap documentation below for options details. Useful if you need to manipulate the sourcemap further, but most of the time you will use generateMap instead.

s.generateMap( options )

Generates a version 3 sourcemap. All options are, well, optional:

  • file - the filename where you plan to write the sourcemap
  • source - the filename of the file containing the original source
  • includeContent - whether to include the original content in the map's sourcesContent array
  • hires - whether the mapping should be high-resolution. Hi-res mappings map every single character, meaning (for example) your devtools will always be able to pinpoint the exact location of function calls and so on. With lo-res mappings, devtools may only be able to identify the correct line - but they're quicker to generate and less bulky. You can also set "boundary" to generate a semi-hi-res mappings segmented per word boundary instead of per character, suitable for string semantics that are separated by words. If sourcemap locations have been specified with s.addSourcemapLocation(), they will be used here.

The returned sourcemap has two (non-enumerable) methods attached for convenience:

  • toString - returns the equivalent of JSON.stringify(map)
  • toUrl - returns a DataURI containing the sourcemap. Useful for doing this sort of thing:
code += '\n//# sourceMappingURL=' + map.toUrl();

s.hasChanged()

Indicates if the string has been changed.

s.indent( prefix[, options] )

Prefixes each line of the string with prefix. If prefix is not supplied, the indentation will be guessed from the original content, falling back to a single tab character. Returns this.

The options argument can have an exclude property, which is an array of [start, end] character ranges. These ranges will be excluded from the indentation - useful for (e.g.) multiline strings.

s.insertLeft( index, content )

DEPRECATED since 0.17 – use s.appendLeft(...) instead

s.insertRight( index, content )

DEPRECATED since 0.17 – use s.prependRight(...) instead

s.isEmpty()

Returns true if the resulting source is empty (disregarding white space).

s.locate( index )

DEPRECATED since 0.10 – see #30

s.locateOrigin( index )

DEPRECATED since 0.10 – see #30

s.move( start, end, index )

Moves the characters from start and end to index. Returns this.

s.overwrite( start, end, content[, options] )

Replaces the characters from start to end with content, along with the appended/prepended content in that range. The same restrictions as s.remove() apply. Returns this.

The fourth argument is optional. It can have a storeName property — if true, the original name will be stored for later inclusion in a sourcemap's names array — and a contentOnly property which determines whether only the content is overwritten, or anything that was appended/prepended to the range as well.

It may be preferred to use s.update(...) instead if you wish to avoid overwriting the appended/prepended content.

s.prepend( content )

Prepends the string with the specified content. Returns this.

s.prependLeft ( index, content )

Same as s.appendLeft(...), except that the inserted content will go before any previous appends or prepends at index

s.prependRight ( index, content )

Same as s.appendRight(...), except that the inserted content will go before any previous appends or prepends at index

s.replace( regexpOrString, substitution )

String replacement with RegExp or string. When using a RegExp, replacer function is also supported. Returns this.

import MagicString from 'magic-string';

const s = new MagicString(source);

s.replace('foo', 'bar');
s.replace(/foo/g, 'bar');
s.replace(/(\w)(\d+)/g, (_, $1, $2) => $1.toUpperCase() + $2);

The differences from String.replace:

  • It will always match against the original string
  • It mutates the magic string state (use .clone() to be immutable)

s.replaceAll( regexpOrString, substitution )

Same as s.replace, but replace all matched strings instead of just one. If regexpOrString is a regex, then it must have the global (g) flag set, or a TypeError is thrown. Matches the behavior of the builtin String.property.replaceAll. Returns this.

s.remove( start, end )

Removes the characters from start to end (of the original string, not the generated string). Removing the same content twice, or making removals that partially overlap, will cause an error. Returns this.

s.reset( start, end )

Resets the characters from start to end (of the original string, not the generated string). It can be used to restore previously removed characters and discard unwanted changes.

s.slice( start, end )

Returns the content of the generated string that corresponds to the slice between start and end of the original string. Throws error if the indices are for characters that were already removed.

s.snip( start, end )

Returns a clone of s, with all content before the start and end characters of the original string removed.

s.toString()

Returns the generated string.

s.trim([ charType ])

Trims content matching charType (defaults to \s, i.e. whitespace) from the start and end. Returns this.

s.trimStart([ charType ])

Trims content matching charType (defaults to \s, i.e. whitespace) from the start. Returns this.

s.trimEnd([ charType ])

Trims content matching charType (defaults to \s, i.e. whitespace) from the end. Returns this.

s.trimLines()

Removes empty lines from the start and end. Returns this.

s.update( start, end, content[, options] )

Replaces the characters from start to end with content. The same restrictions as s.remove() apply. Returns this.

The fourth argument is optional. It can have a storeName property — if true, the original name will be stored for later inclusion in a sourcemap's names array — and an overwrite property which defaults to false and determines whether anything that was appended/prepended to the range will be overwritten along with the original content.

s.update(start, end, content) is equivalent to s.overwrite(start, end, content, { contentOnly: true }).

Bundling

To concatenate several sources, use MagicString.Bundle:

const bundle = new MagicString.Bundle();

bundle.addSource({
    filename: 'foo.js',
    content: new MagicString('var answer = 42;'),
});

bundle.addSource({
    filename: 'bar.js',
    content: new MagicString('console.log( answer )'),
});

// Sources can be marked as ignore-listed, which provides a hint to debuggers
// to not step into this code and also don't show the source files depending
// on user preferences.
bundle.addSource({
    filename: 'some-3rdparty-library.js',
    content: new MagicString('function myLib(){}'),
    ignoreList: false, // <--
});

// Advanced: a source can include an `indentExclusionRanges` property
// alongside `filename` and `content`. This will be passed to `s.indent()`
// - see documentation above

bundle
    .indent() // optionally, pass an indent string, otherwise it will be guessed
    .prepend('(function () {\n')
    .append('}());');

bundle.toString();
// (function () {
//   var answer = 42;
//   console.log( answer );
// }());

// options are as per `s.generateMap()` above
const map = bundle.generateMap({
    file: 'bundle.js',
    includeContent: true,
    hires: true,
});

As an alternative syntax, if you a) don't have filename or indentExclusionRanges options, or b) passed those in when you used new MagicString(...), you can simply pass the MagicString instance itself:

const bundle = new MagicString.Bundle();
const source = new MagicString(someCode, {
    filename: 'foo.js',
});

bundle.addSource(source);

License

MIT

changelog

0.30.17 (2024-12-16)

Bug Fixes

  • remove problematic type: module (092697b)

0.30.16 (2024-12-16)

Bug Fixes

  • missing mapping after a line break with hires: 'boundary' (#298) (24cb8ea)

Features

0.30.15 (2024-12-10)

Features

  • add sideEffects: false to package.json (#295) (889bd73)

0.30.14 (2024-11-26)

Features

0.30.13 (2024-11-18)

Features

  • Add support for sourcemap debugId property (#292) (ef531a8)

0.30.12 (2024-10-11)

Performance Improvements

0.30.11 (2024-07-29)

Bug Fixes

  • not use negative indices for remove in empty string (#281) (5c1cba0)

0.30.10 (2024-04-17)

Bug Fixes

0.30.9 (2024-04-04)

Performance Improvements

  • avoid create uncessary overrides for replace (a1b857c)

0.30.8 (2024-03-03)

Bug Fixes

  • handle last empty line correctly when using update/overwrite (#274) (29c7bfa)

0.30.7 (2024-02-05)

Features

0.30.6 (2024-01-31)

Features

  • support Web Workers by using the global btoa (#269) (8679648)

0.30.5 (2023-10-12)

Bug Fixes

0.30.4 (2023-09-29)

Bug Fixes

  • correct mappings for update containing new line (#261) (adaece9)
  • use global btoa, support services worker, close #258 (#259) (2dea20b)

0.30.3 (2023-08-21)

Bug Fixes

0.30.2 (2023-07-28)

Features

0.30.1 (2023-07-04)

Bug Fixes

0.30.0 (2023-02-22)

Bug Fixes

  • null is invalid for sources and file (#242) (d4e9c31)

Features

  • add the ability to ignore-list sources (#243) (e238f04)

0.29.0 (2023-02-11)

Features

  • x_google_ignoreList: initial support for ignore lists (3c711cd)

0.28.0 (2023-02-11)

Bug Fixes

  • typings: sourcesContent may contain null (#235) (c2b652a)

0.27.0 (2022-12-03)

Performance Improvements

  • use @jridgewell/sourcemap-codec (e68f3e0)

0.26.7 (2022-10-09)

Bug Fixes

0.26.6 (2022-10-05)

Features

  • add update method as safer alternative to overwrite (#212) (9a312e3)

0.26.5 (2022-09-30)

Bug Fixes

  • update typescript definition file to contain replaceAll() (#224) (45a4921)

0.26.4 (2022-09-22)

Features

  • fix .replace() when searching string, add .replaceAll() (#222) (04a05bd)

Performance Improvements

  • avoiding use of Object.defineProperty in Chunk constructor (#219) (130794b)

0.26.3 (2022-08-30)

Performance Improvements

0.26.2 (2022-05-11)

Bug Fixes

0.26.1 (2022-03-03)

Bug Fixes

  • replace: match replacer function signature with spec (902541f)

0.26.0 (2022-03-03)

BREAKING CHANGES

  • Support of Node.js v10 is dropped. Now magic-string requires Node.js v12 or higher. (#204)
  • ESM bundle is now shipped with .mjs extension (#197)
-  "module": "dist/magic-string.es.js",
+  "module": "dist/magic-string.es.mjs",
+  "exports": {
+    "./package.json": "./package.json",
+    ".": {
+      "import": "./dist/magic-string.es.mjs",
+      "require": "./dist/magic-string.cjs.js"
+    }
+  },

Features

0.25.9 (2022-03-03)

Bug Fixes

  • allowed overwrite across moved content preceded by split (#192) (403fa86)
  • types: make options partial by default (2815e77)
  • use defineProperty for appending prop in storeName (#194) (96b7cd3)

0.25.8 (2022-03-02)

Bug Fixes

  • types: mark MagicString options as optional (#183) (15c3e66)

0.25.7

  • fix bundle mappings after remove and move in multiple sources (#172)

0.25.6

  • Use bitwise operators for small performance boost (#171)

0.25.5

  • Use a bitset to reduce memory consumption (#167)

0.25.4

  • Clone intro and outro (#162)

0.25.3

  • Fix typing of SourceMap.version.

0.25.2

  • Remove deprecated new Buffer(...)
  • Handle characters outside Latin1 range when generating a sourcemap in browser (#154)

0.25.1

  • Additional types for index.d.ts (#148)

0.25.0

  • Add length method (#145)
  • Fix trimming chunks with intro/outro (#144)

0.24.1

  • Add lastLine and lastChar methods (#142)

0.24.0

  • Add isEmpty methods (#137)
  • Fix a potential race condition (#136)
  • Fix CJS/ES bundles inlining sourcemap-codec in 0.23.2.

0.23.2

  • Add generateDecodedMap methods (#134)

0.23.1

  • Performance (#132)

0.23.0

  • Use sourcemap-codec for improved performance (#133)

0.22.5

  • Add TypeScript interfaces used by rollup (#131)
  • Remove src directory from npm package

0.22.4

  • contentOnly and storeName are both optional

0.22.3

  • Add original to TS definitions

0.22.2

  • Avoid declare module (#127)

0.22.1

  • Update TypeScript definitions (#124)

0.22.0

  • Prevent overwrite state corruption (#115)
  • Various bugfixes (#126)

0.21.3

  • Clone indentExclusionRanges correctly (#122)
  • Fix more typings (#122)

0.21.2

  • Add default property referencing self in index-legacy.js, to work around TypeScript bug (#121)

0.21.1

  • Add typings file to package

0.21.0

  • Add TypeScript bindings (#119)

0.20.0

  • The fourth argument to overwrite is a {storeName, contentOnly} options object. storeName: true is equivalent to true before. contentOnly will preserve existing appends/prepends to the chunk in question

0.19.1

  • Prevent overwrites across a split point (i.e. following a move)
  • Implement remove separately to overwrite

0.19.0

  • More accurate bundle sourcemaps (#114)

0.18.0

  • Optimisation – remove empty chunks following overwrite or remove (#113)

0.17.0

  • Add appendLeft, appendRight, prependLeft, prependRight methods (#109)
  • insertLeft and insertRight are deprecated in favour of appendLeft and prependRight respectively

0.16.0

  • Include inserts in range for overwrite and remove operations (#89)
  • Make options optional for bundle.generateMap(...) (#73)

0.15.2

  • Generate correct bundle sourcemap with prepended/appended content

0.15.1

  • Minor sourcemap fixes

0.15.0

  • Use named export of Bundle in ES build, so ES consumers of magic-string can tree-shake it out

0.14.0

  • Throw if overwrite of zero-length range is attempted
  • Correctly handle redundant move operations

0.13.1

  • Fix a bevy of s.slice() issues (#62)

0.13.0

  • Breaking: insertAfter is now insertLeft, insertBefore is now insertRight
  • Breaking: insert is no longer available. Use insertLeft and insertRight
  • Significant performance improvements

0.12.1

  • Fix sourcemap generation with insertAfter and insertBefore

0.12.0

  • Add insertAfter and insertBefore methods

0.11.4

  • Fix two regression bugs with trim()
  • More informative error message on illegal removals

0.11.3

  • Fix trim methods to ensure correct sourcemaps with trimmed content (#53)

0.11.2

  • Support sourcemaps with moved content

0.11.1

  • Use findIndex helper for 0.12 support

0.11.0

  • Add experimental move() method
  • Refactor internals to support move()

0.10.2

  • Do not overwrite inserts at the end of patched ranges (#35)

0.10.1

  • Zero-length inserts are not removed on adjacent overwrites

0.10.0

  • Complete rewrite, resulting in ~40x speed increase (#30)
  • Breaking – magicString.locate and locateOrigin are deprecated
  • More forgiving rules about contiguous patches, and which ranges are valid with magicString.slice(...)

0.9.1

  • Update deps

0.9.0

  • Update build process

0.8.0

  • Add an ES6 build, change default UMD build to CommonJS (but keeping existing UMD build with bundled dependencies)
  • Make properties non-enumerable, for cleaner logging
  • Update dependencies

0.7.0

  • The names array is populated when generating sourcemaps, and mappings include name indices where appropriate (#16)
  • Replaced content is mapped correctly in sourcemaps (#15)

0.6.6

  • Adjust mappings correctly when removing replaced content
  • Error correctly when removed characters are used as slice anchors

0.6.5

  • Fix jsnext:main in package.json

0.6.4

  • Fix bug with positive integer coercion

0.6.3

  • Intro content is correctly indented
  • Content following an intro with trailing newline is correctly indented

0.6.2

  • Noop indents are still chainable (fixes bug introduced in 0.6.1)

0.6.1

  • Indenting with an empty string is a noop

0.6.0

  • Use rollup for bundling, instead of esperanto

0.5.3

  • Correct sourcemap generation with bundles containing varied separators
  • s.clone() clones indent exclusion ranges and sourcemap locations

0.5.2

  • s.slice() accepts negative numbers, and the second argument can be omitted (means 'original string length'), just like String.prototype.slice
  • More informative error message when trying to overwrite content illegally

0.5.1

  • Allow bundle separator to be the empty string
  • Indenting is handled correctly with empty string separator

0.5.0

  • s.replace() is deprecated in favour of s.overwrite() (identical signature)
  • bundle.addSource() can take a MagicString instance as its sole argument, for convenience
  • The options in new MagicString(str, options) can include filename and indentExclusionRanges options, which will be used when bundling
  • New method: s.snip( start, end )

0.4.9

  • file option is optional when generating a bundle sourcemap

0.4.7

  • Repeated insertions at position 0 behave the same as other positions (#10)

0.4.6

  • Overlapping ranges can be removed
  • Non-string content is rejected (#9)

0.4.5

  • Implement source.addSourcemapLocation()

0.4.4

  • Another Windows fix, this time for file paths when bundling

0.4.3

  • Handle Windows-style CRLF newlines when determining whether a line is empty

0.4.2

  • Fix typo in package.json (d'oh again)
  • Use only relative paths for internal modules - makes bundling with dependents (i.e. esperanto) possible

0.4.1

  • Includes correct files in npm package (d'oh)

0.4.0

0.3.1

  • Fixes a bug whereby multiple insertions at the same location would cause text to repeat (#5)

0.3.0

  • Breaking change - source.indentStr is null if no lines are indented. Use source.getIndentString() for the old behaviour (guess, and if no lines are indented, return \t)
  • bundle.getIndentString() ignores sources with no indented lines when guessing indentation (#3)

0.2.7

  • source.trimLines() removes empty lines from start/end of source, leaving other whitespace untouched
  • Indentation is not added to an empty source

0.2.6

  • Performance improvement - adjustments are only made when necessary

0.2.5

  • Single spaces are ignored when guessing indentation - experience shows these are more likely to be e.g. JSDoc comments than actual indentation
  • bundle.addSource() can take an indentExclusionRanges option

0.2.4

  • Empty lines are not indented

0.2.3

  • Fixes edge case with bundle sourcemaps

0.2.2

  • Make sources paths in sourcemaps relative to options.file

0.2.1

  • Minor fix for bundle.indent()

0.2.0

  • Implement MagicString.Bundle for concatenating magic strings

0.1.10

  • Fix sourcemap encoding

0.1.9

  • Better performance when indenting large chunks of code

0.1.8

  • Sourcemaps generated with s.generateMap() have a toUrl() method that generates a DataURI

0.1.7

  • Implement s.insert( index, content ) - roughly equivalent to s.replace( index, index, content )

0.1.6

  • Version bump for npm's benefit

0.1.5

  • s.indent({ exclude: [ x, y ] }) prevents lines between (original) characters x and y from being indented. Multiple exclusion ranges are also supported (e.g. exclude: [[a, b], [c, d]])

0.1.4

  • s.locate() doesn't throw out-of-bound error if index is equal to original string's length

0.1.3

  • s.trim() returns this (i.e. is chainable)

0.1.2

  • Implement s.slice()

0.1.1

  • Implement s.trim()

0.1.0

  • First release