Babel 是一個(gè)通用的多功能的 JavaScript 編譯器。
瀏覽器編譯你的js代碼蚤假,需要把js轉(zhuǎn)化成ast栏饮。2015年es6語法發(fā)布,但瀏覽器還普遍不支持es6語法磷仰。于是催生出babel模塊袍嬉,默認(rèn)支持js到ast的轉(zhuǎn)化,并通過修改ast灶平,將es6的特性代碼轉(zhuǎn)化為同效用的es5代碼伺通,同時(shí)也提供了ast的操作函數(shù)。
Babel操作流程:js代碼 -> 原AST -> babel處理 -> 修改后的AST -> 修改后的js代碼 -> 交給瀏覽器編譯
即: 解析(parse)逢享,轉(zhuǎn)換(transform)罐监,生成(generate)
@babel/core
從名稱就可以看出這是babel的核心包。首先介紹一下這個(gè)包瞒爬,這個(gè)包集成了上篇文章講述的@babel/parser弓柱,@babel/traverse,@babel/generator侧但,@babel/types這些包矢空。也就是說這個(gè)包具備解析(parse),轉(zhuǎn)換(traverse)禀横,生成代碼的能力屁药,而且還擴(kuò)展了其它功能。
npm install --save-dev @babel/cli @babel/core
AST解析示例
在線js轉(zhuǎn)換到AST工具:https://astexplorer.net/#/
let a = 1
{
"type": "Program",
"start": 0,
"end": 10,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 10,
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 9,
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "a"
},
"init": {
"type": "Literal",
"start": 8,
"end": 9,
"value": 1,
"raw": "1"
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
visitor(訪問者模式)
當(dāng)談及“進(jìn)入”一個(gè)節(jié)點(diǎn)時(shí)柏锄,實(shí)際上是說我們?cè)?strong>訪問他們酿箭,之所以使用這樣的術(shù)語是因?yàn)橛幸粋€(gè)訪問者模式(visitor)概念
上面AST小節(jié)示例有 “type”: “VariableDeclarator” 立莉,諸如此類的樹節(jié)點(diǎn)在訪問時(shí),就會(huì)進(jìn)入visitor對(duì)象聲明的對(duì)應(yīng)類型的成員方法七问。此時(shí)你訪問到的不是節(jié)點(diǎn)本身蜓耻,而是一個(gè)Path,所以可以追蹤樹的父節(jié)點(diǎn)等其它信息械巡。
常用寫法是取path.node刹淌,如上即取到type = VariableDeclarator 的對(duì)象
舉個(gè)??
const babel = require("@babel/core")
const code = `a+b+c`
const obj = babel.transformSync(code, {
plugins: [
function MyPlugin(babel) {
return {
visitor: {
Identifier(path) {
console.log('visiting====>',path.node.name)
},
}
}
}
]
});
console.log(obj.code);
#生成對(duì)應(yīng)的AST
{
"type": "Program",
"start": 0,
"end": 5,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 5,
"expression": {
"type": "BinaryExpression",
"start": 0,
"end": 5,
"left": {
"type": "BinaryExpression",
"start": 0,
"end": 3,
"left": {
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 2,
"end": 3,
"name": "b"
}
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "c"
}
}
}
],
"sourceType": "module"
}
#代碼執(zhí)行后的結(jié)果
visiting====> a
visiting====> b
visiting====> c
下邊修改一下代碼,改為從文件讀取源代碼,使用babel.transformFileSync直接讀取
舉個(gè)??
//file.js
a+b+c
//index.js
const babel = require("@babel/core")
const path = require("path")
const file = path.resolve(__dirname,'./file.js')
const obj = babel.transformFileAsync(file, {
plugins: [
function MyPlugin(babel) {
return {
visitor: {
Identifier(path) {
console.log('visiting====>',path.node.name)
},
}
}
}
]
});
console.log(obj.code);
從以上講述清楚的知道了babel插件是如何工作的讥耗。以代碼的角度分析有勾,其實(shí)就是babel/core提供的api有一個(gè)plugins配置屬性,支持傳入自定義的插件而已古程。
單獨(dú)抽離插件
在前端項(xiàng)目當(dāng)中蔼卡,babel插件都是以獨(dú)立模塊出現(xiàn)。這時(shí)候babel.config.js配置文件就派上用場(chǎng)了挣磨。transformFileSync第二個(gè)參數(shù)對(duì)象有一個(gè)babelrc屬性雇逞,默認(rèn)是true。這個(gè)屬性代表是否從項(xiàng)目根目錄讀取babelrc文件獲取presets和plugins配置茁裙。
這時(shí)候就已經(jīng)把babel插件單獨(dú)抽離出來了塘砸。如果你想發(fā)布到npm上,你只需要按照發(fā)布規(guī)范和步驟發(fā)布晤锥,然后安裝下來掉蔬,在babel.config.js配置就可以了。
接下來 我們編譯一段es6 class語法矾瘾,看看編譯后的結(jié)果:
code: 'class Person {\n' +
' constructor(name) {\n' +
' this.name = name;\n' +
' }\n' +
' say() {\n' +
' console.log(this.name);\n' +
' }\n' +
'}\n' +
'const person = new Person("張三");\n' +
'person.say();',
細(xì)心的小伙伴發(fā)現(xiàn)了女轿,編譯后的代碼沒有任何變化,我們之前也講過babel/core其實(shí)只是提供了編譯的流程壕翩。如果想要處理代碼蛉迹,還需要提供插件(在編譯的第二部transform)對(duì)節(jié)點(diǎn)進(jìn)行增刪改查,才可以修改節(jié)點(diǎn)生成最終想要的可執(zhí)行代碼戈泼。
這里如果需要處理es6及以上的語法婿禽,需要使用babel/preset-env預(yù)設(shè)包(就是插件的集合),安裝該包大猛,修改.babelrc配置
npm install --save-dev @babel/preset-env
module.exports = {
"presets": [
"@babel/preset-env"
],
"plugins":["./plugin.js"]
}
//編譯之后的代碼
code: '"use strict";\n' +
'\n' +
'function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }\n' +
'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n' +
'function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }\n' +
'function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }\n' +
'function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }\n' +
'function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }\n' +
'var Person = /*#__PURE__*/function () {\n' +
' function Person(name) {\n' +
' _classCallCheck(this, Person);\n' +
' this.name = name;\n' +
' }\n' +
' _createClass(Person, [{\n' +
' key: "say",\n' +
' value: function say() {\n' +
' console.log(this.name);\n' +
' }\n' +
' }]);\n' +
' return Person;\n' +
'}();\n' +
'var person = new Person("張三");\n' +
'person.say();',
map: null,
sourceType: 'script',
externalDependencies: Set(0) {}
}
由上圖可知這個(gè)表達(dá)式對(duì)應(yīng)CallExpression類型的節(jié)點(diǎn)扭倾,該節(jié)點(diǎn)有一個(gè)callee屬性對(duì)應(yīng)的是MemberExpression類型的節(jié)點(diǎn),MemberExpression節(jié)點(diǎn)又有一個(gè)object屬性為Identifier類型的節(jié)點(diǎn)挽绩,只有Identifier節(jié)點(diǎn)名字是console才可以判斷當(dāng)前節(jié)點(diǎn)是console類型的表達(dá)式膛壹。然后執(zhí)行path.remove方法就可以刪除當(dāng)前節(jié)點(diǎn)。
代碼如下
module.exports=function(babel){
return{
visitor:{
CallExpression(path){
if(path.node.callee&&babel.types.isIdentifier(path.node.callee.object,{name:'console'})){
path.remove()
}
}
}
}
}