34.6 Webpack with ts-loader or babel-loader
Alright, let’s get our hands dirty with Webpack. I know, I know, it’s not the cool new kid on the block anymore, but it’s the battle-tested veteran that still powers a massive chunk of the web. And guess what? You’ll probably have to deal with it in a legacy project, or in a team that values its sheer configurability. The good news is, getting it to play nice with TypeScript is straightforward, albeit with a few “why is it like this?” moments.
The core question here isn’t if you can use Webpack with TypeScript, but how. You’ve got two main contenders: ts-loader and babel-loader. Your choice isn’t about which one is “better” in a universal sense, but which one is better for your specific situation. Let’s break them down.
ts-loader: The TypeScript Purist’s Choice
ts-loader is exactly what it sounds like: a Webpack loader that directly calls the TypeScript compiler (tsc) under the hood. You give it .ts files, it uses your tsconfig.json, and it spits out JavaScript. It’s the most straightforward path if you want Webpack to replicate exactly what the tsc command would do.
Here’s a minimal, brutally effective webpack.config.js for it:
const path = require('path');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
The magic is in the rules array. This tells Webpack: “Hey, for every file that ends in .ts or .tsx (but please, not the ones in node_modules), hand it off to the ts-loader to deal with.”
Why you might love it: It enforces type checking during the build process. If you have a type error, your build fails. Full stop. This is fantastic for catching mistakes immediately. It also respects all the esoteric options in your tsconfig.json without any additional fuss.
The catch: It can be slower. Because it’s doing full type checking and compilation in one go, it has to do more work. For a large project, this can start to hurt. Also, if you’re already using Babel for other fancy transformations (like cutting-edge JS features that TypeScript hasn’t implemented yet), you’re now running two compilers, which is… a choice.
babel-loader with @babel/preset-typescript: The Speed Demon’s Choice
This is where things get interesting. babel-loader is Webpack’s loader for, you guessed it, Babel. And Babel has a preset called @babel/preset-typescript. Its job is stunningly simple: it strips the TypeScript types out of your code and leaves behind plain JavaScript. That’s it. It does not type check. It just transplies.
Your webpack config looks almost identical, just with a different loader:
module.exports = {
// ... other config same as above
module: {
rules: [
{
test: /\.tsx?$/,
use: 'babel-loader', // Use babel-loader instead
exclude: /node_modules/,
},
],
},
};
And your .babelrc would need the preset:
{
"presets": [
"@babel/preset-typescript",
"@babel/preset-env" // You'll almost always want this too for targeting JS versions
]
}
Why this is brilliant: It’s incredibly fast. Babel is a beast at just transpiling, and by offloading type checking to a separate process (or your IDE), you get rapid feedback loops during development. It also lets you unify your entire JavaScript/TypeScript transformation pipeline under one tool. Want to use JSX, experimental syntax, and TypeScript? Babel handles it all seamlessly.
The massive, glaring, “you will get fired if you miss this” caveat: BABEL DOES NOT TYPE CHECK. Your build will succeed even if your types are a catastrophic mess. You must run tsc --noEmit in your CI pipeline or as a pre-commit hook. It is not optional; it is a critical part of your workflow. If you forget this, you’ve essentially turned TypeScript into very fancy comments.
So, Which One Should You Use?
Here’s my direct, “been there” advice:
- Choose
ts-loaderif you’re on a smaller project, you want the simplest possible setup, or you really value having your build fail instantly on type errors. It’s the “set it and forget it” option. - Choose
babel-loaderif you’re on a large project where build speed is a concern, you’re already using Babel for other reasons, or you need to use Babel-specific plugins that TypeScript doesn’t support.
Personally, I lean towards the babel-loader approach for anything non-trivial. The speed boost is real, and I’m already running type checks in my IDE and CI, so the safety net is there. It feels less like the tool is holding my hand and more like I’m using a precision instrument. Just make sure that safety net is damn well secured.