## Summary
- update workspace test dependencies to Vitest 5.0.0
- pin Vitest and related tooling to Vite 8.2.2
- migrate removed sequential and benchmark APIs
- update the pnpm lockfile
Over the weekend, I asked Fable to work on optimizing write operations when assembling the output string.
This uncovered a disturbing finding.
### Unrealistic benchmarks
The output string is assembled with repeated `output += segment` calls. This creates "cons strings" - a rope where each append creates a new string which references the previous `output` as its left side, and `segment` as its right side. On each append the rope gets longer - forming a linked list of segments.
That rope-string is what `printSync` returns to the user. It is a string, so it's correct, but there's a problem. In order to actually use that string for anything (write it to a file, index into it, search it, run a regex on it, etc) it has to be flattened. V8 does that transparently, so it works fine, but it's a costly operation.
So the disturbing finding was that we were ignoring that cost - we pushed it onto the user, and didn't measure it in our benchmarks. So, once this hidden cost is included, the codegen isn't as fast as we thought it was.
### Including the flatten cost
This PR alters the benchmarks to include string flattening via the most common path that V8 takes in user code. The effect is pronounced (higher is worse):
| Fixture | Bytes | Change |
|:---| ---:| ---:|
| `tiny.js` | 26 | \+92.4% |
| `RadixUIAdoptionSection.jsx` | 2,424 | \+303.8% |
| `react.development.js` | 50,496 | \+75.2% |
| `binder.ts` | 126,212 | \+85.9% |
| `lodash.js` | 182,262 | \+161.5% |
| `App.tsx` | 298,130 | \+146.8% |
| `kitchen-sink.tsx` | 662,560 | \+116.8% |
| `antd.js` | 5,100,647 | \+72.5% |
`RadixUIAdoptionSection.jsx` suffers in particular (4x slower) because one of the `JSXText` elements contain non-ASCII characters. This means the string has to be flattened to a 2-byte string (UTF-16) instead of the faster 1-byte (Latin1) representation. This encoding is twice as large, and hits V8's slow paths (because its optimized for 1-byte strings).
This doesn't mean that codegen has got slower - we're just now measuring it more realistically.
### Source maps builds
Source map builds are not affected - their perf doesn't change. They already flattened the `output` string by calling `indexOf` on it, so they were already paying the cost, we just didn't realize it.
### What to do?
The good news is that there are some things we can do to mitigate the impact - see following PRs.
Add benchmarks for source maps in `oxc-codegen`.
2 benchmarks:
1. Full end-to-end.
2. Without the actual sourcemap generation at the end.
The 2nd is to measure the overhead that pushing to the sourcemap arrays adds while printing in isolation.
Pin the Codegen benchmark fixture to an immutable `benchmark-files` commit. This makes benchmark inputs reproducible and prevents `main` updates from silently changing results.
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>