目錄
- Babel簡(jiǎn)介
- Babel運(yùn)行原理
- AST解析
- AST轉(zhuǎn)換
- 寫(xiě)一個(gè)Babel插件
Babel簡(jiǎn)介
Babel 是一個(gè) JavaScript 編譯器,它能將es2015,react等低端瀏覽器無(wú)法識(shí)別的語(yǔ)言沛善,進(jìn)行編譯说墨。
上圖的左邊代碼中有箭頭函數(shù)盯仪,Babel將進(jìn)行了源碼轉(zhuǎn)換排作,下面我們來(lái)看Babel的運(yùn)行原理讹开。
Babel運(yùn)行原理
Babel 的三個(gè)主要處理步驟分別是:
解析(parse),轉(zhuǎn)換(transform),生成(generate)射亏。.
其過(guò)程分解用語(yǔ)言描述的話,就是下面這樣:
解析
使用 <font color=Chocolate>babylon</font> 解析器對(duì)輸入的源代碼字符串進(jìn)行解析并生成初始 AST(File.prototype.parse)
利用 <font color=Chocolate>babel-traverse</font> 這個(gè)獨(dú)立的包對(duì) AST 進(jìn)行<font color=Chocolate>遍歷</font>,并解析出整個(gè)樹(shù)的 <font color=Chocolate>path</font>智润,通過(guò)掛載的 metadataVisitor 讀取對(duì)應(yīng)的元信息及舍,這一步叫 set AST 過(guò)程
轉(zhuǎn)換
transform 過(guò)程:遍歷 AST 樹(shù)并應(yīng)用各 <font color=Chocolate>transformers(plugin)</font> 生成變換后的 AST 樹(shù)
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> 樹(shù)輸出為轉(zhuǎn)碼后的代碼字符串
AST解析
AST解析會(huì)把拿到的語(yǔ)法锯玛,進(jìn)行樹(shù)形遍歷,對(duì)語(yǔ)法的每個(gè)節(jié)點(diǎn)進(jìn)行響應(yīng)的變化和改造再生產(chǎn)新的代碼字符串
節(jié)點(diǎn)(node)
AST將開(kāi)頭提到的箭頭函數(shù)轉(zhuǎn)根據(jù)節(jié)點(diǎn)換為節(jié)點(diǎn)樹(shù)
ES2015箭頭函數(shù)
codes.map(code=>{
return code.toUpperCase()
})
AST樹(shù)形遍歷轉(zhuǎn)換后的結(jié)構(gòu)
{
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開(kāi)始往樹(shù)形結(jié)構(gòu)里面走攘残,看到它的內(nèi)部屬性有callee,type为狸,arguments歼郭,所以我們?cè)僖来卧L問(wèn)每一個(gè)屬性及它們的子節(jié)點(diǎn)。
于是就有了如下的順序
進(jìn)入 ExpressionStatement
進(jìn)入 CallExpression
進(jìn)入 MemberExpression
進(jìn)入 Identifier
離開(kāi) Identifier
進(jìn)入 Identifier
離開(kāi) Identifier
離開(kāi) MemberExpression
進(jìn)入 ArrowFunctionExpression
進(jìn)入 Identifier
離開(kāi) Identifier
進(jìn)入 BlockStatement
進(jìn)入 ReturnStatement
進(jìn)入 CallExpression
進(jìn)入 MemberExpression
進(jìn)入 Identifier
離開(kāi) Identifier
進(jìn)入 Identifier
離開(kāi) Identifier
離開(kāi) MemberExpression
離開(kāi) CallExpression
離開(kāi) ReturnStatement
離開(kāi) BlockStatement
離開(kāi) ArrowFunctionExpression
離開(kāi) CallExpression
離開(kāi) ExpressionStatement
離開(kāi) Program
Babel 的轉(zhuǎn)換步驟全都是這樣的遍歷過(guò)程辐棒。(有點(diǎn)像koa的洋蔥模型病曾??)
AST轉(zhuǎn)換
解析好樹(shù)結(jié)構(gòu)后涉瘾,我們手動(dòng)對(duì)箭頭函數(shù)進(jìn)行轉(zhuǎn)換知态。
對(duì)比兩張圖捷兰,發(fā)現(xiàn)不一樣的地方就是兩個(gè)函數(shù)的arguments.type
解析代碼
let babel = require('babel-core');//babel核心庫(kù)
let types = require('babel-types');
let code = `codes.map(code=>{return code.toUpperCase()})`;//轉(zhuǎn)換語(yǔ)句
let visitor = {
ArrowFunctionExpression(path) {//定義需要轉(zhuǎn)換的節(jié)點(diǎn)
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() { ... } } 的簡(jiǎn)寫(xiě)形式立叛。
<font color=Chocolate>Path 是一個(gè)對(duì)象,它表示兩個(gè)節(jié)點(diǎn)之間的連接贡茅。</font>
解析步驟
- 定義需要轉(zhuǎn)換的節(jié)點(diǎn)
ArrowFunctionExpression(path) {
......
}
- 創(chuàng)建用來(lái)替換的節(jié)點(diǎn)
types.functionExpression(null, params, blockStatement, false, false)
- 在node節(jié)點(diǎn)上找到需要的參數(shù)
- replaceWith(替換)
寫(xiě)一個(gè)Babel插件
從一個(gè)接收了 babel 對(duì)象作為參數(shù)的 function 開(kāi)始秘蛇。
export default function(babel) {
// plugin contents
}
接著返回一個(gè)對(duì)象,其 visitor 屬性是這個(gè)插件的主要節(jié)點(diǎn)訪問(wèn)者顶考。
export default function({ types: t }) {
return {
visitor: {
// visitor contents
}
};
};
我們?nèi)粘R胍蕾嚨臅r(shí)候赁还,會(huì)將整個(gè)包引入,導(dǎo)致打包后的代碼太冗余驹沿,加入了許多不需要的模塊艘策,比如index.js三行代碼,打包后的文件大小就達(dá)到了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";
轉(zhuǎn)換為從而只引入兩個(gè)lodash模塊渊季,減少打包體積
import flatten from "lodash/flatten";
import join from "lodash/join";
實(shí)現(xiàn)步驟如下:
- 在項(xiàng)目下的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 {
// 對(duì)import轉(zhuǎn)碼
visitor:{
ImportDeclaration(path, _ref = { opts: {} }) {
const specifiers = path.node.specifiers;
const source = path.node.source;
// 只有l(wèi)ibraryName滿足才會(huì)轉(zhuǎn)碼
if (_ref.opts.library == source.value && (!t.isImportDefaultSpecifier(specifiers[0]))) { //_ref.opts是傳進(jìn)來(lái)的參數(shù)
var declarations = specifiers.map((specifier) => { //遍歷 uniq extend flatten cloneDeep
return t.ImportDeclaration( //創(chuàng)建importImportDeclaration節(jié)點(diǎn)
[t.importDefaultSpecifier(specifier.local)],
t.StringLiteral(`${source.value}/${specifier.local.name}`)
)
})
path.replaceWithMultiple(declarations)
}
}
}
};
}
- 修改<font color=Chocolate>webpack.prod.config.js</font>中babel-loader的配置項(xiàng)朋蔫,在plugins中添加自定義的插件名
rules: [{
test: /\.js$/,
loader: 'babel-loader',
options: {
presets: ["env",'stage-0'],
plugins: [
["extract", { "library":"lodash"}],
["transform-runtime", {}]
]
}
}]
注意:plugins 的插件使用順序是順序的,而 preset 則是逆序的却汉。所以上面的執(zhí)行方式是extract>transform-runtime>env>stage-0
- 運(yùn)行引入了自定義插件的webpack.config.js
打包文件現(xiàn)在為21.4KiB,明顯減小驯妄,自定義插件成功!~
插件文件目錄
YUAN-PLUGINS
|
| - node_modules
| |
| | - babel-plugins-extract
| |
| index.js
|
| - src
| | - index.js
|
| - webpack.config.js