@babel/preset-typescript配置避坑指南与实战优化经验分享
先看效果,再看代码
我上周重装了一个老项目的构建流程,TypeScript + Babel 走起。本来以为就是 npm install @babel/preset-typescript -D,然后 babel.config.json 里加个 preset 就完事——结果跑起来直接报错:Cannot use import statement outside a module。
折腾了快一小时才发现:不是 preset-typescript 没生效,而是我忘了它 只负责类型擦除,不处理模块语法。它不会把 import 编译成 require,也不会把 export 变成 module.exports。它干的活就一件事:把 const a: string = 'x' 变成 const a = 'x',仅此而已。
所以,如果你只配了 @babel/preset-typescript,又没配 @babel/preset-env,那恭喜你,TS 类型是没了,但 ES 模块还在原地发呆。
最稳的配置:三件套起步
我现在所有新项目都直接上这个组合(亲测有效):
@babel/preset-typescript(擦类型)@babel/preset-env(转语法+模块)@babel/plugin-transform-runtime(避免重复注入 helper,尤其适合多包场景)
babel.config.json 长这样(注意顺序!preset-typescript 必须在 preset-env 前面):
{
"presets": [
"@babel/preset-typescript",
[
"@babel/preset-env",
{
"targets": { "node": "current" },
"modules": "commonjs"
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
这里重点说一句:modules: “commonjs” 这项千万别漏。默认值是 auto,Babel 会根据你用的打包器(比如 webpack 或 esbuild)自动选模块格式——但 CLI 直接跑 babel 命令时,auto 会 fallback 成 esm,导致生成的 JS 还带 import/export,Node 直接报错。改成 commonjs 就稳了。
别碰 tsconfig.json 的 “noEmit”: true
很多人图省事,在 tsconfig.json 里设 "noEmit": true,想着“反正 Babel 来编译”,结果发现 Babel 报错找不到类型定义、interface 引用失败、泛型推导全乱……
原因很简单:@babel/preset-typescript 是纯语法擦除,它 不读取 tsconfig.json,不做类型检查,也不解析路径别名(paths)、不处理 declaration 文件。它连 node_modules/@types/xxx 都不看一眼。
所以我的建议是:tsconfig.json 必须保留 “noEmit”: false(默认值),且必须有 “outDir”,哪怕你最终不用它的输出文件。为啥?因为 TypeScript 编译器要靠它做语义分析——Babel 不需要类型信息,但你的 IDE、VS Code 跳转、接口提示、tsc --noEmit 校验,全都依赖这个配置。
我踩过坑:某次删了 outDir,VS Code 突然无法识别 import type 的类型,重启 TS Server 都没用,最后发现是 tsconfig 缺少输出路径导致语义解析中断。改回来立马恢复。
高级技巧:只让 Babel 处理 .ts 文件,跳过 .d.ts
默认情况下,Babel 会尝试处理所有匹配的文件,包括 .d.ts。虽然它通常能跳过(因为没可转换的 JS 语法),但一旦你用了自定义插件或者某些 loader,就可能出问题。
稳妥做法是在 babel.config.json 里显式 exclude:
{
"test": /.(ts|tsx)$/,
"exclude": /node_modules|.d.ts$/,
"presets": ["@babel/preset-typescript"]
}
或者更细一点,在 overrides 里单独配 TS 规则:
{
"overrides": [
{
"test": "**/*.ts",
"exclude": "**/*.d.ts",
"presets": ["@babel/preset-typescript"]
}
]
}
关于 “jsx”: “preserve” 和 React 用户
如果你用 React + TSX,记得 tsconfig.json 里必须设 "jsx": "preserve"。否则 TypeScript 会自己把 JSX 编译成 React.createElement,而 Babel 再来一遍,导致重复调用或编译冲突。
同时,Babel 侧要配 @babel/preset-react,并且确保它在 typescript preset 后面(顺序很重要):
{
"presets": [
"@babel/preset-typescript",
"@babel/preset-react",
["@babel/preset-env", { "modules": "commonjs" }]
]
}
另外提醒一句:Babel 对 jsxImportSource(比如 Preact)的支持是靠 @babel/plugin-transform-react-jsx 插件控制的,不是 preset 自带的,别指望 preset-react 默认支持。
踩坑提醒:这三点一定注意
- 不要混用 tsc –watch 和 babel –watch:两个进程都在监听 .ts 文件,经常出现文件写入竞争,Babel 编译一半,tsc 已经删了 .js,结果产出空文件。我后来统一用
npm run build先 tsc 生成声明文件 + 类型校验,再用 Babel 编译源码,分两步走,稳定得多。 - alias 路径(paths)Babel 一概不管:比如你在 tsconfig 里写了
"@/*": ["src/*"],Babel 不会自动 resolve。要用@babel/plugin-module-resolver手动配,或者——更推荐——用babel-plugin-import+resolve.alias(webpack)或esbuild.onResolve(esbuild)来解决,Babel 层就别硬扛了。 - 装饰器(@decorator)默认不支持:TS 的
experimentalDecorators是 Babel 完全不管的领域。要用就得额外加@babel/plugin-proposal-decorators,而且得注意 stage(目前建议{"version": "2023-11"})。不过说实话,现在真用装饰器的项目越来越少了,我最近三个项目都没开这玩意儿。
小结:它不是万能的,但很趁手
@babel/preset-typescript 就是个“干净利落的剃刀”——只削类型,不碰逻辑,不查错误,不 resolve 路径,不生成 d.ts。它存在的意义,就是让你在已有 Babel 流程里,顺滑接入 TS,而不是另起一套 tsc 构建。
如果你项目已经重度依赖 Babel(比如用了 styled-components 的 babel 插件、emotion、swc 插件兼容层),那它比 tsc 更轻、更快、更可控;但如果你要生成类型声明、要做严格的 monorepo 类型约束、要跑 isolatedModules,那就老老实实用 tsc ——Babel 不是替代品,是协作搭档。
以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

暂无评论