-
Notifications
You must be signed in to change notification settings - Fork 1
Description
关于Babel
- Babel 编译的三个阶段
- Babel使用
- polyfill和runtime差别
- Babel插件开发流程
Babel 编译的三个阶段
Babel 的编译过程可以分为三个阶段:
- 解析(Parsing):将代码字符串解析成抽象语法树。
- 转换(Transformation):对抽象语法树进行转换操作。
- 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串。

1. 解析(Parsing)
使用 babylon 解析器对输入的源代码字符串进行解析并生成初始 AST。整个解析过程分为两个步骤:1)词法分析(lexical analyzer或scanner):将整个代码字符串分割成语法单元数组在线分词工具。2)语法分析(syntax analyzer):它会将词法分析出来的数组转化成树形的表达形式。同时,验证语法,语法如果有错的话,抛出语法错误。
babel 内部使用的解析类库叫做 babylon,并非 babel 自行开发。
简单来说语法分析是对语句和表达式识别,这是个递归过程,在解析中,Babel 会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。
*可以通过astexplorer.net 网站在线生成抽象语法树AST*
2. 转换(Transformation)
babel中最核心的是 babel-core ,它向外暴露出 babel.transform 接口,遍历 AST 树并应用各 transformers(plugin)。
const babel = require('babel-core');
let result = babel.transform(code, {
presets: ['env'],
plugins: ['transform-runtime']
})3. 生成(Code Generation)
利用 babel-generator 将 AST 树生成转码后的代码
Babel使用
将插件的名字增加到配置文件中 (根目录下创建.babelrc或者 package.json的babel中),然后npm install babel-plugin-xxx进行安装。
示例:
{
"presets": [
["env", // 带了配置项,自己变成数组。第一个元素依然是名字
{ // 第二个元素是对象,列出配置项
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
],
// 不带配置项,直接列出名字
"stage-2"
],
"plugins": [
"transform-vue-jsx",
"transform-runtime",
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
],
"env": {
// 基于环境来配置 Babel
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}env:Babel 将根据当前环境来开启 env 下的配置。当前环境可以使用 process.env.BABEL_ENV 来获得。 如果 BABEL_ENV 不可用,将会替换成 NODE_ENV,并且如果后者也没有设置,那么缺省值是development。
Presets
因为一套类似es2015的规范,包含了很多个转译插件。为了每次要开发者一个个添加并安装转译插件问题,babel提供了一组插件的集合。理解为单点和套餐的差别
preset分为以下几种:
- 官方内容:目前包括 env, react, flow, minify 等。
- Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。
- Stage 1 - 提案: 初步尝试。
- Stage 2 - 初稿: 完成初步规范。
- Stage 3 - 候选: 完成规范和浏览器初步实现。
- Stage 4 - 完成: 将被添加到下一年度发布。在下一年更新会直接放到env中,所以没有单独的 stage-4 可供使用。
env配置参数
- 如果不写任何配置项,env 等价于 latest,也等价于 es2015 + es2016 + es2017 三个相加。env包含的插件列表
- targets:描述所需要支持的环境
{
"presets": [
["env", {
"targets": "> 0.25%, not dead"
}]
]
}{
"presets": [
["env", {
"targets": {
"chrome": "58",
"ie": "11"
}
}]
]
}{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
}
}]
]
}语法可以参考browserslist
- modules:它的取值可以是
amd,umd,systemjs,commonjs和false。转换es6模块语法到其他模块规范, false不会转换。默认为commonjs。
执行顺序
- Plugin 会运行在 Preset 之前。
- Plugin 会从前到后顺序执行。
- Preset 的顺序则从后向前。(preset的逆向顺序主要是为了保证向后兼容)
其他工具
- babel-register只会对 require 命令加载的文件转码,而不会对当前文件转码。由于它是实时转码,所以 只适合在开发环境使用。
- babel-loader: 希望在 uglify 之前就加入 babel 处理,所以就有了babel 插入到构建工具内部这样的需求。
- babel-plugin-component:按需引入需要的组件,以达到减小项目体积的目的。
{
"plugins": [
[
"component",
{
"libraryName": "element-ui", "styleLibraryName": "theme-chalk"
},
{ "libraryName": "mint-ui", "style": true }
]
]
}babel-plugin-component部分源码:
var _options = options,
_options$libDir = _options.libDir,//这是组件所在根目录下的路径element-ui/lib/
libDir = _options$libDir === void 0 ? 'lib' : _options$libDir,
_options$libraryName = _options.libraryName,//这是ui库的名字--elementui
libraryName = _options$libraryName === void 0 ? defaultLibraryName : _options$libraryName,
_options$style = _options.style,
style = _options$style === void 0 ? true : _options$style,
styleLibrary = _options.styleLibrary,//这是引入组件时,所需要引入对应组件样式的配置对象
_options$root = _options.root,
root = _options$root === void 0 ? '' : _options$root,
_options$camel2Dash = _options.camel2Dash,
camel2Dash = _options$camel2Dash === void 0 ? true : _options$camel2Dash;
var styleLibraryName = options.styleLibraryName;//这是组件所需样式的路径(相对于上面的lib)
var _root = root;
var isBaseStyle = true;
var modulePathTpl;
var styleRoot;
var mixin = false;
var ext = options.ext || '.css';//这是加载样式的后缀,默认css所以,如果在.babelrc文件配置过styleLibraryName属性的,就不需要在全局引入element的css样式。
polyfill和runtime差别
背景:
Babel 把 Javascript 语法 分为 syntax 和 api:
api指那些我们可以通过 函数重新覆盖的语法 ,类似 includes,map,includes,Promise,凡是我们能想到重写的都可以归属到 api
syntax像箭头函数,let,const,class, 依赖注入Decorators,等等这些,我们在 Javascript 在运行是无法重写的,想象下,在不支持的浏览器里不管怎么样,你都用不了 let 这个关键字
babel 默认只转换syntax层语法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。对于API, Babel 把这个放在了 单独放在了 polyfill 这个模块处理。
polyfill(垫片)
使用时,在所有代码运行之前增加 require('babel-polyfill')。或者更常规的操作是在 webpack.config.js 中将 babel-polyfill 作为第一个 entry。
babel-polyfill主要有两个缺点:
- 使用
babel-polyfill会导致打出来的包非常大,因为 babel-polyfill 是一个整体,把所有方法都加到原型链上。 babel-polyfill会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供其他开发者使用,这种情况就会变得非常不可控。
改进方案:
使用useBuiltIns设置:取值可以是usage(按需引入), entry(全部引入,会根据browserlist 过滤出需要的polyfill)和false。默认值为false。Babel 7该配置才会生效
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage"
}
]
]
}runtime
为了解决babel-polyfill会污染全局空间的问题,有了babel-plugin-transform-runtime。babel-plugin-transform-runtime的目的是为您的代码创建沙盒环境,不会污染全局空间和内置对象原型。它适用于开发供其他人使用的库,或者如果您无法准确控制代码运行的环境。
{
"presets": [
[
"env"
]
],
"plugins": ["transform-runtime"]
}例如,你的代码中用了Promise,babel-plugin-transform-runtime会自动重写你使用Promise的代码,转换为使用babel-runtime导出(export)的Promise-like对象。 所以plugin-transform-runtime一般用于开发(devDependencies),而runtime自身用于部署的代码(dependencies),两者配合来一起工作。
babel-runtime内部集成了:
core-js: 转换一些内置类 (Promise, Symbols等等) 和静态方法 (Array.from 等)。它几乎包含了所有 JavaScript 最新标准的垫片。regenerator:作为 core-js 的拾遗补漏,主要是 generator/yield 和 async/await 两组的支持。helpers: babel的辅助函数,例如 toArray函数, jsx转化函数。
babel-runtime缺点:
babel-plugin-transform-runtime不支持实例方法 (例如 [1,2,3].includes(1)),这时还是需要使用babel-polyfill
*总结:Babel 只是转换syntax层语法,所有需要 @babel/polyfill 来处理API兼容,又因为 polyfill 体积太大,所以通过 preset的 useBuiltIns 来实现按需加载,再接着为了满足 npm 组件开发的需要 出现了@babel/runtime 来做隔离。*
Babel插件开发流程
Babel的插件模块需要你暴露一个function,function内返回visitor,visitor是对各类型的AST节点做处理的地方。类似这样:
module.export = function(babel){
return {
visitor: {
// ...你的插件代码
}
}
}通过AST explorer知道要处理的AST节点,如下:

基本流程:
- 定义需要转换的节点
BinaryExpression(path) {
...
}- 在node节点上找到替换的节点(通过
babel-types)
var t = require('babel-types');
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {
...
}- 创建用来替换的节点(通过
babel-types),replaceWith(替换)
var t = require('babel-types');
...
path.replaceWith(t.numericLiteral(result));【参考】
前端工程师需要了解的 Babel 知识
一口(很长的)气了解 babel
Babel学习系列4-polyfill和runtime差别(必看)
@babel/polyfill 与 @babel/plugin-transform-runtime 详解
你真的会用 Babel 吗?
了解 Babel 6 & 7 生态
深入浅出的webpack构建工具---babel之配置文件.babelrc(三)



