mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
a4478e960a
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>
69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
import { parseSync } from "oxc-parser";
|
|
|
|
import type { Plugin } from "rolldown";
|
|
|
|
/**
|
|
* Plugin to rewrite the printer's top-level function declarations as `const` bindings.
|
|
*
|
|
* ```ts
|
|
* // Original code
|
|
* function printIf(node: ESTree.IfStatement, state: State): void {
|
|
* // ...
|
|
* }
|
|
*
|
|
* // After transform
|
|
* const printIf = (node: ESTree.IfStatement, state: State): void => {
|
|
* // ...
|
|
* };
|
|
* ```
|
|
*
|
|
* A function declaration creates a binding which could be assigned to later, so a call has to read
|
|
* the current value out of the module's scope. A `const` binding cannot, so V8 can treat it
|
|
* as a constant and call it directly. Measured on all 7 benchmark fixtures, it is worth ~3%.
|
|
*
|
|
* It really is the `const`, and not the arrow function or the smaller output it allows - the same rewrite
|
|
* with `let` measures ~4% slower than this one, and slower than leaving the declarations alone.
|
|
* Do not relax it to `let`.
|
|
*
|
|
* The source keeps its declarations - they hoist, they read better, and they are what the rest of
|
|
* the codebase looks like. This is why the rewrite is a build step rather than how printer is written.
|
|
*/
|
|
const plugin: Plugin = {
|
|
name: "const-functions",
|
|
transform: {
|
|
// Only process TS files in `src-js/print` directory
|
|
filter: { id: /\/src-js\/print\/.+\.ts$/ },
|
|
|
|
handler(code, path, meta) {
|
|
const magicString = meta.magicString!;
|
|
const { program, errors } = parseSync(path, code);
|
|
if (errors.length !== 0) throw new Error(`Failed to parse ${path}: ${errors[0].message}`);
|
|
|
|
for (const statement of program.body) {
|
|
let fn;
|
|
if (statement.type === "FunctionDeclaration") {
|
|
fn = statement;
|
|
} else if (
|
|
statement.type === "ExportNamedDeclaration" &&
|
|
statement.declaration?.type === "FunctionDeclaration"
|
|
) {
|
|
fn = statement.declaration;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (fn.async || fn.generator) continue;
|
|
|
|
// `function name` -> `const name =`, `{` of the body -> `=> {`, and `}` -> `};`
|
|
magicString.overwrite(fn.start, fn.id!.end, `const ${fn.id!.name} =`);
|
|
magicString.appendLeft(fn.body!.start, "=> ");
|
|
magicString.appendRight(fn.end, ";");
|
|
}
|
|
|
|
return { code: magicString };
|
|
},
|
|
},
|
|
};
|
|
|
|
export default plugin;
|