Write one Rolldown transform hook that also works in Vite and Rollup.
Rolldown supplies transform metadata such as a parsed AST, a native RolldownMagicString, and the module type. Older Vite and Rollup pipelines do not. rolldownTransform fills in those APIs lazily and adapts native MagicString results for legacy pipelines.
npm install @jsxtools/rolldown-transformimport { rolldownTransform } from "@jsxtools/rolldown-transform";
import type { TransformMeta } from "@jsxtools/rolldown-transform";
import type { Plugin, TransformResult } from "rolldown";
export const plugin: Plugin = {
name: "my-plugin",
transform: rolldownTransform({
filter: {
id: /\.[cm]?[jt]sx?(?:\?|$)/,
code: "console.log",
},
handler(_code, _id, meta: TransformMeta): TransformResult {
meta.magicString.replaceAll("console.log", "console.debug");
return meta.magicString.hasChanged() ? { code: meta.magicString } : null;
},
}),
};The object-form hook, including filter and order, is passed through to the host. The handler's this value is preserved.
The wrapped handler always receives a TransformMeta with:
moduleType— supplied by Rolldown or inferred from the module ID.ast— an Oxc ESTree-compatibleProgram, parsed only when first read.magicString— aRolldownMagicString, created only when first read.ssr— Vite's optional SSR flag when supplied by Vite.
Existing Rolldown metadata is reused. When the host does not provide a native MagicString, a returned RolldownMagicString is converted to a string and an encoded source map automatically. An explicitly returned map, including null, is preserved.
Script extensions are normalized as follows:
| ID | moduleType |
|---|---|
.js, .mjs, .cjs |
js |
.jsx |
jsx |
.ts, .mts, .cts |
ts |
.tsx |
tsx |
.txt |
text |
Other extensions are used without the leading dot, and extensionless IDs default to js. Vite-style query parameters such as ?lang.ts and ?lang=tsx take precedence over the file extension.
Returns an object-form transform hook compatible with a Rolldown or Vite plugin.
The transform function. It receives (code, id, meta) and may return any Rolldown TransformResult, synchronously or asynchronously.
An optional native Rolldown transform hook filter. Filtering remains the responsibility of the host bundler.
Optional hook order: "pre", "post", or null.
npm run benchmark isolates wrapper dispatch without parsing an AST or creating a MagicString. Across three seven-sample, one-million-iteration runs on Node.js 24.16.0 and an Apple M5 Max, the direct handler measured 2.2 ns/op, the native Rolldown path measured 6.0–6.2 ns/op, and the Rollup fallback path measured 329.3–331.4 ns/op. The previous unconditional async boundary measured 29.4–30.0 ns/op. This measures wrapper overhead, not end-to-end build throughput; results vary by runtime and hardware.
- Node.js
^20.19.0 || >=22.12.0 - Rolldown
^1.0.0 - ESM
The automated compatibility matrix exercises builds through Rollup 4.62.2 and development transforms through Vite 6.4.3, 7.3.6, and 8.1.4. Run npm test to execute it or npm run benchmark to compare direct dispatch with native Rolldown and fallback Rollup paths.