TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
214: Babel vs tsc for Transpilation
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 enumsor handlingnamespaces.
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:
- Identify why Babel is failing to handle the
UserRoledefinition. - Modify
roles.tsso that the code works with Babel's type-erasure approach without changing the logic inauth.ts. - Explain in a brief comment why this change prevents the
ReferenceError.
There are no comments for now.