Webpack 默认处理 ES2015 模块并将其转换为代码。但它不会转换特定语法,例如 const
。生成的代码可能会出现问题,尤其是在旧版浏览器中。
如果想要了解默认转换,请观察下面例子的输出(npm run build -- --devtool false --mode development
):
dist/main.js
...
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = ((text = "Hello world") => {
const element = document.createElement("div");
element.className = "pure-button";
element.innerHTML = text;
return element;
});
...
通过 Babel 编译代码可以解决这个问题,Babel 是一个支持 ES2015 + 语法的著名 JavaScript 编译器。类似于ESLint,它建立在预设和插件之上。预设是插件的集合,您也可以定义自己的插件。
鉴于扩展现有预设时的局限性,modify-babel-preset 允许您基于一个基本预设来进行灵活的扩展。
配置 Webpack 使用 Babel
尽管 Babel 可以单独使用,但您也可以将它与 Webpack 连接起来。在开发过程中,如果您使用的浏览器支持的语言特性,则跳过处理。
如果您不依赖任何自定义语言特性并使用现代浏览器工作,则跳过处理是一个不错的选择。但是,在编译生产代码时,通过 Babel 处理几乎是必需的。
你可以通过 babel-loader 在 Webpack 中使用 babel。它可以获取项目级别的 Babel 配置,或者您可以在 webpack loader 本身进行配置。babel-webpack-plugin 是另一个鲜为人知的选择。
您可以使用 babel 来编译项目的 Webpack 配置。要实现此目的,请使用 webpack.config.babel.js 来命名 Webpack 配置。interpret 包使用了这种方式,它也支持其他编译器。
鉴于 Node 现在支持 ES2015 规范,您可以使用许多 ES2015 语法特性,而无需通过 Babel 处理配置。
如果您使用 webpack.config.babel.js,请注意设置
"modules": false
。如果要使用 ES2015 模块语法,您可以跳过 Babel 全局配置中的设置,然后按照下面的讨论为每个环境进行单独配置。
配置 babel-loader
配置 Babel 的第一步是设置 babel-loader,它将现有的代码转换为旧浏览器可以理解的格式。首先,我们安装 babel-loader 及其依赖项 @ babel/core:
npm install babel-loader @babel/core --save-dev
像往常一样,让我们为 Babel 定义一个配置函数:
webpack.parts.js
exports.loadJavaScript = ({ include, exclude } = {}) => ({
module: {
rules: [
{
test: /\.js$/,
include,
exclude,
use: "babel-loader",
},
],
},
});
接下来,您需要将其合并到主配置。如果您使用现代浏览器进行开发,则可以考虑仅仅通过 Babel 编译生产环境的代码。下面的配置中,它用于生产和开发环境。并且,只有应用程序代码通过 Babel 处理。
调整如下:
webpack.config.js
const commonConfig = merge([
...
parts.loadJavaScript({ include: PATHS.app }),
]);
即使您安装了 Babel 并进行了 Webpack 配置,您仍然缺少一点:Babel 配置。可以使用 .babelrc 设置 Babel 配置,因为其他工具可以使用这个文件。
如果您尝试导入配置根目录之外的文件,然后通过 babel-loader 编译它们,则会失败。这是一个已知的问题,有一些解决方法,包括在项目的目录的更外层维护_.babelrc_,并通过配置 Webpack
require.resolve
来解析 Babel 的预设。
设置 .babelrc
你需要 @babel/preset-env,它是一个 Babel 预设,可根据您传递给它的选项来确定要启用的插件。
首先,安装这个包:
npm install @babel/preset-env --save-dev
要让 Babel 找到这个预设,你需要写一个 .babelrc 。鉴于 Webpack 支持开箱即用的 ES2015 模块,您可以告诉 Babel 跳过处理它们。跳过这一步将破坏Webpack 的 HMR 机制,尽管生产构建仍然有效。您还可以将构建的代码限制为仅在最新版本的 Chrome 中有效。
你可以按照 browserslist 来调整配置,以期达到目标浏览器的兼容性要求,下面是一个配置的例子:
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
}
]
]
}
如果你运行 npm run build -- --devtool false --mode development
,然后检查 dist/main.js,你会看到一些不一样的东西,它们是根据 .browserslistrc
生成的。
尝试只包含 IE 8
,代码应该相应地改变:
dist/main.js
...
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (function () {
var text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Hello world";
var element = document.createElement("div");
element.className = "pure-button";
element.innerHTML = text;
return element;
});
...
特别注意函数是如何转换的。您可以尝试启用不同的浏览器和语言特性,以查看输出是如何相应第改变的。
Polyfilling 功能
@ babel/preset-env 允许您为旧版浏览器支持某些语言特性。为此,您应该启用其 useBuiltIns
选项(设置 "useBuiltIns": true
或 "useBuiltIns": "usage"
)并安装 @babel/polyfill。您必须通过 import 或 entry(app: ["@babel/polyfill", PATHS.app]
)将其包含在项目中。@babel/preset-env 根据您选定的浏览器重写导入,并仅加载所需要的 polyfill。
@babel/polyfill 会在全局范围内提供了像 Promise
、Set
这样的对象,这会污染全局作用域,对于一些库的开发者来说这可能会造成影响,这时候可以使用 @babel/plugin-transform-runtime 。它可以作为 Babel 插件启用,避免了全局变量污染的问题。
某些 Webpack 功能,例如 代码拆分,可以在 loader 运行之后,在 Webpack 启动代码部分写入
Promise
,在执行应用程序代码之前运行垫片程序,从而解决这个问题。示例:entry: { app: ["core-js/es/promise", PATHS.app] }
。
Babel 小技巧
除了 .babelrc 中介绍的技巧以外,还有很多其他的功能,类似 ESLint,.babelrc 支持 JSON5 作为其配置格式,这意味着您可以在 JSON 配置中包含注释,使用单引号字符串等。
有时您希望在项目中使用一些实验性功能,虽然你可以在一些所谓的 stage 预设中找到它们,但最好逐个启用,甚至将它们组织成自己的预设,除非你正在进行一次性项目。如果您希望项目能够存活很长时间,那么最好记录您正在使用的功能。
Babel 并不是唯一的选择,尽管它是最受欢迎的选择。Rich Harris 的 Buble 是另一个值得一试的编译器。有一个实验性质的 buble-loader,允许你在 Webpack 中使用它。Buble 不支持 ES2015 模块,但这不是问题,因为Webpack 提供了这种功能。
Babel 插件
Babel 最棒的事情可能就是可以使用插件对其进行扩展:
- babel-plugin-import 重写模块导入,以便您可以使用这样的形式(
import { Button } from "antd";
)来导入模块,而不必指出精确的路径。 - babel-plugin-import-asserts 用来断言导入的定义。
- babel-plugin-jsdoc-to-assert 将 JSDoc 注释转换为可运行的断言。
- babel-plugin-log-deprecated 如果函数注释中包含
@deprecate
,就在函数中注入console.warn
。 - babel-plugin-annotate-console-log 在使用
console.log
时,该插件会将有关调用上下文的信息一起打印,因此更容易看到打印时的位置。 - babel-plugin-sitrep 记录函数中的所有赋值操作并打印它们。
- babel-plugin-webpack-loaders 允许您通过 Babel 使用某些 Webpack loader。
- babel-plugin-syntax-trailing-function-commas 为函数参数添加尾逗号语法支持。
- babel-plugin-transform-react-remove-prop-types 允许您在生产环境中将
propType
相关的代码删除。
可以通过 babel-register 或 babel-cli 将 Babel 与 Node 连接起来。如果您想在不使用 Webpack 的情况下通过 Babel 编译代码,这些包会很方便。
在每个环境中启用预设和插件
Babel 允许您通过其 env 选项 控制每个环境使用哪些预设和插件。您可以通过这种方式管理不同构建目标时 Babel 的行为。
env
检查 NODE_ENV
和 BABEL_ENV
,并基于此来执行构建。如果设置了BABEL_ENV
,它将覆盖 NODE_ENV
。
考虑下面的例子:
.babelrc
{
...
"env": {
"development": {
"plugins": [
"annotate-console-log"
]
}
}
}
我们在开发环境中启用了 annotate-console-log
插件,env
允许您精细化地配置 Babel。
我们还可以将 Webpack 环境参数传递给 Babel:
webpack.config.js
module.exports = mode => {
process.env.BABEL_ENV = mode;
...
};
env
的工作方式很微妙,设置好env
并确保它与您的 Babel 配置匹配,否则 Babel 可能不会按照你的期望进行打包。
设置 TypeScript
Microsoft 的 TypeScript 是一种需要编译的语言,遵循与 Babel 类似的设置。与 JavaScript 不同的是,它具备强类型。这样,一个好的 IDE 可以更好的提示,提高编码体验。强类型对于开发来说是很有意义的,因为它比较清晰地约束了变量的类型。
与 Facebook 的类型检查器 Flow 相比,TypeScript 是一种更安全的选择。因为,它的预定义类型比较多,总体上的维护质量也更好。
您可以使用以下 loader 将T ypeScript 与 Webpack 一起使用:
ESLint 有一个 TypeScript 解析器。你也可以通过 tslint 来 lint ts 代码。
设置 Flow
Flow 根据您的代码及其类型声明执行静态分析。您必须将其作为单独的工具安装,然后把它应用在你的代码上。有一个 flow-status-webpack-plugin,允许你在开发过程中通过 Webpack 运行它。
如果您使用 React,则 React 特定的 Babel 预设 babel-plugin-syntax-flow 可以完成大部分工作。它可以删除代码中的 Flow 声明并将您的代码转换为可以进一步转换的格式。
还有 babel-plugin-typecheck,允许您根据 Flow 声明执行运行时检查。flow-runtime 进一步发展并提供更多的功能。这些方法补充了 Flow 静态检查程序,使您可以捕获更多问题。
flow-coverage-report 显示了你的代码中 Flow 声明的覆盖情况。
总结
Babel 已经成为开发人员不可或缺的工具,因为它使旧版浏览器也能使用较新的 JavaScript 语法。即使您针对的是现代浏览器,通过 Babel 进行转换也是一种不错的选择。
回顾一下:
- Babel 让您可以控制要支持的浏览器。它可以将 ES2015+ 语法特性编译为旧浏览器所理解的形式。babel-preset-env 很有价值,因为它可以根据您的浏览器定义选择要编译的语法以及要启用的 polyfill。
- Babel 允许您使用实验语法特性。您可以找到许多插件,通过优化改善开发体验和生产构建。
- 可以为每个环境启用 Babel 功能。这样您就可以确保在正确的位置使用正确的插件。
- 除了 Babel 之外,Webpack 还支持 TypeScript 或 Flow 等其他解决方案。Flow 可以补充 Babel,而 TypeScript 可以整体上编译为 JavaScript。