HomeArticles

TypeScript's `erasableSyntaxOnly` Flag

TypeScript

TypeScript adds a new flag to its compiler with version 5.8: erasableSyntaxOnly. It ensures you won’t use TypeScript features that generate code.

Like enums.

enum Result {
Ok = "Ok",
Error = "Error",
}

// results in

var Result;
(function (Result) {
Result["Ok"] = "Ok";
Result["Error"] = "Error";
})(Result || (Result = {}));

Or Namespaces.

namespace Environment {
let releaseMode: boolean;
}

// results in

(function (Environment) {
let releaseMode;
})(Environment || (Environment = {}));

Or parameter properties in class constructors.

class Person {
constructor(
private name: string,
private age: number
) {}
}

// results in

class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}

Everything you’re now allowed to do is write stuff that can be removed. A style that lots of people – myself included – always recommended.

I love that Ryan mentions “ideological purists” in the corresponding issue. I’m far from being a purist, let alone an ideological one. But I do recommend to keep TypeScript features to “type syntax” only for a variety of reasons:

  1. No surprises. I have seen countless companies and teams commit heavily to using everything TypeScript has to offer, no questions asked. The result was that they didn’t know what the outcome of their file was in the end. They implemented crucial libraries that were four times the size of what they actually needed just because TypeScript was blowing up their codebase so much.
  2. Stable APIs. Using enums, they created APIs for outside teams that were constantly changing and causing breaking changes. If you expect an enum in a function call but later need to get rid of that enum because of size and performance reasons, the API becomes incompatible with the previous version, even though you most likely pass strings in the final output. A simple string type or subset of string would’ve done the same job and is much more future-proof.
  3. Better compatibility between TypeScript subsets. isolatedModules is a very common flag, and it has some implications for everything that can be globally declaration-merged, like namespaces. If you rely on declaration merged namespaces that generate code (important! There are some that just work on a type level), knowingly or not, you create incompatibility with projects that suddenly require isolatedModules.
  4. Cognitive overhead. TypeScript has so many features that I like to reduce them to a few that get most of the job done. Defining types and annotating types is simple and very effective.

So yeah, you could argue that everything above is “ideological” or comes from a purist’s standpoint. However, it’s important to say that the primary motivator for this change wasn’t any of the ideological reasons the TypeScript team mentions (Those discussions can be very dogmatic, and I love that the team doesn’t give in to those arguments). The main reason was that TypeScript finally landed in Node.js! That’s right; it’s now totally legal to write TypeScript files (with a .ts, .cts, or .mts extension to accommodate the module system of your needs), slap a minimalist tsconfig.json in your project’s root, and run it without any further transpilation step!

// tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
}
}
// index.mts
import { readFileSync } from "node:fs";

function readFile(path: string): string {
return readFileSync(path).toString();
}

console.log(readFile("index.mts"));

Depending on your editor, you need to install the Node.js types, but all in all, you don’t need anything more. Run node index.mts, and you’re good. However, this feature comes with a little caveat. It only erases type information rather than doing a full transpile. This means that

  1. You need to stick with the features of the EcmaScript version of your Node.js runtime and can’t transpile down to older ES versions.
  2. You can’t use features that generate code. Like experimental decorators, parameter properties in class constructors, namespaces, or enums.

All of those features need to take the code they get and rewrite it into something different. I assume that Node.js simply streams the file’s content and strips away type information on the go, making sure the transpilation step doesn’t severely impact performance.

Personally, I think we’re going to write a lot of TypeScript in the future, which will just be erased. This aligns with the proposal to bring Types as Comments to browsers, and it works well with environments where you write a few lines of JavaScript to automate your tasks, for example.

But see it as a feature that you can opt-in to. I won’t give the dogmatic advice to abolish namespaces and enums. I won’t tell you never to use parameter properties in class constructors. I just tell you what the impact of it is, what my experience with it was, and that I’m happy that there’s a flag that can turn them off if we need to.

Related Articles