The TypeScript Developers Toolbox - Part 2

Part Two: Running TypeScript Projects

by: Simon Plenderleith

TypeScript is JavaScript with syntax for types layered on top, or what is sometimes referred to as a "superset" of JavaScript. If you're working with TypeScript in your projects, you might already know that a compile step is required to transform your TypeScript code into JavaScript code, which can then be executed in a JavaScript runtime.

The official compiler for TypeScript is the TypeScript Compiler. You'll most commonly encounter it when using the tsc command-line tool, which allows you to type check and compile TypeScript to JavaScript. Beyond the TypeScript compiler however, there's a whole world of bundlers, runtimes and code runners, which are capable of compiling TypeScript, and in some cases automatically running the compiled JavaScript code for you.

When should you use a bundler? And what are code runners even for? It can be tricky to know how to choose the right tool for the job! Let's explore together, and gain a better understanding of the options that are available for running TypeScript code.

Bundlers

Bundlers are typically used to combine and compress web assets — such as HTML, CSS, JavaScript and images — ready for them to be served to a web browser. They're also sometimes used in serverless projects to minimize bundle sizes and optimize the application for the serverless environment that it will be deployed to.

A number of bundlers and build tools provide out-of-the-box support for compiling TypeScript code:

  • esbuild. Requires minimal configuration. Limited out-of-the box features, but highly optimized for speed.
  • swc (Speedy Web Compiler). Another performance focused tool, built in Rust. Compatible with Babel, making it simpler to migrate existing projects.
  • Vite. Leverages native ES modules for a better development experience. Also includes a development server and support for Hot Module Replacement (HMR). Uses esbuild and Rollup under the hood.
  • Parcel. Zero configuration required. Built-in development server and support for HMR. Uses swc under the hood.

Other tools, such as webpack, Babel, Rollup, Gulp and Grunt, can be configured to compile TypeScript by installing a plugin and adding a little additional configuration.

While all of these tools can compile TypeScript code to JavaScript, they don't provide a runtime for executing the resulting JavaScript. It's also important to note that most bundlers won't type-check your TypeScript code. Because of this, you'll likely want to run tsc --noEmit to check for type errors in your project before running a bundler to build it.

Runtimes

Typically, JavaScript runtimes exist within a web browser or as a standalone server-side runtime. There are no web browsers which currently support running TypeScript code directly, but TypeScript is now supported by several server-side runtimes.

The three most popular open source JavaScript runtimes are Node.js, Deno and Bun. These runtimes are typically used for building server-side applications and command-line tools.

Deno and Bun provide built-in support for TypeScript, allowing you to run TypeScript code without any additional tooling or configuration. Not wanting to miss out on all the fun, Node.js recently added experimental support for running TypeScript too.

Let's dive in and take a look at how we can use TypeScript with these runtimes.

Tip: If you're writing applications which use JSX, the good news is that Deno and Bun both support .jsx and .tsx files out-of-the-box, with no additional configuration.

Deno

Deno treats TypeScript as a first class language, meaning that you can run a TypeScript file without any additional setup:

deno run script.ts

Deno has a built-in TypeScript compiler and automatically applies sensible defaults for the compiler options. If needed, you can explicitly configure the compiler by adding settings into deno.json.

Unlike with a dedicated compiler or a bundler, no JavaScript files are created when you run a TypeScript file with Deno. All the compilation happens under the hood.

By default, Deno doesn't perform type checking when you run a script. This is for performance reasons, as type checking can be slow, particularly on large projects. However, there are a few ways you can type check your TypeScript code with Deno:

# Type check a module before executing it.
deno run --check script.ts

# Type check a module.
deno check script.ts

# Type check all TypeScript files in your application.
deno check src/**/*.ts

When you run your tests with deno test, Deno will automatically type check your code too. Neat!

Bun

Bun's out-of-the-box support for TypeScript works similarly to Deno's, allowing you to directly run TypeScript files without any additional configuration:

bun run script.ts

The Bun runtime does however read your project's tsconfig.json. The Bun documentation recommends a specific set of TSConfig options to ensure you can use all of Bun's features without warnings from the built-in TypeScript compiler.

If you want to compile the TypeScript files in your project to JavaScript that can be run elsewhere, for example in a web browser, Bun provides a bundler. The bundler can be used programmatically or via the Bun CLI:

bun build script.ts --outfile=script.js

Similarly to the bundlers that we looked at earlier, Bun doesn't support type checking, so you'll likely want to use the TypeScript compiler to check for type errors in your project, with tsc --noEmit.

Experimental TypeScript Support in Node.js

While Deno and Bun are known for their out-of-the-box TypeScript support, the lack of TypeScript support in the Node.js runtime has been a growing pain point for developers in recent years. Fortunately, Node.js has recently added experimental support for running TypeScript code, with the addition of two new command-line flags. They allow you to run TypeScript code like this:

node --experimental-strip-types --experimental-transform-types script.ts

Here's what's happening when you pass those command-line flags to the node CLI:

  • --experimental-strip-types (added: Node.js v22.6.0) — Instead of compiling your code from TypeScript to JavaScript, the types and annotations are stripped from the code so that it can be run as JavaScript. This makes it possible for Node.js to run TypeScript files by replacing inline type annotations with whitespace, but doesn't support all TypeScript features or perform type checking.
  • --experimental-transform-types (added: Node.js v22.7.0) — This flag enables TypeScript-only syntax to be transformed into JavaScript code, allowing Node.js to support features such as enums and namespaces.

While this is an exciting step forward for TypeScript support in the Node.js runtime, the Node.js documentation does currently state:

For full support of all of TypeScript's syntax and features, including using any version of TypeScript, use a third-party package.

(Source: Node.js TypeScript documentation)

It's also important to note that Node.js won't read your project's tsconfig.json to apply any TypeScript compiler settings. Despite this limitation, you might find that Node.js' experimental TypeScript support can help reduce the amount of tooling that you need to integrate and configure in your projects.

Code Runners

Code runners are tools that compile TypeScript and then automatically run the resulting JavaScript code. These tools don't typically write JavaScript files to your hard drive, but instead handle everything in memory. Code runners provide a great way of quickly running TypeScript code, typically without the performance overhead of type checking.

ts-node is by far the most popular TypeScript code runner for Node.js. It's a flexible and highly configurable tool, but it doesn't provide the same level of module interoperability as some of the newer TypeScript code runners.

As TypeScript has grown in popularity, there have been a number of new additions to the TypeScript code runner space. Here are a couple of other options you might want to consider instead:

  • tsx. This tool makes the bold claim that it's "the easiest way to run TypeScript in Node.js", but it really does make things quite straightforward, and has some great documentation too. tsx even provides a watch mode, which will re-run your script when you make changes, simplifying testing during development.
  • jiti. Part of the UnJS ecosystem, jiti is another popular TypeScript code runner. While it isn't as fully featured as tsx, it does provide a couple of neat extra features such as opt-in support for JSX and custom resolve aliases.

Tip: Here at Moon Highway, we love using tsx with students in our TypeScript courses. If you'd like help deciding which code runner is best for you, Hiroki Osame , the author of tsx, has created a comprehensive comparison of TypeScript code runners that you might want to take a look at.

Want to start working with TypeScript but struggling to implement it on your team? In our TypeScript for Teams workshop, your whole team can get hands-on experience in a friendly format!