Skip to content

Instantly share code, notes, and snippets.

@mattpocock
Created May 15, 2025 08:45
Show Gist options
  • Select an option

  • Save mattpocock/e8c00d8dc5440d9366fe2c0eec92677b to your computer and use it in GitHub Desktop.

Select an option

Save mattpocock/e8c00d8dc5440d9366fe2c0eec92677b to your computer and use it in GitHub Desktop.

A Note On Node's TypeScript Support

Recently, Node has been moving closer and closer to supporting TypeScript as a first-class citizen.

In Node 23 and 24, you can now use node on the command line to run TypeScript files. This is currently in the experimental stage, and is known colloquially as --experimental-strip-types. In Node 23 or greater, you can run node index.ts. In versions 20-22, a flag is required:

node index.ts # Node 23+
node --experimental-strip-types index.ts # Node 20-22

--experimental-strip-types requires some additional configuration. Six tsconfig.json settings are required:

{
  "compilerOptions": {
    "module": "NodeNext",
    "noEmit": true,
    "rewriteRelativeImportExtensions": true,
    "verbatimModuleSyntax": true,
    "erasableSyntaxOnly": true
  }
}

Let's break down each one:

module is described in the sections above. It lets TypeScript know that we're using Node's module system.

noEmit tells TypeScript to not emit any files. We don't need to emit files because we're running the TypeScript files directly with Node.

rewriteRelativeImportExtensions allows you to import TypeScript files using the .ts extension:

import { example } from "./example.ts";

Since we're not creating JavaScript files, we need to point Node to the TypeScript files directly.

Next, we come to the issue of imports. Node's TypeScript support runs an extremely fast type stripper to strip the types in the file before running the code. One issue with this is that it doesn't know the difference between a type and a value, especially when it comes to imports.

verbatimModuleSyntax forces you to import types using the import type syntax:

import type { MyExampleType } from "./example.ts";

Without import type here, Node will throw a runtime error.

Finally, Node only supports some TypeScript features. The ones it doesn't support might feel familiar:

  • Enums
  • Namespaces
  • Class parameter properties

As we've seen in a previous chapter, each of these features requires some kind of transformation. For instance, the transpiled enum code looks very different from the original code:

// Original
enum MyEnum {
  A = "a",
  B = "b",
}

// Transpiled
var MyEnum;
(function (MyEnum) {
  MyEnum["A"] = "a";
  MyEnum["B"] = "b";
})(MyEnum || (MyEnum = {}));

However, features that don't require transformation are trivial to support. You simply strip the types and you're done:

// Original
const myFunction = (a: number, b: number) => a + b;

// Transpiled
const myFunction = (a, b) => a + b;

By only supporting type stripping (and not transformation), Node simiplifies its mental model to just "JavaScript with types". This reduces the maintenance burden on Node itself.

We can disable these features in TypeScript by turning on erasableSyntaxOnly. This will raise an error if any of these features are used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment