Skip to Content
Course content

57: Type-Checking Only Builds

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was lead on a project using a modern build tool that handled the TypeScript-to-JavaScript conversion incredibly fast. We were thrilled with the developer experience—saves were nearly instantaneous. One afternoon, a junior dev pushed a change that accidentally changed a User object's id from a string to a number in one specific utility file. The build pipeline finished in seconds, the tests passed (because they were mocking the data), and the code hit production. Within ten minutes, the dashboard started crashing for every user because the frontend was trying to call .toLowerCase() on a number. The terrifying part? The code had blatant TypeScript errors that would have caught this, but because our build tool just "stripped" the types to get to JS faster, it never actually checked if the types were correct.

The Speed Trap of Type Stripping

If you're using tools like Vite, esbuild, or SWC, you've probably noticed that your project builds almost instantly. This is because these tools perform "transpilation only." They don't actually look at your types; they just delete them and hand the resulting JavaScript to the browser. While this is great for productivity, it creates a dangerous blind spot: your code can be completely broken from a type perspective, but it will still "build" and run.

To solve this, we use what I call a "Type-Checking Only Build." This is where we tell the official TypeScript compiler (tsc) to analyze the entire project for errors, but specifically tell it not to generate any JavaScript files. We use the --noEmit flag for this. It's the equivalent of asking TypeScript, "Don't give me any files, just tell me if I've messed anything up."

# Run this in your terminal to check for errors without creating .js files
npx tsc --noEmit

Integrating Safety into your Workflow

Running a manual command every few minutes is a chore you won't stick to. I usually recommend adding this to your package.json scripts so it becomes a first-class citizen of your development cycle. I personally like to call this script type-check.

{
  "scripts": {
    "dev": "vite",
    "build": "tsc --noEmit && vite build",
    "type-check": "tsc --noEmit"
  }
}

Notice how I've updated the build script above. By chaining tsc --noEmit before the actual build command with &&, the process will hard-fail if there's a single type error. This ensures that no code ever reaches your staging or production environment unless it is type-safe. It turns your type system from a "helpful suggestion" into a "strict requirement."

One thing to keep in mind: tsc --noEmit can be slower than your Vite build because it's doing the heavy lifting of analyzing the entire dependency graph. Don't let that discourage you. I'd rather wait an extra thirty seconds in CI than spend three hours on a Saturday morning fixing a production outage caused by a null pointer that TypeScript saw coming a mile away.




📋 Practical Task

Exercise: Hardening the Order-Processing Pipeline

You have been handed a project that uses a fast-transpiler build. The project currently "builds" successfully, but it contains a critical type mismatch in the order processing logic that would cause a runtime crash.

  • Open the project's package.json and create a type-check script that runs the TypeScript compiler without emitting files.
  • Run your new script. You will notice that while the project "runs," tsc reports an error in src/orders.ts where a Price (number) is being treated as a CurrencyString (string).
  • Fix the type error in src/orders.ts so that the logic is sound.
  • Run the type-check script again to verify that the build now passes the type-checking phase.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.