How the TypeScript runtime works

Posted on 3/4/2022

I accidentally created a “lightweight TypeScript runtime” inside Node.js as part of Novo Cantico. Like the rest of the project, it’s a lot simpler than it seems:

  1. All nodes under under app/* are created as FsFile and FsDir objects
  2. All FsFile objects have a Buffer object
  3. For any file ending in .ts or .tsx:
    1. Its buffer is read as UTF8
    2. Transformed into JavaScript via sucrase with full import/export syntax, JSX support, and TypeScript support
    3. And turned into a JS function via vm.compileFunction

A tiny bit of wiring is needed to make require work (which sucrase transforms imports into), which is entirely the purpose of the small Module class.

The Runtime class just glues this all together, provides an entry point, and has (currently incomplete) shims for timeouts/intervals.

That’s the whole runtime.

The benefit is that I can now completely load up an entire tree of TypeScript code, run it, and shut it down, instantly, all in a normal Node.js program, without needing the official TypeScript compiler or Babel installed; and sucrase is very fast.

This is the basis of both TypeScript support and hot-reloading support in Novo Cantico apps.

Blog posts