Open Luin-Li opened 4 years ago
Babel 的编译过程可以分为三个阶段:
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 树生成转码后的代码
将插件的名字增加到配置文件中 (根目录下创建.babelrc或者 package.json的babel中),然后npm install babel-plugin-xxx进行安装。 示例:
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。
env
development
因为一套类似es2015的规范,包含了很多个转译插件。为了每次要开发者一个个添加并安装转译插件问题,babel提供了一组插件的集合。理解为单点和套餐的差别
preset分为以下几种:
{ "presets": [ ["env", { "targets": "> 0.25%, not dead" }] ] }
{ "presets": [ ["env", { "targets": { "chrome": "58", "ie": "11" } }] ] }
{ "presets": [ ["env", { "targets": { "browsers": ["last 2 versions", "safari >= 7"] } }] ] }
语法可以参考browserslist
amd
umd
systemjs
commonjs
false
babel-plugin-component:按需引入需要的组件,以达到减小项目体积的目的。
{ "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" }, { "libraryName": "mint-ui", "style": true } ] ] }
babel-plugin-component部分源码:
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样式。
背景: 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 这个模块处理。
使用时,在所有代码运行之前增加 require('babel-polyfill')。或者更常规的操作是在 webpack.config.js 中将 babel-polyfill 作为第一个 entry。
require('babel-polyfill')
babel-polyfill主要有两个缺点:
babel-polyfill
改进方案: 使用useBuiltIns设置:取值可以是usage(按需引入), entry(全部引入,会根据browserlist 过滤出需要的polyfill)和false。默认值为false。Babel 7该配置才会生效
usage
entry
{ "presets": [ [ "@babel/preset-env", { "useBuiltIns": "usage" } ] ] }
使用上面的配置,打包后如下:
为了解决babel-polyfill会污染全局空间的问题,有了babel-plugin-transform-runtime。babel-plugin-transform-runtime的目的是为您的代码创建沙盒环境,不会污染全局空间和内置对象原型。它适用于开发供其他人使用的库,或者如果您无法准确控制代码运行的环境。
babel-plugin-transform-runtime
{ "presets": [ [ "env" ] ], "plugins": ["transform-runtime"] }
babel配置如上,打包后的结果:
例如,你的代码中用了Promise,babel-plugin-transform-runtime会自动重写你使用Promise的代码,转换为使用babel-runtime导出(export)的Promise-like对象。 所以plugin-transform-runtime一般用于开发(devDependencies),而runtime自身用于部署的代码(dependencies),两者配合来一起工作。
Promise
babel-runtime内部集成了:
babel-runtime
core-js
regenerator
helpers
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的插件模块需要你暴露一个function,function内返回visitor,visitor是对各类型的AST节点做处理的地方。类似这样:
visitor
module.export = function(babel){ return { visitor: { // ...你的插件代码 } } }
通过AST explorer知道要处理的AST节点,如下:
基本流程:
BinaryExpression(path) { ... }
babel-types
var t = require('babel-types'); if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) { ... }
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(三)
关于Babel
Babel插件开发流程
Babel 编译的三个阶段
Babel 的编译过程可以分为三个阶段:
1. 解析(Parsing)
使用 babylon 解析器对输入的源代码字符串进行解析并生成初始 AST。整个解析过程分为两个步骤:1)词法分析(lexical analyzer或scanner):将整个代码字符串分割成语法单元数组在线分词工具。2)语法分析(syntax analyzer):它会将词法分析出来的数组转化成树形的表达形式。同时,验证语法,语法如果有错的话,抛出语法错误。
简单来说语法分析是对语句和表达式识别,这是个递归过程,在解析中,Babel 会在解析每个语句和表达式的过程中设置一个暂存器,用来暂存当前读取到的语法单元,如果解析失败,就会返回之前的暂存点,再按照另一种方式进行解析,如果解析成功,则将暂存点销毁,不断重复以上操作,直到最后生成对应的语法树。
*可以通过astexplorer.net 网站在线生成抽象语法树AST*
2. 转换(Transformation)
babel中最核心的是 babel-core ,它向外暴露出 babel.transform 接口,遍历 AST 树并应用各 transformers(plugin)。
3. 生成(Code Generation) 利用 babel-generator 将 AST 树生成转码后的代码
Babel使用
将插件的名字增加到配置文件中 (根目录下创建.babelrc或者 package.json的babel中),然后
npm install babel-plugin-xxx
进行安装。 示例:env
:Babel 将根据当前环境来开启 env 下的配置。当前环境可以使用 process.env.BABEL_ENV 来获得。 如果 BABEL_ENV 不可用,将会替换成 NODE_ENV,并且如果后者也没有设置,那么缺省值是development
。Presets
因为一套类似es2015的规范,包含了很多个转译插件。为了每次要开发者一个个添加并安装转译插件问题,babel提供了一组插件的集合。理解为单点和套餐的差别
preset分为以下几种:
env配置参数
语法可以参考browserslist
amd
,umd
,systemjs
,commonjs
和false
。转换es6模块语法到其他模块规范, false不会转换。默认为commonjs
。执行顺序
其他工具
babel-plugin-component:按需引入需要的组件,以达到减小项目体积的目的。
babel-plugin-component
部分源码:所以,如果在.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该配置才会生效使用上面的配置,打包后如下:
runtime
为了解决
babel-polyfill
会污染全局空间的问题,有了babel-plugin-transform-runtime
。babel-plugin-transform-runtime
的目的是为您的代码创建沙盒环境,不会污染全局空间和内置对象原型。它适用于开发供其他人使用的库,或者如果您无法准确控制代码运行的环境。babel配置如上,打包后的结果:
例如,你的代码中用了
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节点做处理的地方。类似这样:通过AST explorer知道要处理的AST节点,如下:
基本流程:
babel-types
)babel-types
),replaceWith(替换)【参考】 前端工程师需要了解的 Babel 知识 一口(很长的)气了解 babel Babel学习系列4-polyfill和runtime差别(必看) @babel/polyfill 与 @babel/plugin-transform-runtime 详解 你真的会用 Babel 吗? 了解 Babel 6 & 7 生态 深入浅出的webpack构建工具---babel之配置文件.babelrc(三)