import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import path from "node:path"; import fs from "node:fs"; const require = createRequire(import.meta.url); const here = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(here, ".."); const pkg = (name) => path.join(here, "node_modules", name); // A tool's platform binary is its own dependency, so resolve it from the tool, not from bench/. const platformPkg = (parent, prefix) => path.dirname(createRequire(require.resolve(parent)).resolve(`${prefix}${process.platform}-${process.arch}/package.json`)); /** * `positional` tools blank in place, so their output can be compared byte-for-byte with ts-blank-space. * `entry` is what gets bundled and minified for the size column; `binaries` are loaded from disk at runtime. * `lowers` marks a tool as a downlevel-mode candidate (enums/namespaces/parameter properties down-leveled), * so throughput.mjs's --mode=downlevel includes it. `native` marks a NAPI addon or a separate native binary, * whose "transfer size" is really install/binary size, not something a bundler ships to wasm/JS consumers; * see the README's downlevel-mode table note. */ export const tools = { "typestrip": { positional: true, entry: path.join(root, "src/index.js"), binaries: [process.env.TYPESTRIP_ERASE_WASM ? path.resolve(process.env.TYPESTRIP_ERASE_WASM) : path.join(root, "build/erase.wasm")], // Report-and-continue, like ts-blank-space, so non-erasable inputs are compared rather than thrown on. load: async () => { const { transformSync } = await import(path.join(root, "src/index.js")); return (src) => transformSync(src, { onError: () => {} }); }, lowers: true, downlevel: async () => { const { transformSync } = await import(path.join(root, "src/index.js")); return (src) => transformSync(src, { onError: () => {}, mode: "downlevel" }); }, }, // Same wrapper, but measured as a downlevel-mode consumer would load it: erase.wasm loads lazily // on first erase call and downlevel.wasm loads lazily on first downlevel call, so a pure // downlevel-mode consumer never pays for erase.wasm at all. "typestrip (downlevel)": { positional: false, lowers: true, entry: path.join(root, "src/index.js"), binaries: [process.env.TYPESTRIP_DOWNLEVEL_WASM ? path.resolve(process.env.TYPESTRIP_DOWNLEVEL_WASM) : path.join(root, "build/downlevel.wasm")], load: async () => { const { transformSync } = await import(path.join(root, "src/index.js")); return (src) => transformSync(src, { onError: () => {}, mode: "downlevel" }); }, }, "ts-blank-space": { positional: true, entry: path.join(root, "vendor/ts-blank-space/out/index.js"), binaries: [], load: async () => (await import(path.join(root, "vendor/ts-blank-space/out/index.js"))).default, }, oxidase: { // No distinct erase mode: transpile() always down-levels enums/namespaces/parameter properties, // in place, alongside the erasable-syntax blanking. Shown only in the downlevel-mode table. positional: true, lowers: true, entry: path.join(pkg("oxidase"), "wasm_node/oxidase_wasm_bindings.js"), binaries: [path.join(pkg("oxidase"), "wasm_node/oxidase_wasm_bindings_bg.wasm")], load: async () => require("oxidase").transpile, }, "@swc/wasm-typescript": { positional: true, entry: path.join(pkg("@swc/wasm-typescript"), "wasm.js"), binaries: [], load: async () => { const { transformSync } = require("@swc/wasm-typescript"); return (src) => transformSync(src, { mode: "strip-only" }).code; }, }, "@swc/wasm-typescript (transform)": { positional: false, lowers: true, entry: path.join(pkg("@swc/wasm-typescript"), "wasm.js"), binaries: [], load: async () => { const { transformSync } = require("@swc/wasm-typescript"); return (src) => transformSync(src, { mode: "transform" }).code; }, }, amaro: { // Node's node:module stripTypeScriptTypes below is this package (swc_fast_ts_strip via wasm), // vendored into core. amaro is what you'd ship to get the same engine on older Node or other runtimes. positional: true, entry: path.join(pkg("amaro"), "dist/index.js"), binaries: [], load: async () => { const { transformSync } = require("amaro"); return (src) => transformSync(src, { mode: "strip-only", sourceMap: false }).code; }, }, "node:module stripTypeScriptTypes": { positional: true, builtin: true, load: async () => { process.removeAllListeners("warning"); const { stripTypeScriptTypes } = require("node:module"); return (src) => stripTypeScriptTypes(src, { mode: "strip" }); }, }, sucrase: { positional: false, lowers: true, entry: path.join(pkg("sucrase"), "dist/esm/index.js"), binaries: [], load: async () => { const { transform } = require("sucrase"); return (src) => transform(src, { transforms: ["typescript"], disableESTransforms: true, keepUnusedImports: true }).code; }, }, "oxc-transform (native)": { positional: false, lowers: true, native: true, entry: path.join(pkg("oxc-transform"), "index.js"), binaries: [platformPkg("oxc-transform", "@oxc-transform/binding-")].flatMap((d) => fs.readdirSync(d).filter((f) => f.endsWith(".node")).map((f) => path.join(d, f))), load: async () => { const { transformSync } = require("oxc-transform"); return (src) => { const r = transformSync("input.ts", src, {}); if (r.errors.length) throw new Error(r.errors[0].message); return r.code; }; }, }, "ts.transpileModule": { positional: false, lowers: true, entry: path.join(pkg("typescript"), "lib/typescript.js"), binaries: [], load: async () => { const ts = require("typescript"); const compilerOptions = { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ESNext, verbatimModuleSyntax: true }; return (src) => ts.transpileModule(src, { compilerOptions }).outputText; }, }, "esbuild (native)": { // Native Go binary, driven over stdio by lib/main.js; transformSync spawns (and reuses) that // long-lived service process. Its "size" below is the binary esbuild actually ships, not something // a bundler links in, so it isn't comparable to the wasm/JS rows; see the README note. positional: false, lowers: true, native: true, entry: path.join(pkg("esbuild"), "lib/main.js"), binaries: [path.join(platformPkg("esbuild", "@esbuild/"), "bin/esbuild")], load: async () => { const esbuild = require("esbuild"); // experimentalDecorators: esbuild doesn't know a file's tsconfig in transformSync, and without // it rejects parameter/property decorators outright; TypeScript projects that use them (common // in Angular- and NestJS-style DI) set this, so it's the fair, documented option to pass here. const tsconfigRaw = { compilerOptions: { experimentalDecorators: true } }; return (src) => esbuild.transformSync(src, { loader: "ts", tsconfigRaw }).code; }, }, };