Back to Tsx

Transform backend

notes/tsx/transform-backend.md

4.23.97.6 KB
Original Source

Transform backend

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.

Current integration boundary

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).

Transform selection

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).

Backend capability matrix

PathImport elisionFunction identityRole
esbuildRemoves unused qualified aliases but retains originating external importskeepNames restores renamed symbols through module-scoped helpersCurrent general backend
Oxc transformCascades type-only status to originating imports by defaultOrdinary transform avoids name-restoration helpers; downleveling can add root-scope helpersBlocked general-backend candidate
TypeScriptCascading elision uses one-file binding/reference analysisNot evaluated as a runtime backendSemantic reference
Node strippingRejects non-erasable import aliasesErasure-only path does not lower supported syntaxGated fast path only

Oxc candidate gates

GateStatusEvidence
DistributionBlockedNode engines, native targets, and fallback behavior do not cover tsx's package contract (distribution, tsx engines)
Module outputBlockedPublic NAPI preserves ordinary ESM and exposes no complete ESM-to-CJS output (module output)
HelpersBlockedRuntime-helper resolution is not guaranteed from arbitrary user files; external mode requires a global, and inline mode is unavailable (helpers)
Runtime semanticsBlockedStage 3 decorators and namespace behavior do not satisfy the current contract (compatibility)
ConfigurationPartialFilename, target, JSX, decorator, class-field, and import-elision options require a tsx-owned adapter (configuration)
DiagnosticsBlocked before adapterCommonJS 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 mapsAdapter requiredNormalize the parsed v3 map and remove sourcesContent outside coverage/debugger mode (source maps, tsx policy)

Current compatibility boundaries

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.

Invariants

  • Evaluate backends in per-file, external-module mode; bundle-only output does not establish loader compatibility.
  • Target the running Node version so supported syntax is not lowered unnecessarily.
  • Preserve observable function and class names; disabling name preservation is not a default fix.
  • Current-target TypeScript transformation must not add free helper dependencies to otherwise self-contained functions.
  • A replacement backend must match TypeScript's default cascading elision, including removal of an emptied import and its side effects; explicit/verbatim imports preserve runtime intent.
  • Keep public configuration backend-neutral; do not expose backend escape hatches or make cache settings alter semantics.
  • Do not add library detection, output regexes, helper surgery, or a second parser to patch one backend gap.
  • Select native stripping only when a gate proves equivalent behavior; gate misses fall back to the configured transform backend.
  • Validate source maps through observable locations instead of generated byte equality.

Native strip gate

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.

Re-verification matrix

ContractRequired coverageReverify when
Import elisionESM/CJS; type-only/runtime-use twins; explicit side-effect assertionBackend or import analysis changes
Name preservationUnchanged and collision-renamed function/class namesRenamer or name policy changes
Fresh-realm executionNested arrow, function, and class serialized into node:vmBackend, target, or helper changes
Module outputAsync ESM, sync ESM, sync CJS, import.meta, dynamic importBackend replacement
Source mapsSync/async maps plus stack line and columnBackend or map composition changes
Syntax/configJSX factories, decorators, allowJs, tsconfig inclusionSyntax or config adapter changes
CJS/ESM interopNamespace shape, preparse annotations, require(esm), sync/async hooksBackend or Node gate changes
Native fast pathUnsupported syntax falls back; supported output is behaviorally equivalentEvery supported Node major
Operational fitStartup, package size, install/build smoke across supported platformsBackend dependency or packaging changes

Non-goals

  • Guarantee arbitrary Function.prototype.toString() portability after required syntax lowering introduces external helpers.
  • Treat a backend-specific option or partial prepass as a substitute for the full backend contract.