notes/tsx/transform-backend.md
The transform-backend contract owns tsx's per-file integration semantics and backend re-verification requirements. Upstream implementation evidence belongs to the corresponding tool folder.
tsx uses esbuild's per-file APIs with CommonJS options in transformSync() and ESM options in transform() (CJS path, ESM path). The target is the running Node version (configuration); cache keys include source identity, transform options, esbuild version, and dynamic-import transformer version (sync key, async key).
The shared configuration enables source maps, whitespace minification, and keepNames without identifier or syntax minification (options).
The CommonJS extension hook transforms TypeScript-family files and JavaScript with static ESM syntax; .cjs receives only the targeted dynamic-import rewrite (extensions, selection). The ESM hook transforms TypeScript formats and TypeScript-extension URLs, conditionally transforms CommonJS source for interop, and otherwise limits JavaScript changes to dynamic-import rewriting (async path, sync path).
| Path | Import elision | Function identity | Role |
|---|---|---|---|
| esbuild | Removes unused qualified aliases but retains originating external imports | keepNames restores renamed symbols through module-scoped helpers | Current general backend |
| Oxc transform | Cascades type-only status to originating imports by default | Ordinary transform avoids name-restoration helpers; downleveling can add root-scope helpers | Blocked general-backend candidate |
| TypeScript | Cascading elision uses one-file binding/reference analysis | Not evaluated as a runtime backend | Semantic reference |
| Node stripping | Rejects non-erasable import aliases | Erasure-only path does not lower supported syntax | Gated fast path only |
| Gate | Status | Evidence |
|---|---|---|
| Distribution | Blocked | Node engines, native targets, and fallback behavior do not cover tsx's package contract (distribution, tsx engines) |
| Module output | Blocked | Public NAPI preserves ordinary ESM and exposes no complete ESM-to-CJS output (module output) |
| Helpers | Blocked | Runtime-helper resolution is not guaranteed from arbitrary user files; external mode requires a global, and inline mode is unavailable (helpers) |
| Runtime semantics | Blocked | Stage 3 decorators and namespace behavior do not satisfy the current contract (compatibility) |
| Configuration | Partial | Filename, target, JSX, decorator, class-field, and import-elision options require a tsx-owned adapter (configuration) |
| Diagnostics | Blocked before adapter | CommonJS import.meta can retain a stale parser error after replacement; resolve source-type/pre-parse behavior before converting remaining severity Error results to fatal failures (diagnostics) |
| Source maps | Adapter required | Normalize the parsed v3 map and remove sourcesContent outside coverage/debugger mode (source maps, tsx policy) |
Qualified import-equals aliases are removed by esbuild, but their originating external imports remain (esbuild analysis); current CommonJS output therefore retains module evaluation, while ESM output can retain named-export validation for bindings used only by erased TypeScript syntax.
tsx enables esbuild's keepNames, whose restoration calls depend on a module-scoped helper (esbuild identity); a transformed function containing nested restoration calls is not self-contained when serialized without its enclosing module.
A native-stripping path is eligible only through Node's public stripTypeScriptTypes() in explicit strip mode when the file needs no module conversion, the applicable tsconfig enables verbatimModuleSyntax, and the original URL is supplied as sourceUrl; every throw falls back to the configured transform backend (Node contract, TypeScript contract). Qualified import-equals, TSX, and transform-required syntax are never native-fast-path eligible.
| Contract | Required coverage | Reverify when |
|---|---|---|
| Import elision | ESM/CJS; type-only/runtime-use twins; explicit side-effect assertion | Backend or import analysis changes |
| Name preservation | Unchanged and collision-renamed function/class names | Renamer or name policy changes |
| Fresh-realm execution | Nested arrow, function, and class serialized into node:vm | Backend, target, or helper changes |
| Module output | Async ESM, sync ESM, sync CJS, import.meta, dynamic import | Backend replacement |
| Source maps | Sync/async maps plus stack line and column | Backend or map composition changes |
| Syntax/config | JSX factories, decorators, allowJs, tsconfig inclusion | Syntax or config adapter changes |
| CJS/ESM interop | Namespace shape, preparse annotations, require(esm), sync/async hooks | Backend or Node gate changes |
| Native fast path | Unsupported syntax falls back; supported output is behaviorally equivalent | Every supported Node major |
| Operational fit | Startup, package size, install/build smoke across supported platforms | Backend dependency or packaging changes |
Function.prototype.toString() portability after required syntax lowering introduces external helpers.