iTranslated by AI
Running TypeScript directly in Node.js v22.7.x
Recent versions of Node.js have introduced experimental support for TypeScript. You can now execute TypeScript scripts without relying on ts-node or tsx.
Roadmap
Understanding that this is an experimental feature, I'll give it a try.
tl;dr
# ~/.zshrc
alias nodets="node --experimental-strip-types --experimental-transform-types --experimental-detect-module --no-warnings=ExperimentalWarning"
# run
nodets run.ts
--experimental-strip-types
This is the simplest implementation, which just strips type annotations.
node --experimental-strip-types run.ts
This allows you to execute code like the following.
const x: number = 1;
console.log(x);
--experimental-transform-types
--experimental-strip-types cannot execute features that go beyond simple removal of type annotations, such as enum, namespace, and module. The --experimental-transform-types flag transforms these features.
node --experimental-strip-types --experimental-transform-types run.ts
const enum E {
A,
B,
C = SubEnum.X,
}
namespace xxx {
export namespace yyy {
export const zzz = 123;
}
}
module a {
export module b {
export const c = 456;
}
}
// See https://zenn.dev/uhyo/articles/typescript-module-option
console.log(E.A, xxx.yyy.zzz, a.b.c);
These features are somewhat like legacy artifacts of TypeScript; honestly, they aren't features I'd actively want to use just because they are now supported. However, you encounter them frequently when porting legacy assets, so it's better to have support than not.
Module Resolution
Since it only strips types, the file resolution rules for Node.js remain unchanged. Therefore, you cannot omit extensions in a module context.
// NG
import { foo } from "./foo";
// OK
import { foo } from "./foo.mts";
As a result, you need to ensure consistency on the tsconfig.json side, for example by using "moduleResolution": "Bundler" or "allowImportingTsExtensions": true so that extensions are not omitted. (Note, however, that Node's current TypeScript support ignores tsconfig.json.)
It also supports .mts and .cts to some extent, but I'll leave the explanation of their behavior to the following article:
My Use Case
I created a nodets alias and set up these options in it.
# ~/.zshrc
alias nodets="node --experimental-strip-types --experimental-transform-types --experimental-detect-module --no-warnings=ExperimentalWarning"
That said, I wouldn't do something as reckless as running an entire project with this, but it seems like a good fit for writing task scripts like zx's script/ci.ts.
// nodets script/ci.ts
import { $ } from 'zx';
$.verbose = true;
await $`echo "Hello, world!"`;
(I tend to write these kinds of things in Deno.)
If it stops working, I'll fix it myself. Since it's an experimental feature, I won't complain even if it breaks.
Others
The implementation is amaro, an swc-based implementation. Although the arguments become complex, it can be swapped as a loader.
Details on motivation, objectives, etc.: https://github.com/nodejs/node/blob/main/doc/api/typescript.md
Currently, .tsx and source maps are not supported, and it cannot resolve .ts files from other modules in node_modules, so its use cases are limited. However, I think it has made writing zx scripts incredibly easy.
Discussion