需同时使用 ts-loader 和 babel-loader:ts-loader 负责 TS 类型检查与语法转 ESNext,babel-loader 负责新语法降级、API polyfill 注入及接入 Babel 生态,二者顺序不可颠倒(先 ts-loader 后 babel-loader)。

直接用 ts-loader 就能完成 TypeScript 编译,但加上 babel-loader 是为了补足两件事:一是对新语法(比如可选链、空值合并)做更细粒度的降级和 polyfill 注入;二是统一接入 Babel 生态(如按需引入 core-js、兼容老浏览器、集成自定义插件)。
为什么需要两个 loader 一起用
ts-loader 负责类型检查 + TS 语法转 ESNext,但它不处理运行时缺失的 API(如 Array.prototype.flat 或 Promise.allSettled),也不自动注入 polyfill。而 babel-loader 在后续阶段能基于目标环境(通过 @babel/preset-env 和 browserslist)决定哪些语法要转、哪些 API 要垫片,还能配合 useBuiltIns: "usage" 实现按需引入。
- ts-loader 输出的是标准 ESNext 代码(含箭头函数、async/await、装饰器等)
- babel-loader 接收这段代码,再根据 preset-env 配置降级语法 + 注入必要 polyfill
- 两者串联顺序不能颠倒:必须先 ts-loader → 再 babel-loader(Webpack rules 中从右往左执行)
webpack.config.js 中的 loader 配置写法
在 module.rules 里声明 TypeScript 文件的处理链,注意 use 数组顺序:
- 数组末尾的 loader 先执行 → 所以
ts-loader放后面,babel-loader放前面 - 推荐写成对象形式,方便传选项或控制缓存
module: {
rules: [{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: { cacheDirectory: true }
},
{
loader: 'ts-loader',
options: { transpileOnly: true } // 关闭类型检查提速,交给 fork-ts-checker-webpack-plugin
}
],
exclude: /node_modules/
}]
}
配套配置不能少
光配 loader 不够,几个关键文件要同步到位:
-
tsconfig.json:设"target": "esnext",让 ts-loader 输出最新语法,留给 babel 处理降级 -
.babelrc或babel.config.js:必须包含@babel/preset-env,并启用useBuiltIns: "usage"和指定corejs: 3 -
package.json中的browserslist字段:决定 preset-env 的编译目标(例如"> 1%, last 2 versions, not dead")
常见坑与优化点
实际跑起来容易卡在几个地方:
- 如果用了
import "@babel/polyfill"或core-js/stable,要确保只引入一次,避免重复垫片 -
transpileOnly: true可显著提升构建速度,但需额外加fork-ts-checker-webpack-plugin做类型检查分离 - 避免同时用
@babel/preset-typescript和ts-loader—— 二者都做 TS 语法剥离,会冗余甚至冲突 - 若项目纯 TS 无 JSX,可删掉
@babel/preset-react;有 JSX 则保留并配好jsx: "react-jsx"


















