Execute (ttsx)
ttsx runs a TypeScript file directly, like tsx or ts-node, but it type-checks first and applies your plugins.
Run a file
npx ttsx src/index.tsttsx:
- Resolves
tsconfig.jsonfrom the current directory (or-P path/to/tsconfig.json). - Type-checks the project, including every plugin you’ve configured.
- If the check passes, executes the script natively.
Type errors stop execution. So do @ttsc/lint errors (warnings don’t).
Projects that enable allowImportingTsExtensions are supported. During the runtime emit, ttsx rewrites relative .ts, .tsx, .mts, and .cts import specifiers to the matching JavaScript extensions so Node can execute the output. The emitted JavaScript lives in a per-run cache directory and is removed after the script exits.
Raw-TypeScript dependencies
The type-check covers your whole program, but at runtime your dependencies are loaded by Node directly. When part of the graph is ESM, Node trips over dependencies that ship raw TypeScript:
- A workspace dependency (a
node_modulessymlink whose real files live outsidenode_modules) gets its types stripped by Node, but its own extensionless relative imports (import "./util") are rejected withERR_MODULE_NOT_FOUND. - A published dependency that ships
.tsinsidenode_modulescannot be loaded at all, because Node refuses to strip types undernode_modules(ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING).
ttsx installs Node module hooks in the run, the same approach tsx uses, so both cases just work without touching the dependency’s exports, sources, or tsconfig:
- a
resolvehook probes the candidate extensions (and directoryindexfiles) for an extensionless relative import anywhere in the graph; - a
loadhook transpiles raw.tsfiles undernode_modules, scoped so workspace neighbours keep Node’s native type-stripping path.
These hooks are runtime-only and never weaken the compile gate: the up-front tsgo build still deep-checks the whole program, imported workspace neighbours included, so a type error in a dependency’s source fails the run before anything executes. A genuinely missing module still throws ERR_MODULE_NOT_FOUND. This makes ttsx strictly stronger than tsx: a full type-check plus the same runtime reach.
When an ESM entry imports named bindings from a CommonJS-classified source package, ttsx also exposes names re-exported through nested export * barrels. Node’s CommonJS interop can miss those names because TypeScript-Go lowers them through dynamic helper calls, so the runtime hook makes the emitted CommonJS names visible without changing the package’s CommonJS require path.
Pass arguments to the script
Use -- to separate ttsx flags from the script’s own args:
npx ttsx src/server.ts -- --port 3000 --watchEverything after -- is passed through to process.argv unchanged.
Preload modules
Same as Node’s --require:
npx ttsx -r ./preload.cjs src/index.ts
npx ttsx -r dotenv/config src/index.tsRepeatable: -r a -r b preloads a then b. These modules are passed to Node’s raw --require loader; ttsx does not compile preload files for you.
Pick a tsconfig
npx ttsx -P tsconfig.scripts.json src/seed.tsFlags
| Flag | Meaning |
|---|---|
-P, --project <file> | Use an explicit tsconfig.json. |
--cwd <dir> | Resolve the entrypoint and project from this directory. |
--cache-dir <dir> | Override the runtime and source-plugin cache root. |
--binary <path> | Use an explicit TypeScript-Go binary. |
--no-plugins | Build the entry’s project without loading ttsc plugins. |
-r, --require <module> | Preload a module before the entrypoint. Repeatable. |
--singleThreaded | Run TypeScript-Go single-threaded. Mirrors tsc --singleThreaded. |
--checkers <n> | Type-checker pool size. Mirrors tsc --checkers. |
-h, --help | Show help. |
-v, --version | Print the runner version. |
Any flag ttsx does not own, placed before the entry file, is forwarded to the tsgo type-check, ttsx --strict src/index.ts works like the matching tsgo invocation. Flags after the entry go to the running program as its own process.argv, the same as node.
When ttsx vs ttsc
| Task | Use |
|---|---|
| One-off script, fast feedback | ttsx src/script.ts |
| Build for deployment | ttsc |
| Run inside CI to gate on types | ttsc --noEmit |
| Run inside CI to gate on types and execute tests | ttsx test/index.ts |
ttsx vs tsx vs ts-node
tsx | ts-node | ttsx | |
|---|---|---|---|
| Transpile speed | Fast | Slow | Fast |
| Type-checks before running | No | No (by default) | Yes |
| Plugin support | No | Custom transformers | First-class |
| TypeScript-Go runtime | No | No | Yes |
If you currently use tsx: switching is one command; ergonomics are the same.
Plugins and cache
ttsx uses the same tsconfig.json and plugins as ttsc. If a lint or check plugin reports an error, the script never runs. Transform plugins rewrite the code before it executes.
Compiled JavaScript is temporary and cleaned after the script exits. Source plugin binaries still use the shared ttsc plugin cache, so repeated runs do not rebuild unchanged Go sidecars. Use --cache-dir when you want both the temporary runtime output root and the source-plugin binary cache anchored in a known directory. Relative --cache-dir values resolve from --cwd; ttsx places runtime output under <cache-dir>/project/<pid> and plugin binaries under <cache-dir>/plugins. The runtime project subtree is still removed after each run. Use npx ttsc clean --cache-dir <dir> to wipe an explicit plugin cache.
See also
- TTSC ·
ttscCLI: the type-check / build front of the same pipeline. - Lint & Prettier: the linter that runs alongside the compile.
- FAQ: common runner gotchas and Node-version questions.