Skip to Content
Course content

214: Babel vs tsc for Transpilation

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

I remember the first time I moved a project from a simple tsc build to a Babel-based pipeline. Everything felt faster—the builds were nearly instant, and the integration with Webpack was seamless. But then, I pushed a change to production and the site immediately crashed with a ReferenceError. I was staring at a piece of code that my IDE told me was perfectly valid, yet the browser was acting like the variable didn't even exist.

// config.ts
export const enum AppTheme {
  Light = 'LIGHT',
  Dark = 'DARK',
}

// theme-manager.ts
import { AppTheme } from './config';

export function setTheme(theme: AppTheme) {
  console.log(`Setting theme to ${theme}`);
}

setTheme(AppTheme.Dark);

The Runtime Crash Caused by Type Erasure

If you run this through tsc, it works perfectly. The TypeScript compiler sees the const enum and "inlines" the values. Instead of looking up AppTheme.Dark at runtime, tsc replaces that code with the string 'DARK'. The enum itself disappears from the final JavaScript, and the code is highly optimized.

But when I ran this through Babel with @babel/preset-typescript, the output looked something like this:

// transpiled-theme-manager.js
import { AppTheme } from './config';

export function setTheme(theme) {
  console.log(`Setting theme to ${theme}`);
}

setTheme(AppTheme.Dark);

The problem is that Babel doesn't actually "compile" TypeScript in the way tsc does. Babel performs type erasure. It simply looks for things that look like TypeScript types (like : AppTheme) and deletes them. It doesn't perform a type-check, and it doesn't understand the semantic meaning of your code. When it encountered const enum, it stripped the const keyword (or the whole definition depending on the config), but it didn't inline the values because it has no idea what the values actually are—it doesn't look at other files to figure that out.

Since const enums aren't meant to exist as real objects at runtime, the resulting JavaScript tried to access a property on an object that wasn't there, leading to a crash.

Choosing Between Type Erasure and Full Compilation

This brings us to the core tension between Babel and tsc. You have to decide if you want a tool that strips types or a tool that understands types.

  • Babel is an isolated transpiler. It treats every file as a standalone unit. This is why it's so fast. It doesn't care if your types are wrong; it just removes them and gives you JavaScript.
  • tsc is a full-blown compiler. It analyzes the entire project graph to ensure type safety and can perform optimizations like inlining const enums or handling namespaces.

If you're using Babel, you simply cannot use certain TypeScript features that require "cross-file knowledge," such as const enum or namespace. If you try, you'll end up with the same ReferenceError I did.

The Hybrid Pipeline Fix

You don't have to give up Babel's speed to get TypeScript's safety. The industry standard is a hybrid approach: use Babel for the actual transpilation (converting TS to JS) and use tsc strictly for type-checking.

To fix the bug in the example above, I had two choices. First, I could change the const enum to a regular enum. Regular enums are transpiled into real JavaScript objects, which Babel can handle because they exist at runtime.

// Change 'const enum' to 'enum'
export enum AppTheme {
  Light = 'LIGHT',
  Dark = 'DARK',
}

Second, I updated my CI/CD pipeline. Since Babel doesn't tell you when you've made a type error (it just deletes the evidence!), I added a step to run tsc with the --noEmit flag. This tells TypeScript: "Check all my types and tell me if I broke anything, but don't actually generate any JavaScript files."

Now, my workflow looks like this:
1. Development: Babel transpiles code instantly for a fast feedback loop.
2. Verification: tsc --noEmit runs in the background or in CI to catch type mismatches.
3. Production: Babel handles the final build, and I avoid using const enums or namespaces.




📋 Practical Task

Fixing the Babel Runtime ReferenceError

You have a small project configured to use Babel for transpilation. The project currently crashes in the browser with ReferenceError: UserRole is not defined.

The Code:

// roles.ts
export const enum UserRole {
  Admin = 1,
  Editor = 2,
  Viewer = 3,
}

// auth.ts
import { UserRole } from './roles';

export function checkAccess(role: UserRole) {
  if (role === UserRole.Admin) {
    return "Access Granted";
  }
  return "Access Denied";
}

console.log(checkAccess(UserRole.Admin));

Your Task:

  1. Identify why Babel is failing to handle the UserRole definition.
  2. Modify roles.ts so that the code works with Babel's type-erasure approach without changing the logic in auth.ts.
  3. Explain in a brief comment why this change prevents the ReferenceError.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.