Optimization to sourcemaps builds.
#25986 made the `node` param passed to `writeWithMap` etc redundant in sourcemap release builds.
Alter the TSDown plugin to remove the param from these functions in sourcemap release builds, and also remove the `node` argument from all call sites.
It was already removed in no-sourcemap builds - this only affects sourcemaps builds.
Pure refactor, in preparation for #25989.
Turn `unmap_writes` TSDown plugin into a factory function. This allows passing it arguments to control the plugin's behavior.
This change split into a separate PR to avoid a massive diff in #25989.
Refactor in preparation for #25989.
`remove_asserts` TSDown plugin removes debug assertions in release builds, but only when the assertion functions are imported from `asserts.ts`.
We have various other `debugAssert*` functions which are defined elsewhere. Make the plugin remove them too in release builds.
The reason this is needed is that many of these `debugAssert` functions receive `node`. #25989 removes `node` param from functions containing these assertions, which means `node` becomes a global. Minifier has to assume accessing a global may have side-effects and therefore does not remove these debug assertions. We need the plugin to do it.
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.
Optimization to sourcemap and no-sourcemap builds.
`writeWithMapNamed` now always receives an `Identifier`, so all call sites pass `last` as `CAT_IDENT`. Remove the param and just write `state.last = CAT_IDENT` directly. This avoids using a register to pass a value which is always the same, and makes setting `last` cheaper too.
Add a new `writeIdent` function which `writeWithMapNamed` is converted to in no-sourcemap builds, which also doesn't have a `last` param.
Continuation of #25980.
Add a specialized function `writeWithMapNamedPrivate` for writing a `PrivateIdentifier`. It feeds `hashLength: 1` to `markMapNamed`, which allows removing polymoprhic `node.type` access from `markMapNamed`.
The code being printed in paths that lead to `markMapNamed` is always the same as `node.name`, so just pass it in to `markMapNamed`, which removes a polymorphic `node.name` read there.
Additionally, `writeWithMapNamedNoLast` now only receives `Identifier` nodes, so becomes monomorphic.
Writing mappings for JSX identifiers requires different logic than for normal `Identifiers`.
- Add specialized function `writeWithMapNamedJSXNoLast` for writing `JSXIdentifier`s.
- Move the logic for getting original name of a `JSXIdentifier` into a separate function.
- Branch on an `isJSXIdentifier` param passed in to `markMapNamed`, to decide which function to use to get original identifier name.
The main motivation is to avoid having to pass `node` into `originalNameFromSource`, where reading `node.type` is a polymorphic access (because `node` could be an `Identifier`, an `PrivateIdentifier` or `JSXIdentifier`). This is part of a broader move to remove the reliance of all `write` and `markMap` functions on receiving polymorphic/megamorphic `node` params (later PRs in this stack).
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.
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.
Small optimization to no-sourcemap builds.
`printString` and `printNonNegativeFloat` receive `node` as their last param, but in no-sourcemap builds, all the code which uses `node` is removed anyway.
Use the mechanism added in #25970 to remove the `node` param from the function declarations, and the corresponding argument from all call sites. This avoids passing data into these functions pointlessly, freeing a register.
Alter how functions where are not used in no-sourcemap builds get removed from the build.
Previously, the plugin explicitly replaced calls to these functions with `void 0`. Instead:
1\. Add `if (SOURCEMAPS)` guards to these functions, so minifier sees that they're no-ops and removes the calls.
2\. Remove arguments from their call sites, so nothing gets left over by minifier.
Additionally, remove the params from the function declarations themselves.
The table in plugin specifies how many arguments to remove for each function.
These change does not alter the output code - the same stuff gets removed, just by a different mechanism. But this change enables the next PR #25971.
The TSDown `unmap_writes` plugin was a bit sloppy. It was over-engineered, doing multiple passes over the AST, printing it and re-parsing it. Condense this all down to a single AST pass.
In addition:
1\. Check the declarations of listed functions to make sure that `arity` accurately matches the actual declarations.
2\. Rename `plain` to `rename` - that better describes what this property is.
This does not alter the output code.
## Summary
Preserve source maps when `strip-ts` removes TypeScript-only printer regions from JS builds.
The plugin now uses Rolldown’s `magicString` transform context instead of a plain string replacement, so removed `/* IF TS */` regions retain correct mappings back to the original printer source.
Follow-on after #25585.
Pure style change. I hate the way LLMs put line breaks in weird places like after "the" - it makes comments hard to read. And what's with all the colons and semicolons?
No substantive changes, just reformatting comments and docs to avoid this unreadable formatting.
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>