### 🚀 Features -ca649e0ecma: Define math constants as known globals and resolve their types (#26585) (Armano) -9ef028ccodegen: Add `ascii_only` option (#25994) (Samuel Attard) -80a76a0minifier: Negate binary comparison for `typeof x < 'u'` (#26367) (Armano) ### 🐛 Bug Fixes -8ca76daparser: Reject `accessor` modifiers on methods (#26617) (camc314) -1916f31parser: Reject `readonly` modifier on constructors (#26612) (camc314) -1c42008parser: Handle escaped let in for loops (#26583) (camc314) -d21d5cfparser: Recognize annotated empty arrows in conditionals (#26537) (camc314) -d7713adparser: Classify Unicode line breaks in block comments (#26536) (camc314) -75cd919transformer: Preserve receivers in private optional chains (#26535) (camc314) -b32d25dparser: Reject escaped import-phase keywords (#26534) (camc314) -47b8311parser: Recognize contextual binding names in type lookaheads (#26532) (camc314) -d6b6705parser: Require arrow separator in TypeScript function types (#26529) (camc314) -e98beefparser: Disambiguate await using in for initializers (#26527) (camc314) -92afee6parser: Allow parenthesized JSX comma expressions with preserve_parens=false (#26524) (camc314) -31508b1parser: Reject return types on constructor overloads (#26523) (camc314) -a091fc4parser: Validate await context for await using declarations (#26495) (camc314) -5501e86parser: Disallow in expressions in using for-loop initializers (#26490) (camc314) -c8e5fa7parser: Allow escaped type names in import and export specifiers (#26487) (camc314) -2dcee2fparser: Reject async modifiers on class fields (#26486) (camc314) -973d58eparser: Require comma after TypeScript this parameter (#26480) (camc314) -32d00c5codegen: Preserve instantiation expression precedence (#26424) (camc314) -cfa47abparser: Allow `in` expressions in class static blocks (#26423) (camc314) -5986187packages/codegen: Preserve private-in right operand precedence (#26420) (camc314) -f8e6c6cpackages/codegen: Preserve in restriction through yield arguments (#26421) (camc314) -d61e3bfparser: Validate TS named tuple rest elements (#26419) (camc314) -ae6c386codegen: Preserve in restriction through yield arguments (#26413) (camc314) -42ac916codegen: Preserve private-in right operand precedence (#26411) (camc314) -10521b2parser: Allow escaped type default import bindings (#26409) (camc314) -72cb5e3parser: Reject rest parameters in getters (#26400) (camc314) -6e15ad5packages/codegen: Print matching quoted import names as identifiers (#26404) (camc314) -bbbb4bcpackages/codegen: Preserve private-in left operand precedence (#26403) (camc314) -a111b5bpackages/codegen: Print accessibility modifiers before abstract (#26402) (camc314) -4e76602parser: Allow `in` in arrow block bodies within `for` initializers (#26395) (camc314) -b20fc19parser: Reject partially parenthesized mixed coalesce expressions (#26394) (camc314) ### ⚡ Performance -1f902a6isolated_declarations: Key scope maps by `Ident` (#26380) (Dunqing) -a242469minfiier: Reduce allocs when creating indirect access (#26601) (Armano) -5b4787fminifier: Update chain expressions in place (#26544) (Armano) -0bc1661minifier: Try merging before creating new expression statements (#26556) (Armano) -c78d707minifier: Process newly created stmt in handle_if_statement (#26541) (Armano) -029c84bminfier: Update expressions in place when substituting alternate syntax (#26460) (Armano) -d198982codegen: Outline postfix source mapping work (#26450) (camc314) -53f006eecmascript: Format small integer literals with itoa (#26446) (camc314) -8bfb8c0codegen: Avoid duplicate sourcemap name lookups (#26441) (camc314) ### 📚 Documentation -38533acast: Move type annotation span comment to span field (#26522) (camc314)
oxc-codegen
Fast, synchronous code generation for JavaScript and TypeScript ASTs.
oxc-codegen turns an ESTree or
TS-ESTree AST into formatted source
code. It supports JavaScript, JSX, TypeScript, and TSX.
The printer is a port of Oxc's Rust oxc_codegen crate. With the default options, both printers
produce byte-identical output: tab indentation, double-quoted strings, and no comments.
Installation
npm install oxc-codegen
oxc-codegen is ESM-only and requires Node.js ^20.19.0 or >=22.12.0.
Quick start
Pair it with oxc-parser to parse and print source code:
import { printSync } from "oxc-codegen";
import { parseSync } from "oxc-parser";
const { program } = parseSync("input.js", "const answer=6*7");
const { code } = printSync(program);
console.log(code);
// const answer = 6 * 7;
You can also print a manually constructed AST:
const program = {
type: "Program",
sourceType: "script",
body: [
{
type: "ExpressionStatement",
expression: {
type: "CallExpression",
callee: {
type: "MemberExpression",
object: { type: "Identifier", name: "console" },
property: { type: "Identifier", name: "log" },
computed: false,
optional: false,
},
arguments: [{ type: "Literal", value: "Hello!" }],
optional: false,
},
},
],
};
console.log(printSync(program).code);
// console.log("Hello!");
TypeScript and TSX
Set ts when the AST can contain TypeScript nodes. For TSX, set both ts and jsx:
const { program } = parseSync("component.tsx", "const Box = <T,>(value: T) => <div>{value}</div>");
const { code } = printSync(program, {
ts: true,
jsx: true,
});
API
printSync(node, options?)
function printSync(
node: ESTree.Program | ESTree.Statement,
options?: Options,
): {
code: string;
map: SourceMap | null;
};
Prints a complete Program or a single statement and returns the generated source code,
and (when requested) a standard Source Map v3 object.
import { printSync } from "oxc-codegen";
import { parseSync } from "oxc-parser";
const sourceText = "const answer=6*7";
const { program } = parseSync("input.js", sourceText);
const { code, map } = printSync(program, {
sourcemap: true,
sourceFilename: "input.js",
sourceText,
});
Source-map mappings require sourceText and nodes with valid Oxc start / end offsets.
A manually constructed AST without offsets can still be printed, but its source map has
an empty mappings string.
Options
| Option | Type | Default | Description |
|---|---|---|---|
indent |
string |
"\t" |
Non-empty string of spaces and/or tabs used for one indent level |
startingIndentLevel |
number |
0 |
Starting indent level, from 0 to 1000 |
jsx |
boolean |
false |
Enable TSX-safe printing for ambiguous TypeScript syntax |
ts |
boolean |
false |
Select the printer that supports TypeScript nodes |
sourcemap |
boolean |
false |
Return a Source Map v3 object in map |
sourceFilename |
string |
"" |
Original source filename recorded in the source map |
sourceText |
string |
- | Original text required for source-map mappings and content |
Why pure JavaScript?
Most Oxc packages use native bindings. This package deliberately does not: when an AST already
lives in JavaScript, passing the entire object graph across a JS/native boundary can cost more than
printing it in place. oxc-codegen avoids that serialization and uses specialized printer builds
for JavaScript and TypeScript workloads.
See DESIGN.md for the implementation details and performance constraints.
Current limitations
- Comments are not printed.
- Minified output is not supported.
Benchmarks
Representative time per printSync call:
| Fixture | Bytes | Time |
|---|---|---|
tiny.js |
26 | 0.0001 ms |
RadixUIAdoptionSection.jsx |
2,518 | 0.0070 ms |
react.development.js |
72,141 | 0.1518 ms |
binder.ts |
193,077 | 0.3364 ms |
App.tsx |
415,340 | 1.2912 ms |
lodash.js |
544,096 | 0.7977 ms |
kitchen-sink.tsx |
732,222 | 4.2924 ms |
antd.js |
6,683,633 | 16.9652 ms |
These figures come from one machine and are illustrative, not a regression baseline.
Results vary between runs, most noticeably for large fixtures such as antd.js.