目前部分浏览器和 Node.Js 已经支持 ES6,但是对 ES6 所有的标准支持不全,这导致在开发中不敢全面地使用 ES6。
通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码。
class
语法用 ES5 的 prototype
实现。polyfill
,例如项目使用 fetch API
时,只有注入对应的 polyfill,才能在低版本使用。Babel 可以方便的完成上面的两件事。Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展。在 Babel 执行编译的过程中,会从项目根目录下的 .babelrc
文件读取配置。.babelrc 是一个 JSON 格式的文件。
plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。
例如: 配置文件里的 transform-runtime
对应的插件全名叫做 babel-plugin-transform-runtime
。在文件里可以忽略前缀 babel-plugin-
,安装需要全名。
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": [
[
"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
}
]
]
}
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
特性。在低版本依赖会报错。
我们可以借助 @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 只会存在一个。