babel 每次学习都有新的了解,哪怕是其配置都与咱们前端生态中的各种概念非亲非故。近期再次温习 babel 常识从而更好的编写 js 类库,本文是学习过程所做的记录。
babel 生态里的一些 npm 模块
- babel/core 外围本义性能
- babel/cli 命令行工具,能够通过 babel 命令来转换代码,或者转换文件,输入本义后的文件。如 babel src –out-dir lib –watch
- babel/node 是用他来执行 es6+ 代码,以 node 形式执行。适宜于去执行你写的 nodejs 代码。
那么,咱们应用 webpack 来打包 js 代码时,是应用的什么呢?答案是 babel-loader。
babel-lodaer 是适配 webpack 的一个模块,其外部就是调起 babel/core 来实现代码转译。
了解转译其实有 2 种类型的转译
记住:babel 本身惟一能做的事件叫:代码转译。
至于新增 api 那些,那是须要引入 polyfill 垫片的事件(下文会讲)。
babel 自身所做的事件,个别都叫他代码转译。那么所谓的转译都有哪些品种呢?如下:
1、单纯的语法解析和转换。例如箭头函数转成 function
2、须要一点辅助的语法解析和转换,例如 class 语法,给你转成 prototype 原型语法
解释下第二点:咱们晓得,一种新的语法意味着新的一些关键字和符号等,那么对于一些特殊符号如箭头函数,那么他恰好有 es5 对应的 function
关键字,所以 babel 只需简略换一下字符。
然而有些却不是简略的换一下,例如 class 语法,babel 给你转成 es5 之后,他必须革新成函数和 prototype 的写法,在这种状况下,babel 编译后的代码中会退出一些辅助函数 (也就是下文说的 babel/runtime 所干的事儿) 以帮助来用 es5 的形式实现 class。
举个栗子:
看到了吗,babel 生成的原型式写法中,须要在构造函数中对调用者的实例化形式进行检测,这些都封装成了 helper 函数 _classCallCheck。
除此之外,还有更简单的场景 babel 转译之后甚至须要依赖 polyfill 垫片:例如 async 转换。babel 会把 async 语法代码转成 generator 辅助函数,而 generator 辅助函数的性能须要依赖 regenerator-runtime
这个 polyfill 垫片。如下图,babel 给你把 async 语法转换后,多了很多辅助函数;甚至其中有一个 regeneratorRuntime 的函数是没有找到定义的,而这个函数其实就是依赖全局须要引入 regenerator-runtime
这个 polyfill 才行。
了解什么是 corejs 和 babel/runtime
首先,咱们要了解什么是 polyfill 什么是辅助函数。
像上文所说的 2 种 babel 转译,其中转换之后所呈现的那些辅助函数,叫做运行时所须要的 helper,他们其实就是 runtime。而这些运行时函数,有一个独自的 npm 包去实现他们,叫做 babel/runtime。
而像那些 es6 新增的 API,例如 Promise、Map、WeakMap。数据新办法 flat、includes 等等 api,这些货色不属于 babel 语法编译的领域,是属于一些新的 api。这些叫做 垫片,英文名叫 polyfill。在社区里,polyfill 垫片通过另外 2 个 npm 包来实现:一个叫做 corejs(用来实现除了 generator 之外的垫片,当初要应用他的版本 3),另一个叫做 regenerator-runtime(用来实现 generator 垫片)。
实战:配置 babel 的最佳实际
其实最佳实际的前提就是正确的了解上文中两个概念。从而正确的应用 babel/runtime 和 corejs3.
如果比喻成做饭,babel/runtime 和 corejs3 就是咱们的食材。有了食材,咱们就要用一个配套的刀具来加工他们。
- babel/runtime 配套的刀具是:babel/plugin-transform-runtime
- corejs3 的配套刀具是 preset/env 预设。
咱们接下来所讲的实际,其背景是在 webpack + babel 下的实际。因为,毕竟咱们通常不会单据来用 babel 操作一个 js 文件。
webpack 打包一个网站利用 js 时
这种场景的要求是:
- 按需转译语法和辅助函数。例如如果我的指标平台反对了箭头函数,那么 babel 请不要给我转换成 es5 那种语法。
- 全局 polyfill 垫片即可,不怕净化,因为我冀望面向我产品所有用户应用的浏览器
- 尽可能的少打包垫片。即按需 polyfill。因为咱们冀望尽可能减少不必要的体积。比方我面向的用户浏览器大于 IE10,那么 IE9 的 polyfill 不要打;同时,如果我代码中没有应用 promise,那么 promise 的垫片也不要给我打
- helper 那些辅助函数,不要每个 js 模块中都写一份(因为 babel 自身必定是针对每个 js 编译的,所以默认每个 js 必定都会呈现辅助函数)
解决办法:
1、为了能按需转译语法。在最新 babel7 之后,咱们只需应用 preset/env 预设就很简略了。
// babel.config.js
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": false, // polyfill 配置先关掉,前面再讲
}
]
]
}
咱们能够通过批改 targets,来决定 babel 编译哪些语法。例如你把 chrome 版本号调到最新 1 个版本,那么箭头函数必然是不会转换的。
2、3、按需加载 polyfill
按需加载 polyfill 有 2 种形式,一种是把 useBuiltIns 配置成 entry,另一种是配置成 usage.
应用上的区别是:
- entry:须要你手工在你的 webpack 入口 js 里,引入一下 corejs 和 regenerator-runtime 这俩 polyfill。babel 编译后,会主动在入口 js 里,把你那 2 行换成面向指标 targets 按需援用的 corejs 模块。
- usage:不须要你手工引入。babel 会主动把 corejs 库的模块放到你的每个 js 模块里。搁置的准则就是:不仅面向指标 targets 来按需引入,而且还依照你代码中是否应用来引入。如果你的 a.js 里用了 promise,那么他会把 corejs 中 promise 模块引入。
如下是 usage 的形式:
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": 'usage',
"corejs": 3 // 写死就好!以后阶段就该用 3
}
]
]
}
如下是 entry 形式
//babel.config.js
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": 'entry',
"corejs": 3
}
]
]
}
entry 模式时,须要在代码里手工引入一下。
// index.js
// 手工引入
import "core-js/stable";
import "regenerator-runtime/runtime";
既然 usage 这么好,咱们何苦要用 entry 呢?所以,就用 usage 模式吧!
4、问题来了:怎么解决辅助函数的冗余问题。
babel/runtime 是干啥的,要杂用?
上文的配置,咱们解决了 polyfill 的问题。下一步,咱们要解决辅助函数在每个 js 里都有冗余的问题。因为当初的配置下,如果你有 a.js 和 b.js,那么两个 js 中都会被 babel 放入那些 helper 辅助函数。当 webpack 打包实现一个 bundle。学过 webpack 原理的应该指到:最终的 bundle 里也会有 a.js 和 b.js 模块,每个模块中必然也存在反复的辅助函数。
怎么解决?那就用 babel/plugin-transform-runtime 插件。
这个插件的工作就是:在 babel 编译每个 js 时,把外面的辅助函数给换成 对 babel/runtime 的 require 援用。
当每个 js 里的辅助函数,都变成 babel/runtime 的援用。那么 webpack 打包后的 bundle,必然这些辅助函数就变成一个公共模块了,解决了冗余问题。
配置形式:
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": 'usage',
"corejs": 3
}
]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"helpers": true, // 这就是抽离 helper 的配置
"corejs": false, // 先设置 false,前面再讲
"regenerator": false // 先设置 false,前面再讲
}]
]
}
下面把 helper 配置为 true,就能够实现 helper 函数抽到 babel/runtime。
至此,网站打包解说配置实现!开发网站时就用上述配置即可。
webpack 打包一个 jssdk 时
开发 jssdk 跟开发网站对 bundle.js 有不同的要求。
- 咱们冀望咱们的 sdk 能够反对很多 target 环境,因而须要 polyfill 垫片。但咱们不晓得 jssdk 的运行环境是否有垫片。这里有 2 个办法,办法 1:通过文档通知开发者你须要加哪些垫片;办法 2:咱们本人 polyfill,然而不能净化全局
- jssdk 如果本人进行 polyfill。那当然也心愿按需 polyfill,缩小体积
- 辅助函数同样须要缩小冗余,跟上文网站雷同。
能够不言而喻,对于开发 jssdk,咱们第一要做的,就是先把全局 polyfill 给他关掉。比方关掉那个 usage:
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": false
}
]
]
}
侥幸的是,jssdk 的另外几个要求,靠 babel/plugin-transform-runtime 都能够搞定。如下配置即可:
module.exports = {
"presets": [
[
"@babel/env",
{
"targets": "last 50 Chrome versions",
"useBuiltIns": false
}
]
],
"plugins": [
["@babel/plugin-transform-runtime", {
"helpers": true, // 这就是抽离 helper 的配置
"corejs": 3, // 这是部分 polyfill 的配置
"regenerator": true // 这是部分 polyfill generator 的配置
}]
]
}
咱们只需把该插件的配置全开。把 preset/env 的 polyfill 配置关掉,就是一个适配 jssdk 开发的配置了。