前端-webpack-配置js兼容性

Babel + @babel/preset-env 在 Webpack 中的配置实践

Babel 是 JavaScript 转换平台。@babel/preset-env 根据目标环境智能启用必要插件,完成语法降级与 polyfill 注入。babel-loader 是 Webpack 与 Babel 的桥梁。

本文覆盖职责、核心 API、配置方式、多场景组合、加速替代方案及常见坑点。

一、核心职责

组件职责
Babel解析 JS → AST → 插件转换 → 输出兼容代码
@babel/preset-env根据 targets / browserslist 只启用必要语法转换 + polyfill
babel-loader在 Webpack 打包链路中调用 Babel
core-js提供运行时 API polyfill(Promise、Array.includes 等)

语法转换解决「能不能解析」,polyfill 解决「运行时有没有这个 API」。

二、安装

1
2
3
4
5
6
yarn add -D @babel/core @babel/preset-env babel-loader
yarn add core-js # 运行时 polyfill,生产依赖

# TypeScript 项目额外安装
yarn add -D typescript ts-loader
# 或使用 babel 处理 TS:@babel/preset-typescript

三、@babel/preset-env 核心 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[
'@babel/preset-env',
{
// targets:目标环境。可写对象、字符串、数组
// 优先级高于 package.json / .browserslistrc 的 browserslist
targets: { ie: '11' }, // 或 'defaults', '> 0.5%, not dead'

// useBuiltIns:polyfill 注入策略
// 'usage' 按需注入(推荐,体积最优)
// 'entry' 根据入口 import 'core-js/stable' 展开全量所需
// false 不注入,需自行处理
useBuiltIns: 'usage',

// corejs:配合 useBuiltIns 使用,指定 core-js 版本
// proposals: true 同时注入提案阶段 API
corejs: { version: 3, proposals: true },

// modules:模块转换方式。默认 'auto'
// false 保留 ES Module(配合 Webpack / Rollup 做 tree-shaking)
// 'commonjs' / 'amd' / 'umd' 等
modules: false,

// bugfixes:启用已知 bug 修复转换(Babel 7.9+ 推荐开启)
bugfixes: true,

// shippedProposals:启用已在浏览器落地但尚未进正式标准的特性
shippedProposals: false,

// debug:打印实际启用的插件列表
debug: true,

// exclude / include:强制排除或包含某些插件
exclude: ['transform-typeof-symbol'],
include: ['transform-object-rest-spread'],
},
]

useBuiltIns 三种模式对比

模式行为体积适用场景
usage静态分析,只注入实际用到的 API最优绝大多数项目
entry入口手动 import 'core-js/stable',按 targets 展开较大有动态访问 API 需求
false不注入任何 polyfill最小自行管理 polyfill

usage 局限:无法识别间接访问(如 window['Pro'+'mise']),也无法处理被 exclude 的第三方包内部新 API。

四、基础 Webpack 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启缓存,二次构建加速明显
presets: [
[
'@babel/preset-env',
{
targets: { ie: '11' },
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
modules: false,
bugfixes: true,
},
],
],
},
},
},
],
},
};

关键说明

  1. test / exclude
    test 决定哪些文件进入 loader 链,exclude 优先级更高。
    跳过 node_modules 的原因:

    • 第三方包通常已发布兼容产物,重复转译浪费时间
    • 依赖体量远大于业务代码,是构建耗时主因

    例外:某些只发 ESM + 新语法的现代包在旧浏览器会报错,可单独放行:

    1
    exclude: /node_modules\/(?!(package-name|another-pkg)\/).*/
  2. cacheDirectory
    转译结果缓存到 node_modules/.cache/babel-loader,以文件内容 + Babel 配置 + 版本号为 key。
    开发模式收益显著;CI 需保留该目录才有效。

  3. targets 优先级
    Babel options 内的 targets > package.json / .browserslistrc 的 browserslist。
    建议与 PostCSS、Autoprefixer 共用同一份 browserslist,避免 JS/CSS 目标不一致。

五、四种配置方式(与 PostCSS 类似)

方式一:全部写在 webpack.config.js

适合小项目或临时验证(见上方基础配置)。

方式二:抽出 babel.config.js / .babelrc(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// babel.config.js(项目根目录,支持 monorepo)
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
modules: false,
bugfixes: true,
},
],
],
};
1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
// 不再写 presets,自动读取 babel.config.js
},
},
}

方式三:目标环境抽到 .browserslistrc

1
2
3
4
5
6
7
8
9
10
11
12
13
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
modules: false,
},
],
],
};
1
2
3
4
# .browserslistrc
> 0.5%
not dead
ie >= 11

与 PostCSS、Autoprefixer 完全共用,保证一致性。

方式四:目标环境写在 package.json

1
2
3
4
5
6
{
"browserslist": {
"production": ["> 0.5%", "not dead", "ie >= 11"],
"development": ["last 1 chrome version", "last 1 firefox version"]
}
}

开发环境只编译当前浏览器,构建更快。

六、多场景配置组合

1. 纯 JS(开发 + 生产)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
modules: false,
bugfixes: true,
}],
],
},
},
}

2. TypeScript + Babel(推荐现代方案)

先用 @babel/preset-typescript 去类型,再用 preset-env 降级:

1
yarn add -D @babel/preset-typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// babel.config.js
module.exports = {
presets: [
'@babel/preset-typescript',
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
modules: false,
bugfixes: true,
},
],
],
};
1
2
3
4
5
6
7
8
9
// webpack
{
test: /\.(js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: true },
},
}

3. TypeScript + ts-loader + Babel(传统方案)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [['@babel/preset-env', { /* ... */ }]],
},
},
'ts-loader', // 先类型检查 + 转 JS,再交给 Babel
],
}

