目前部分浏览器和 Node.Js 已经支持 ES6,但是对 ES6 所有的标准支持不全,这导致在开发中不敢全面地使用 ES6。
通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码。
- 把新的 ES6 语法用 ES5 实现,例如 ES6 的
class
语法用 ES5 的prototype
实现。 - 给新的 API 注入
polyfill
,例如项目使用 fetch API
时,只有注入对应的 polyfill,才能在低版本使用。
Babel
Babel 可以方便的完成上面的两件事。Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展。在 Babel 执行编译的过程中,会从项目根目录下的 .babelrc
文件读取配置。.babelrc 是一个 JSON 格式的文件。
Plugins
plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。
例如: 配置文件里的 transform-runtime
对应的插件全名叫做 babel-plugin-transform-runtime
。在文件里可以忽略前缀 babel-plugin-
,安装需要全名。
Presets
presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets
可以叠加。 Presets 其实是一组 Plugins
的集合,每一个 Plugin 完成一个新语法的转换工作。Presets 是按照 ECMAScript 草案来组织的,通常可以分为以下三大类:
- 已经被写入 ECMAScript 标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分为:全拼 babel-preset-
a. es2015 包含在 2015 里加入的新特性。
b. es2016 包含在 2016 里加入的新特性。
c. es2017 包含在 2017 里加入的新特性。
d. env 包含当前所有 ECMAScript 标准里的最新特性。
{
"plugins": [
"check-es2015-constants",
"es2015-arrow-functions",
"es2015-block-scoped-functions"
]
}
为了方便,将同属ES2015的几十个Transform Plugins集合到babel-preset-es2015一个Preset中。
{
"presets": [
"es2015"
]
}
随着时间的推移,可能出现好多预设版本,然后就出现了 preset-env , 包含当前所有 ECMAScript 标准里的最新特性。
{
"presets": [
"env"
]
}
- 被社区提出来但还未被写入 ECMAScript 标准里的特性。
a. stage0 只是一个想法,有 Babel 插件实现了对这些特性的支持,但是不确定是否会被定为标准;
b. stage1 值得被纳入标准的特性;
c. stage2 该特性规范已经被起草,将会被纳入标准里;
d. stage3 该特性规范已经定稿,各大浏览器厂商和 Node.js 社区开始着手实现;
d. stage4 在接下来的一年将会加入到标准里去。 - 为了支持一些特定应用场景下的语法,和 ECMAScript 标准没有关系,例如 babel-preset-react 是为了支持 React 开发中的 JSX 语法。
Presets的其他配置
{
"presets": [
[
"env",
{
// 项目所支持的浏览器的配置
"target": {
"browsers": [
// 支持每一个浏览器最后两个版本
"last 2 versions",
// 大于等于7版本的 safari
"safari >= 7",
// 支持市场份额超过 5% 的浏览器
"> 5%"
],
// 也能够指定浏览器的指定版本
"chrome": 56,
// 如是经过 Babel 编译 Node.Js 代码的话,支持的是当前运行版本的nodejs
"node": "current"
},
// 启用 ES6 模块语法转换为另一种模块类型,false 不会进行转换
// 默认为 'commonjs', 'amd' | 'umd' | 'systemjs' | 'commonjs' | false
// 之前我们通过 babel 来将 ES6 模块语法转换为 AMD, CommonJS,UMD
// webpack都帮我作了这件事了,因此咱们不须要babel来作
// 不然可能会产生冲突
"modules": false
}
]
]
}
webpack 配置
module.exports = {
module: {
rules: [
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
]
}
}
babel-loader 不是用来 ES6 转译的,它充其量只是一个打通 babel
与 webpack
的一个 桥梁
,首先两者得建立一个连接。
@babel/preset-env 是用来将 ES6 的语法转为 ES5的。比如 let
、const
转为 var
; ()=>{}
转为 function () {}
ES5函数。
但是 @babel/preset-env 不会转换 新的API,以及一些在全局对象上的方法 都不会进行转码。 比如 Iterator, Generator, Set, Maps, Proxy, Reflect,Symbol,Promise
等全局对象,以及 Object.assign , Array.from
全局对象的方法,都不会进行转码。
虽然也是进行了语法翻译,但是只是翻译了一部分 ES6
特性。在低版本依赖会报错。
polyfill (填充)
业务代码
我们可以借助 @babel/polyfill
来对这些 API 进行填充。
npm install --save @babel/polyfill
import "@babel/polyfill"; 引入到我们使用了 ES6 特性的文件。
这样子,我们就可以了看到,API 都被 polyfill 好打包输出到 bundle.js 文件。babel 会将所有的 polyfill 全部引入,这样会导致结果的包大小非常大,而我们可能仅仅需要一个方法而已.
按需加载
options: {
['@babel/preset-env', {
useBuiltIns: 'usage',
"debug": true
}]
}
我们通过 useBuiltIns: 'usage'
配置,就可以做到 按需转译
。
targets: {chrome: "67"}
是说如果 chrome 67版本,对 ES6 兼容性好的话,我们就不需要进行 ES6 转 ES5.
babel
的polyfill
机制是,对于例如Array.from
等静态方法,直接在global.Array
上添加;对于例如includes
等实例方法,直接在global.Array.prototype
上添加。这样直接修改了全局变量的原型,有可能会带来意想不到的问题。污染全局.- babel 经常会使用一些辅助函数来帮忙转。
- class 语法中,babel 自定义了
_classCallCheck
这个函数来辅助;typeof 则是直接重写了一遍,自定义了_typeof
这个函数来辅助。这些函数叫做 helpers。如果一个项目中有100个文件,其中每个文件都写了一个 class,那么这个项目最终打包的产物里就会存在100个_classCallCheck
函数,这样肯定是不好的。
组件库,类库
为了不污染全局对象和内置的对象原型,又想要在低版本浏览器运行,可以配合使用babel-runtime
和 @babel/plugin-transform-runtime
。
比如当前运行环境不支持 promise
,可以通过引入 babel-runtime/core-js/promise
来获取 promise。
在一开始的时候,只是存在 babel-runtime
,用起来不方便,但代码中引入 helper
函数,意味着不能页面间共享,造成打包完很多重复的 helper 代码。
Babel又开发了 @babel/plugin-transform-runtime
,这个模块会将我们的代码重写,然后引入 helper
函数。这样子就避免了重复打包
代码和手动引入
的繁琐。
npm install --save-dev @babel/plugin-transform-runtime npm install --save @babel/runtime-corejs2
@babel/plugin-transform-runtime
的作用就是转译代码,转译后的代码中可能会引入 @babel/runtime-corejs2
里面的模块。@babel/runtime-corejs2 需要被打包到最终产物里在浏览器中运行
module.exports = {
module: {
rules: [
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
"plugins": ["@babel/plugin-transform-runtime", {
"corejs": 2
}]
}
}
]
}
}
指定 runtime-corejs
的版本,目前有 2 3 两个版本,默认值是 false,不会 polyfill
提案。
使用了 @babel/plugin-transform-runtime
之后
-
API
从直接修改原型改为从一个统一的模块中引入,避免了对全局变量及其原型的污染。 -
helpers
从之前的直接定义改为了从一个统一的模块中引入,使得打包的结果中每个 helper 只会存在一个。