Commit Graph

6 Commits

Author SHA1 Message Date
overlookmotel d50c985fc2 refactor(packages/codegen): split categories into separate file (#26083)
Pure refactor. Move the category consts (`CAT_IDENT` etc) into a separate file.

2 reasons:

1. `write.ts` had got too large.
2. `index.ts`, which is not part of printer builds, imports `CAT_OTHER`. Importing it from a file containing only consts avoids any problems with minifier not being able to recognise dead code and remove it.
2026-08-25 13:46:38 +00:00
overlookmotel 29a774930a perf(packages/codegen): pass start and end to write and mark functions (#25986)
Optimization to sourcemap builds. This is what the rest of this stack has been working towards.

All the `writeWithMap*` and `markMap*` functions received a `node` param which they're extract `start` and `end` from. The problem is that these functions receive a range of different node types, so accessing `node.start` / `node.end` are megamorphic, involving an expensive dispatch.

Instead, extract `start` and `end` at call sites, where the type of `node` is already known (monomorphic), and pass them in to `writeWithMap*` / `markMap*` functions.

These functions still keep their `node` param, as it's needed for debug asserts, but it's never touched in release builds. #25989 removes it entirely from release builds too.

This has quite a large effect on perf. The cumulative perf gain of this stack (#25968 onwards) in the "without generation" sourcemaps benchmark is:

<table>
<tr>
<th align="left">Fixture</th>
<th align="right">Bytes</th>
<th align="right">Before</th>
<th align="right">After</th>
<th align="right">Faster by</th>
</tr>
<tr>
<td align="left">

`RadixUIAdoptionSection.jsx`

</td>
<td align="right">2,518</td>
<td align="right">0.0077 ms</td>
<td align="right">0.0059 ms</td>
<td align="right">+31.5%</td>
</tr>
<tr>
<td align="left">

`react.development.js`

</td>
<td align="right">72,141</td>
<td align="right">0.2549 ms</td>
<td align="right">0.2015 ms</td>
<td align="right">+26.9%</td>
</tr>
<tr>
<td align="left">

`lodash.js`

</td>
<td align="right">544,096</td>
<td align="right">1.14 ms</td>
<td align="right">0.9289 ms</td>
<td align="right">+23.5%</td>
</tr>
<tr>
<td align="left">

`App.tsx`

</td>
<td align="right">415,340</td>
<td align="right">1.84 ms</td>
<td align="right">1.51 ms</td>
<td align="right">+21.2%</td>
</tr>
<tr>
<td align="left">

`binder.ts`

</td>
<td align="right">193,077</td>
<td align="right">0.6598 ms</td>
<td align="right">0.5483 ms</td>
<td align="right">+20.6%</td>
</tr>
<tr>
<td align="left">

`kitchen-sink.tsx`

</td>
<td align="right">732,222</td>
<td align="right">5.12 ms</td>
<td align="right">4.33 ms</td>
<td align="right">+17.7%</td>
</tr>
<tr>
<td align="left">

`antd.js`

</td>
<td align="right">6,683,633</td>
<td align="right">23.52 ms</td>
<td align="right">20.30 ms</td>
<td align="right">+15.7%</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>

The vast majority of that gain is down to this PR, and the ones preceding it which also remove polymorphic/megamorphic accesses.

---

Note: Actually this change is unnecessary for `writeWithMapNamed`, `writeWithMapNamedPrivate`, and `writeWithMapNamedJSXNoLast` - they only receive a single type of node so are already monomorphic. But it's easier to be consistent, and this uniformity enables the further optimization in #25989. We could pare this back in a later PR, so we only pass in `start` and `end` where it's beneficial to do so.
2026-08-24 09:20:18 +00:00
overlookmotel 215a6a51ad perf(packages/codegen): add functions for writing/marking unnamed mappings (#25975)
Small optimization to sourcemap builds.

Add functions for writing with a source mapping, where it's known from what's being written that the mapping cannot need a name, because `node.name` is never a string, and change all call sites to use the appropriate function.

This allows removing 2 checks from ``recordSourceMapping``:

- `typeof node.name === "string"` - because that's now statically known.
- `printedName.length > 0` - because identifiers cannot be length.

All `write` and `markMap` functions take a `NamedMappableNode` or `UnnamedMappableNode`, which makes it a type error to call the wrong function.

`markMapNamed` becomes dead code, and is removed.
2026-08-24 07:24:06 +00:00
overlookmotel 3216cbb04c refactor(packages/codegen): rename write* and mark* functions (#25973)
Pure refactor. Just rename functions:

- `markWithMap` -> `markMap` for brevity.
- All functions which can produce a named mapping called `*Named`.

This mainly prepares the way for next PR #25975, which introduces separate `write` functions for where a mapping can never be named.
2026-08-22 13:24:58 +00:00
camc314 5444cbfaf8 perf(codegen): skip escaping harmless < tokens (#25564)
## Summary

- Detect actual case-insensitive `</script` sequences in the initial string-escape scan.
- Keep strings containing only harmless `<` on the bulk-write fast path.
- Add regression coverage for script closers, controls, paired and lone surrogates, and template quasis.

## Benchmark methodology

No permanent microbenchmark is added. Measurements used release bundles built with `pnpm --filter oxc-codegen run build` on Node v26.5.0, a one-off focused harness over representative string-literal ASTs, and the existing `packages/codegen/bench.bench.ts` parsed-AST fixtures. The focused harness used ten warmup batches and 20 measured batches per case; Vitest fixtures used 1s warmup and 3s measurement windows (50/20 iteration minima).

The removed one-off harness is retained for reference at [bench.string.bench.ts](https://github.com/oxc-project/oxc/blob/49e7a9d43bdc55a8f01075363bb8701ca5a9380c/packages/codegen/bench.string.bench.ts).

Focused release results (ms/print, mean ± standard deviation):

| Case | Before | After | Change |
| --- | ---: | ---: | ---: |
| Short ordinary string | 0.00003 ± 0.00000 | 0.00003 ± 0.00000 | no material change |
| Long string without `<` | 0.02048 ± 0.00114 | 0.01906 ± 0.00038 | within noise / slightly faster |
| Dense harmless HTML/SVG-like `<` (15,360 chars) | 0.05625 ± 0.00152 | 0.01283 ± 0.00036 | 77% faster |
| Mixed text with harmless `<` | 0.04766 ± 0.00210 | 0.02371 ± 0.00072 | 50% faster |
| Frequent mixed-case `</script` closers | 0.04350 ± 0.00074 | 0.04366 ± 0.00079 | within noise |

Existing parsed-AST benchmark fixtures (Vitest RME 0.15–0.79%): Radix 0.0028 -> 0.0027 ms, React development 0.0981 -> 0.0976 ms. App.tsx varied from 0.6261 to 0.6657 ms after versus 0.6415 ms before, so this PR makes no throughput claim for that fixture and shows no consistent regression.

A release CPU profile of the dense harmless case reduced `printEscapedStringContents` from 1,099 of 1,459 samples, plus 124 `^</script` regex samples, to zero; the after profile spends 321 of 383 samples in the single initial scan.
2026-08-13 08:49:27 +00:00
overlookmotel a4478e960a feat(codegen): add oxc-codegen package (#25488)
Add `oxc-codegen` package - a printer written in TS.

### Features

- Prints JS, JSX, TS, TSX.
- Matches `oxc_codegen` exactly on all Test262, TypeScript, Acorn-JSX fixtures.
- Very fast.

### Deficiencies

- Pretty-printing only (no compact/minified output).
- No support for printing comments.
- Source map support is semi-there, but largely untested, and poor API (design doc has some ideas on how to improve it).

### This PR adds

- The printer code.
- Conformance testing infra.
- Further tests for edge cases.
- README.
- Design doc - how it differs from `oxc_codegen` and why, what produces the perf.

### This PR doesn't add

- Not integrated into CI or release pipeline.

### Notes

This started as a slop port of `oxc_codegen`. I've iterated on that base, cleaned it up a lot, and at least skimmed through all of it. I think it's in fairly decent shape.

The design doc is I think worth a read. It explains the rationale for the choices, in particular the "1 codebase, 4 builds" approach.

I've put it in a new top-level directory called `packages`. It's not an app, so didn't seem to me to fit alongside `apps/oxlint` and `apps/oxfmt`. It's pure JS, so not like e.g. `napi/parser`. And it felt too heavyweight to put into `npm` dir. But maybe there's a better place for it.

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-08-12 13:41:31 +00:00