4. React 项目

1
yarn add -D @babel/preset-react
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// babel.config.js
module.exports = {
presets: [
'@babel/preset-react', // 处理 JSX
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: 3,
modules: false,
},
],
],
};

5. 同时处理 JS + TS + JSX

1
2
3
4
5
6
7
8
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: { cacheDirectory: true },
},
}

对应 babel.config.js 同时启用 preset-env + preset-typescript + preset-react

6. 强制转译特定 node_modules 包

1
2
3
4
5
6
7
8
9
10
11
{
test: /\.js$/,
exclude: /node_modules\/(?!(swiper|some-esm-pkg)\/).*/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [['@babel/preset-env', { /* ... */ }]],
},
},
}

7. 开发环境精简 targets(加速)

1
2
3
4
5
6
7
// package.json
{
"browserslist": {
"production": ["> 0.5%", "not dead", "not IE 11"],
"development": ["last 1 chrome version"]
}
}

七、提高速度:可替换 babel-loader 的方案

babel-loader 基于 JS 实现,是大型项目构建的主要瓶颈之一。以下两个 loader 可直接替换,通常能带来 5~20 倍 转译加速(实测因项目而异)。

1. swc-loader(推荐首选)

基于 SWC(Rust 编写),与 Babel 生态最接近,支持 TypeScript、JSX、按需 polyfill,迁移成本最低。

1
yarn add -D @swc/core swc-loader

基础替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript', // 或 'ecmascript'
tsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
},
target: 'es5', // 对应 Babel 的 targets
},
env: {
targets: 'ie >= 11', // 或读取 browserslist
mode: 'usage', // 类似 useBuiltIns: 'usage'
coreJs: 3,
},
},
},
}

也可使用 .swcrc 文件(推荐,与 babel.config.js 对应):

1
2
3
4
5
6
7
8
9
10
11
{
"jsc": {
"parser": { "syntax": "typescript", "tsx": true },
"target": "es5"
},
"env": {
"targets": "ie >= 11",
"mode": "usage",
"coreJs": "3"
}
}

优点

  • 速度接近 esbuild,兼容性更好
  • 支持动态 polyfill(mode: 'usage'
  • 可处理装饰器、部分高级语法

注意:插件生态不如 Babel 丰富,复杂 Babel 插件需确认是否有 SWC 等价实现。

2. esbuild-loader

基于 esbuild(Go 编写),转译极快,同时可替换 Terser 做压缩。

1
yarn add -D esbuild-loader

基础替换

1
2
3
4
5
6
7
8
9
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
loader: 'esbuild-loader',
options: {
loader: 'tsx', // 'js' | 'jsx' | 'ts' | 'tsx'
target: 'es2015', // 注意:完整降到 ES5 支持有限
},
}

同时替换压缩(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
const { EsbuildPlugin } = require('esbuild-loader');

module.exports = {
optimization: {
minimizer: [
new EsbuildPlugin({
target: 'es2015',
css: true, // 同时压缩 CSS
}),
],
},
};

优点

  • 转译 + 压缩都极快
  • 配置极简

局限

  • 不支持完整降级到 ES5(官方明确说明)
  • 无类似 useBuiltIns: 'usage' 的动态 polyfill,需自行处理
  • 适合现代浏览器目标(es2015+)的项目

对比与选型

方案语言相对 Babel 速度ES5 支持动态 polyfill迁移难度推荐场景
babel-loaderJS1x完整-强依赖 Babel 插件
swc-loaderRust20~70x较好大多数项目首选
esbuild-loaderGo极快有限现代浏览器目标

进一步加速:若项目可接受迁移 bundler,可考虑 Rspack(Webpack 兼容 API + 内置 builtin:swc-loader),整体构建常能再快 5~10 倍。

八、常见踩坑点

  1. targets 与 browserslist 混用
    Babel options 里的 targets 会覆盖 browserslist。与 PostCSS 共用目标时,建议只维护一份 browserslist,Babel 不写 targets。

  2. useBuiltIns: ‘usage’ 未安装 core-js
    必须安装 core-js,否则运行时报模块找不到。

  3. polyfill 重复注入
    若入口又手动 import 'core-js',再开 usage 会导致重复。二选一即可。

  4. node_modules 被跳过导致第三方新语法报错
    用正则白名单放行,或升级该依赖到兼容版本。

  5. 缓存未失效
    修改 browserslist 或 Babel 配置后,建议清理 node_modules/.cache 再构建。

  6. modules: ‘auto’ 在 Webpack 中的影响
    默认会转成 CommonJS,破坏 tree-shaking。生产项目建议显式 modules: false

  7. async/await 在 IE11
    需要 regenerator-runtime。使用 useBuiltIns: 'usage' + core-js 3 时会自动处理;否则需手动引入。

  8. 替换为 swc / esbuild 后兼容性变化
    务必在目标浏览器(尤其 IE11)做完整回归测试,部分边缘语法转换结果可能与 Babel 略有差异。

九、完整推荐配置(生产项目)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// babel.config.js(或改用 .swcrc + swc-loader)
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
modules: false,
bugfixes: true,
},
],
],
};
1
2
3
4
5
6
# .browserslistrc(与 PostCSS 共用)
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 11
1
2
3
4
5
6
7
8
9
10
11
// webpack.config.js(部分)
{
test: /\.(js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader', // 或 'swc-loader' 追求速度
options: {
cacheDirectory: true,
},
},
}

按上述方式配置后,JS 与 CSS 目标浏览器保持一致,构建结果可预期,体积与兼容性达到较好平衡。需要更高速度时,优先替换为 swc-loader