docs/in-depth/native-magic-string.md
experimental.nativeMagicString is an optimization feature that replaces the JavaScript-based MagicString implementation with a native Rust version, enabling source map generation in background threads for improving performance.
MagicString is a JavaScript library developed by Rich Harris (the creator of Rollup and Svelte) that provides efficient string manipulation with automatic source map generation. It's commonly used by bundlers and build tools for:
The original MagicString implementation is written in JavaScript and runs in the Node.js environment. When bundlers perform code transformations, they typically:
Rolldown's native MagicString implementation rewrites the core functionality in Rust, providing several advantages:
When experimental.nativeMagicString is enabled, Rolldown modifies the transformation pipeline. The diagrams below show the architectural differences:
:::info
Some technical details are simplified for better illustration. The native MagicString implementation provides a magicString object in the meta parameter of transform hooks, which plugins can use just like the JavaScript version.
:::
(Correction in the image: "rolldown without js magic-string" should be "rolldown without native magic-string")
Key Difference: The native implementation is written in Rust, providing both Rust's performance advantages and background thread source map generation. Offloading to background threads improves overall CPU usage and enables significant performance improvements.
The native implementation maintains API compatibility with the JavaScript version. The most commonly used APIs are already implemented, with the remaining APIs planned for completion in future releases.
The following MagicString methods are currently available in the native implementation:
String Manipulation:
append(content) - Appends content to the end of the stringprepend(content) - Prepends content to the beginning of the stringappendLeft(index, content) - Appends content to the left of a specific indexappendRight(index, content) - Appends content to the right of a specific indexprependLeft(index, content) - Prepends content to the left of a specific indexprependRight(index, content) - Prepends content to the right of a specific indexoverwrite(start, end, content) - Replaces content in a rangeupdate(start, end, content) - Updates content in a rangeremove(start, end) - Removes content in a rangereplace(from, to) - Replaces the first occurrence of a patternreplaceAll(from, to) - Replaces all occurrences of a patternTransformations:
indent(indentor?) - Indents the content with optional custom indentation stringrelocate(start, end, to) - Moves content from one position to anotherUtilities:
toString() - Returns the transformed stringhasChanged() - Checks if the string has been modifiedlength() - Returns the length of the transformed stringisEmpty() - Checks if the string is emptyclone() - Returns a clone of the MagicString instancetrim(charType?) - Trims whitespace or specified characters from both endstrimStart(charType?) - Trims whitespace or specified characters from the starttrimEnd(charType?) - Trims whitespace or specified characters from the endtrimLines() - Trims newlines from both endssnip(start, end) - Returns a clone with content outside the range removedslice(start?, end?) - Returns content between positionsreset(start, end) - Resets a range to its original contentlastChar() - Returns the last characterlastLine() - Returns the content after the last newlineSource Map Generation:
generateMap(options?) - Generates a source map as a JSON string
options.source - Source file nameoptions.includeContent - Include original source in the mapoptions.hires - High-resolution mode: true, false, or "boundary"The following features are planned for future releases:
generateDecodedMap() - Generate source map with decoded mappingsuse rolldown/benchmarks as benchmark cases
| Runs | oxc raw transfer + js magicString | oxc raw transfer + native magicString | Time Saved | Speedup |
|---|---|---|---|---|
| apps/1000 | 497.6 ms | 431.1 ms | 66.5 ms | 1.15x |
| apps/5000 | 1.100 s | 894.5 ms | 205.5 ms | 1.23x |
| apps/10000 | 1.814 s | 1.368 s | 446.0 ms | 1.33x |
| Runs | Transform Time (oxc raw transfer + js magicString) | Transform Time (oxc raw transfer + native magicString) | Time Saved | Speedup |
|---|---|---|---|---|
| 1000 | 172.0 ms | 105.5 ms | 66.5 ms | 1.63x |
| 5000 | 455.4 ms | 249.9 ms | 205.5 ms | 1.82x |
| 10000 | 799.0 ms | 353.0 ms | 446.0 ms | 2.26x |
For detailed benchmark results, see the benchmark pull request.
import { defineConfig } from 'rolldown';
export default defineConfig({
experimental: {
nativeMagicString: true,
},
output: {
sourcemap: true,
},
plugins: [
{
name: 'transform-example',
transform(code, id, meta) {
if (!meta?.magicString) {
// Fallback when nativeMagicString is not available
return null;
}
const { magicString } = meta;
// Example transformation: Add debug comments
if (code.includes('console.log')) {
magicString.replace(/console\.log\(/g, 'console.log("[DEBUG]", ');
}
// Example: Add file header
magicString.prepend(`// Transformed from: ${id}\n`);
return {
code: magicString,
};
},
},
],
});
transform(code, id, meta) {
if (meta?.magicString) {
// Native MagicString is available
const { magicString } = meta;
// Use the native implementation
// Note: Return the magicString object directly, not a string
return {
code: magicString
};
} else {
// Fallback to regular string manipulation
// or use the JavaScript MagicString library
const MagicString = require('magic-string');
const ms = new MagicString(code);
// Your transformations here...
return {
code: ms.toString(),
map: ms.generateMap()
};
}
}
This feature is Rolldown-specific and not available in Rollup. For plugins that need to work with both bundlers:
function createTransform() {
return function (code, id, meta) {
if (meta?.magicString) {
// Rolldown with native MagicString
return transformWithNativeMagicString(code, id, meta);
} else {
// Rollup or Rolldown without native MagicString
return transformWithJsMagicString(code, id);
}
};
}
::: tip
You can use rolldown-string, which provides a unified interface that works with both bundlers.
:::
export default {
experimental: {
nativeMagicString: true,
},
output: {
sourcemap: true, // Required for source map generation
},
};
// Before
transform(code, id) {
const ms = new MagicString(code);
// ... transformations
return { code: ms.toString(), map: ms.generateMap() };
}
// After
transform(code, id, meta) {
if (meta?.magicString) {
const { magicString } = meta;
// ... transformations (same API)
return { code: magicString };
}
// Fallback logic
}
meta?.magicString exists before usingexperimental.nativeMagicString represents a significant performance optimization for Rolldown by leveraging Rust's efficiency for code transformation tasks. While it requires some considerations for compatibility, the performance benefits make it an attractive option for large-scale projects and performance-critical build processes.
As an experimental feature, it's recommended to test thoroughly in development environments before adopting in production workflows. The Rolldown team is actively working on this feature, and feedback from the community is valuable for its continued development.