<< Home

Chapter 22: Build and Run Strategies

Once your project structure is in place, you need a reliable way to turn TypeScript into something Node can execute. Some teams prefer a classic compile step, others run code on the fly during development, and larger projects often adopt fast builders that compress the edit to feedback loop. This chapter explores these approaches so you can pick the style that fits your workflow.

Transpile and run: tsc outDir workflow

This workflow compiles TypeScript files into a dedicated output directory and points Node at the generated JavaScript. It keeps the boundary between source and build products clear, which is helpful for libraries and production deployments. You write code in src, then run tsc to emit into dist or another folder of your choice.

Clean separation of source and output

A split between directories avoids confusion when debugging and removes the risk of shipping TypeScript files by mistake. Most teams use a structure similar to the one below, where src holds the TypeScript and dist receives the compiled output.

my-app/
  src/…
  dist/…
  tsconfig.json

Configuring the compiler

Set rootDir and outDir so that TypeScript mirrors your folder structure and emits files consistently. This also helps tools like nodemon or pm2 watch the correct location.

{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "sourceMap": true
  }
}
💡 For projects that produce packages, the outDir workflow is often the simplest and most reliable because consumers only see the compiled output. It also makes npm publish predictable.

On the fly execution with tsx or ts-node

Sometimes you want instant execution without a manual build step. Tools like tsx and ts-node execute TypeScript directly by compiling each file just before running it. This keeps iteration fast and cuts down on the number of moving parts during development.

Using tsx for ESM friendly projects

tsx handles modern ESM resolution rules and supports speedy startup. It respects your tsconfig.json and keeps imports stable, which suits apps that lean on Node’s current resolution model.

npx tsx src/index.ts

ts-node for classic environments

ts-node hooks into Node’s loader chain and works well for CommonJS or mixed projects. While not as fast as dedicated builders, it is simple and dependable for small apps or command line tools.

npx ts-node src/index.ts
⚠️ On the fly execution is convenient, but it can hide performance issues and differences between development and production. Always test the compiled output before release so you avoid surprises.

Fast builders: esbuild and swc

For large codebases, build speed becomes part of developer happiness. Builders like esbuild and swc compile TypeScript extremely quickly and can bundle code when needed. They skip type checking by default, so many teams pair them with tsc --noEmit in continuous integration.

esbuild for ultrafast bundling and transforms

esbuild is designed for speed and can collapse an entire dependency graph into a single output. You can use it to build server code or to prepare a hybrid project that mixes client and server elements.

esbuild src/index.ts --platform=node --format=esm --outfile=dist/index.js

swc for a Rust powered pipeline

swc offers fast transforms and a flexible configuration file. It is a strong choice when you want speed with fine grained control over emitted JavaScript targets.

{
  "jsc": {
    "target": "es2022",
    "parser": { "syntax": "typescript" }
  }
}
💡 Pair a fast builder with tsc --noEmit to keep type checking clean. This gives you speed without sacrificing safety.

Source maps and production debugging

Source maps connect emitted JavaScript back to your TypeScript files. When configured correctly, error stacks point to the original source, which saves time during debugging. You can use inline maps during development and external maps for production deployments.

Compiler options for clean debugging

Enable sourceMap and inlineSources so your editor and Node’s inspector can locate original lines. External maps are often better in production because they reduce payload size while still giving traceability when you capture logs.

{
  "compilerOptions": {
    "sourceMap": true,
    "inlineSources": true
  }
}

Runtime support in Node

Node reads source maps automatically when the loader provides them. For modern setups, no extra flags are needed. If you use older runtime tools or process managers, confirm that source maps remain available in the deployed environment.

⚠️ Never publish source maps that expose private code or environment specific details unless you are confident they will not leak sensitive information. Keep maps behind authenticated endpoints when necessary.