很多初学者会误以为「用了 webpack 就能写 ES6+」,其实这是个误解。webpack 对 JavaScript 的「理解」仅限于模块化语法——它能识别 import / export,从而构建依赖图并把模块打包在一起。但除此之外的新语法,webpack 本身并不会帮你转译。本文讲清这个能力边界,并介绍处理 JS 资源所需的 loader 与 ESLint 等工具。
一、webpack 到底能处理什么
webpack 是一个模块打包工具,不是编译器。它对 JS 的处理可以概括为:
- 能识别模块语法:
import、export、动态import()。 - 不做语法降级:箭头函数、
let/const、模板字符串、解构、可选链?.、空值合并??、async/await等,webpack 原样保留。
也就是说,下面这段代码 webpack 打包后依然保持原样:
const fn = (a, b) => a?.b ?? b;
const res = await fetch('/api');
如果目标浏览器不支持这些语法,页面就会直接报错。webpack 不会替你解决兼容性问题——这正是 babel-loader 等工具存在的意义。
二、babel-loader:转译 JS 语法
Babel 是事实上的 JS 编译器,babel-loader 让 webpack 在打包时调用 Babel。
1. 安装
npm install babel-loader @babel/core @babel/preset-env --save-dev
2. 配置 loader
module.exports = {
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/, // 第三方包通常已编译,无需重复处理
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};
3. preset-env 与 browserslist
@babel/preset-env 会根据目标环境,自动决定要启用哪些语法转换插件。目标环境通过 browserslist 声明,可写在 package.json 或 .browserslistrc:
{
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]
}
这样既能覆盖目标浏览器,又不会把代码转译得过于老旧。
4. 开启 Babel 缓存
Babel 转译较慢,建议开启缓存:
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: ['@babel/preset-env'],
},
}
三、Polyfill:补齐缺失的 API
语法转换解决的是「写法不兼容」,但像 Promise、Array.prototype.includes、Object.assign 这类内置 API,光靠语法转译是补不上的,需要 polyfill。
在 webpack 5 + Babel 7 中,推荐用 core-js:
npm install core-js --save-dev
然后通过 useBuiltIns: 'usage' 按需注入:
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage', // 按实际用到的 API 自动引入
corejs: 3,
},
],
],
},
}
useBuiltIns 有三个常用取值:
false:不自动引入 polyfill(默认)。'entry':在入口import 'core-js',全量引入。'usage':按代码实际使用情况按需引入,产物体积更小。
使用 transform-runtime 避免污染全局
如果开发的是类库,不希望 polyfill 污染使用者的全局环境,可以用 @babel/plugin-transform-runtime:
npm install @babel/plugin-transform-runtime @babel/runtime --save-dev
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-transform-runtime', { corejs: 3 }],
],
},
}
它把 helper 和 polyfill 从内联改为按模块引入,避免重复注入。
四、处理 TypeScript
TS 文件有两种常见处理方式。
方式一:ts-loader(类型检查完整,但较慢)
npm install ts-loader typescript --save-dev
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
}
方式二:babel-loader + @babel/preset-typescript(转译快,但只去类型、不做类型检查)
npm install @babel/preset-typescript --save-dev
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-typescript'],
},
}
若追求类型安全,可再单独跑 tsc --noEmit 做检查,把类型检查与打包解耦。
五、ESLint:约束代码规范
转译解决「能不能运行」,ESLint 解决「写得好不好」。在 webpack 中集成 ESLint 需要 eslint-webpack-plugin(webpack 5 已弃用旧的 eslint-loader)。
1. 安装
npm install eslint eslint-webpack-plugin --save-dev
2. 配置
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
plugins: [
new ESLintPlugin({
extensions: ['js', 'jsx', 'ts', 'tsx'],
exclude: 'node_modules',
fix: true, // 自动修复可修复的问题
failOnError: true,
}),
],
};
配合项目根目录的 eslint.config.js(或旧版 .eslintrc.*)定义规则。这样每次构建都会检查代码,问题会直接在终端和浏览器中提示。
六、其他处理 JS 的工具
- terser-webpack-plugin:生产环境代码压缩。webpack 5 在
mode: 'production'下已内置,一般无需手动配置。 - thread-loader:把耗时的 loader 放到 worker 线程执行,加快大型项目构建。
- swc-loader / esbuild-loader:基于 Rust/Go 的极速转译方案,可作为 babel-loader 的性能替代。
七、完整配置示例
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
clean: true,
},
module: {
rules: [
{
test: /\.m?jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }],
'@babel/preset-react',
],
},
},
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { useBuiltIns: 'usage', corejs: 3 }],
'@babel/preset-typescript',
],
},
},
},
],
},
plugins: [
new ESLintPlugin({
extensions: ['js', 'jsx', 'ts', 'tsx'],
fix: true,
}),
],
};
小结
webpack 只负责「把模块拼起来」,真正让新语法在旧环境跑起来的是 Babel,补齐内置 API 的是 polyfill(core-js),保证代码质量的是 ESLint。三者职责不同、缺一不可:
| 工具 | 解决的问题 |
|---|---|
babel-loader | ES6+ 语法转译为兼容代码 |
core-js + preset-env | 补齐 Promise、includes 等内置 API |
@babel/preset-typescript / ts-loader | 处理 TypeScript |
eslint-webpack-plugin | 代码规范与静态检查 |
terser-webpack-plugin | 生产环境压缩混淆 |
理清这条分工链,就不会再纠结「为什么我用了 webpack 代码还是不兼容」了。