Files
oxc-project__oxc/packages/codegen/tsdown_plugins/strip_ts.ts
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

72 lines
2.4 KiB
TypeScript

import type { Plugin } from "rolldown";
/**
* Plugin to remove the TypeScript-only regions of `print.ts` from the JS-only builds.
*
* The `TS` build-time constant (see `src-js/globals.d.ts`) lets the minifier drop TS-only code
* in expression positions - `if (TS && node.declare)` folds to nothing when `TS` is `false`.
* But `case "TSEnumDeclaration":` switch arms are not expressions, and no minifier can remove an arm
* on the grounds that its node type never occurs. Those arms are fenced with `IF TS` / `END_IF`
* block-comment markers (the same convention as the raw transfer deserializer builds):
*
* ```ts
* /* IF TS *\/
* case "TSEnumDeclaration":
* ...
* break;
* /* END_IF *\/
* ```
*
* (`*\/` escapes above only because this is itself a block comment.)
*
* This plugin deletes everything from each `IF TS` marker line through its `END_IF` marker line.
* Once the arms are gone, the TS-only printer functions they called lose their last references,
* and tree-shaking removes them wholesale.
*
* It's a plain text transform (the fences are only visible in source text), so it must run before
* the AST-based plugins in the plugin list, and only in builds where `TS` is `false`.
*/
const REGION = /[ \t]*\/\* IF TS \*\/\n[\s\S]*?\/\* END_IF \*\/\n/g;
// A factory, so each build counts its own regions rather than sharing a total with the others
const stripTsPlugin = (): Plugin => {
let strippedRegions = 0;
return {
name: "strip-ts",
buildStart() {
strippedRegions = 0;
},
transform: {
// Only process TS files in `src-js/print` directory
filter: { id: /\/src-js\/print\/.+\.ts$/ },
handler(code) {
code = code.replace(REGION, () => {
strippedRegions++;
return "";
});
// Unbalanced or nested fences leave a marker behind.
// Most files have no fences at all, so the "did this plugin do anything" check is per build, in `buildEnd`.
if (code.includes("IF TS") || code.includes("END_IF")) {
throw new Error("strip-ts: unbalanced or nested `/* IF TS */` / `/* END_IF */` fences");
}
return code;
},
},
buildEnd() {
// No fences anywhere means they have been lost and this plugin is silently doing nothing
if (strippedRegions === 0) {
throw new Error("strip-ts: no `/* IF TS */` / `/* END_IF */` fences found");
}
},
};
};
export default stripTsPlugin;