目次
- Babel简介
- Babel运转道理
- AST剖析
- AST转换
- 写一个Babel插件
Babel简介
Babel 是一个 JavaScript 编译器,它能将es2015,react等低端浏览器没法辨认的言语,举行编译。
上图的左侧代码中有箭头函数,Babel将举行了源码转换,下面我们来看Babel的运转道理。
Babel运转道理
Babel 的三个重要处置惩罚步骤分别是:
剖析(parse),转换(transform),天生(generate)。.
其历程剖析用言语形貌的话,就是下面如许:
剖析
运用 <font color=Chocolate>babylon</font> 剖析器对输入的源代码字符串举行剖析并天生初始 AST(File.prototype.parse)
运用 <font color=Chocolate>babel-traverse</font> 这个自力的包对 AST 举行<font color=Chocolate>遍历</font>,并剖析出全部树的 <font color=Chocolate>path</font>,经由过程挂载的 metadataVisitor 读取对应的元信息,这一步叫 set AST 历程
转换
transform 历程:遍历 AST 树并运用各 <font color=Chocolate>transformers(plugin)</font> 天生变更后的 AST 树
babel 中最中心的是 <font color=Chocolate>babel-core</font>,它向外暴露出 babel.transform 接口。
let result = babel.transform(code, {
plugins: [
arrayPlugin
]
})
天生
运用 <font color=Chocolate>babel-generator</font> 将 <font color=Chocolate>AST</font> 树输出为转码后的代码字符串
AST剖析
AST剖析会把拿到的语法,举行树形遍历,对语法的每一个节点举行相应的变化和革新再生产新的代码字符串
节点(node)
AST将开首提到的箭头函数转依据节点换为节点树
ES2015箭头函数
codes.map(code=>{
return code.toUpperCase()
})
AST树形遍历转换后的构造
{
type:"ExpressionStatement",
expression:{
type:"CallExpression"
callee:{
type:"MemberExpression",
computed:false
object:{
type:"Identifier",
name:"codes"
}
property:{
type:"Identifier",
name:"map"
}
range:[]
}
arguments:{
{
type:"ArrowFunctionExpression",
id:null,
params:{
type:"Identifier",
name:"code",
range:[]
}
body:{
type:"BlockStatement"
body:{
type:"ReturnStatement",
argument:{
type:"CallExpression",
callee:{
type:"MemberExpression"
computed:false
object:{
type:"Identifier"
name:"code"
range:[]
}
property:{
type:"Identifier"
name:"toUpperCase"
}
range:[]
}
range:[]
}
}
range:[]
}
generator:false
expression:false
async:false
range:[]
}
}
}
}
我们从 ExpressionStatement最先往树形构造内里走,看到它的内部属性有callee,type,arguments,所以我们再顺次接见每一个属性及它们的子节点。
因而就有了以下的递次
进入 ExpressionStatement
进入 CallExpression
进入 MemberExpression
进入 Identifier
脱离 Identifier
进入 Identifier
脱离 Identifier
脱离 MemberExpression
进入 ArrowFunctionExpression
进入 Identifier
脱离 Identifier
进入 BlockStatement
进入 ReturnStatement
进入 CallExpression
进入 MemberExpression
进入 Identifier
脱离 Identifier
进入 Identifier
脱离 Identifier
脱离 MemberExpression
脱离 CallExpression
脱离 ReturnStatement
脱离 BlockStatement
脱离 ArrowFunctionExpression
脱离 CallExpression
脱离 ExpressionStatement
脱离 Program
Babel 的转换步骤全都是如许的遍历历程。(有点像koa的洋葱模子??)
AST转换
剖析好树构造后,我们手动对箭头函数举行转换。
对照两张图,发明不一样的处所就是两个函数的arguments.type
剖析代码
let babel = require('babel-core');//babel中心库
let types = require('babel-types');
let code = `codes.map(code=>{return code.toUpperCase()})`;//转换语句
let visitor = {
ArrowFunctionExpression(path) {//定义须要转换的节点
let params = path.node.params
let blockStatement = path.node.body
let func = types.functionExpression(null, params, blockStatement, false, false)
path.replaceWith(func) //
}
}
let arrayPlugin = { visitor }
let result = babel.transform(code, {
plugins: [
arrayPlugin
]
})
console.log(result.code)
注重: ArrowFunctionExpression() { … } 是 ArrowFunctionExpression: { enter() { … } } 的简写情势。
<font color=Chocolate>Path 是一个对象,它示意两个节点之间的衔接。</font>
剖析步骤
- 定义须要转换的节点
ArrowFunctionExpression(path) {
......
}
- 建立用来替代的节点
types.functionExpression(null, params, blockStatement, false, false)
- 在node节点上找到须要的参数
- replaceWith(替代)
写一个Babel插件
从一个接收了 babel 对象作为参数的 function 最先。
export default function(babel) {
// plugin contents
}
接着返回一个对象,其 visitor 属性是这个插件的重要节点接见者。
export default function({ types: t }) {
return {
visitor: {
// visitor contents
}
};
};
我们一样平常引入依靠的时刻,会将全部包引入,致使打包后的代码太冗余,到场了很多不须要的模块,比方index.js三行代码,打包后的文件大小就达到了483 KiB,
index.js
import { flatten, join } from "lodash";
let arr = [1, [2, 3], [4, [5]]];
let result = _.flatten(arr);
所以我们此次的目标是将
import { flatten, join } from "lodash";
转换为从而只引入两个lodash模块,削减打包体积
import flatten from "lodash/flatten";
import join from "lodash/join";
完成步骤以下:
- 在项面前目今的node_module中新建文件夹 <font color=Chocolate>babel-plugin-extraxt</font>
注重:babel插件文件夹的定义体式格局是 babel-plugin-插件名
我们能够在.babelrc的plugin中引入自定义插件 或许在webpack.config.js的loader options中到场自定义插件
- 在babel-plugin-extraxt新建index.js
module.exports = function ({types:t}) {
return {
// 对import转码
visitor:{
ImportDeclaration(path, _ref = { opts: {} }) {
const specifiers = path.node.specifiers;
const source = path.node.source;
// 只要libraryName满足才会转码
if (_ref.opts.library == source.value && (!t.isImportDefaultSpecifier(specifiers[0]))) { //_ref.opts是传进来的参数
var declarations = specifiers.map((specifier) => { //遍历 uniq extend flatten cloneDeep
return t.ImportDeclaration( //建立importImportDeclaration节点
[t.importDefaultSpecifier(specifier.local)],
t.StringLiteral(`${source.value}/${specifier.local.name}`)
)
})
path.replaceWithMultiple(declarations)
}
}
}
};
}
- 修正<font color=Chocolate>webpack.prod.config.js</font>中babel-loader的设置项,在plugins中增加自定义的插件名
rules: [{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ["env",'stage-0'],
plugins: [
["extract", { "library":"lodash"}],
["transform-runtime", {}]
]
}
}]
注重:plugins 的插件运用递次是递次的,而 preset 则是逆序的。所以上面的实行体式格局是extract>transform-runtime>env>stage-0
- 运转引入了自定义插件的webpack.config.js
打包文件现在为21.4KiB,显著减小,自定义插件胜利!~
插件文件目次
YUAN-PLUGINS
|
| - node_modules
| |
| | - babel-plugins-extract
| |
| index.js
|
| - src
| | - index.js
|
| - webpack.config.js