Babel 是一個通用的多功能的 JavaScript 編譯器。

瀏覽器編譯你的js代碼,需要把js轉(zhuǎn)化成ast。2015年es6語法發(fā)布,但瀏覽器還普遍不支持es6語法。于是催生出babel模塊,默認(rèn)支持js到ast的轉(zhuǎn)化,并通過修改ast,將es6的特性代碼轉(zhuǎn)化為同效用的es5代碼,同時也提供了ast的操作函數(shù)。
Babel操作流程:js代碼 -> 原AST -> babel處理 -> 修改后的AST -> 修改后的js代碼 -> 交給瀏覽器編譯
即: 解析(parse),轉(zhuǎn)換(transform),生成(generate)
@babel/core
從名稱就可以看出這是babel的核心包。首先介紹一下這個包,這個包集成了上篇文章講述的@babel/parser,@babel/traverse,@babel/generator,@babel/types這些包。也就是說這個包具備解析(parse),轉(zhuǎn)換(traverse),生成代碼的能力,而且還擴展了其它功能。
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)談及“進入”一個節(jié)點時,實際上是說我們在訪問他們,之所以使用這樣的術(shù)語是因為有一個訪問者模式(visitor)概念
上面AST小節(jié)示例有 “type”: “VariableDeclarator” ,諸如此類的樹節(jié)點在訪問時,就會進入visitor對象聲明的對應(yīng)類型的成員方法。此時你訪問到的不是節(jié)點本身,而是一個Path,所以可以追蹤樹的父節(jié)點等其它信息。
常用寫法是取path.node,如上即取到type = VariableDeclarator 的對象
舉個??
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);
#生成對應(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直接讀取
舉個??
//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插件是如何工作的。以代碼的角度分析,其實就是babel/core提供的api有一個plugins配置屬性,支持傳入自定義的插件而已。
單獨抽離插件
在前端項目當(dāng)中,babel插件都是以獨立模塊出現(xiàn)。這時候babel.config.js配置文件就派上用場了。transformFileSync第二個參數(shù)對象有一個babelrc屬性,默認(rèn)是true。這個屬性代表是否從項目根目錄讀取babelrc文件獲取presets和plugins配置。
這時候就已經(jīng)把babel插件單獨抽離出來了。如果你想發(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其實只是提供了編譯的流程。如果想要處理代碼,還需要提供插件(在編譯的第二部transform)對節(jié)點進行增刪改查,才可以修改節(jié)點生成最終想要的可執(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) {}
}
由上圖可知這個表達(dá)式對應(yīng)CallExpression類型的節(jié)點,該節(jié)點有一個callee屬性對應(yīng)的是MemberExpression類型的節(jié)點,MemberExpression節(jié)點又有一個object屬性為Identifier類型的節(jié)點,只有Identifier節(jié)點名字是console才可以判斷當(dāng)前節(jié)點是console類型的表達(dá)式。然后執(zhí)行path.remove方法就可以刪除當(dāng)前節(jié)點。
代碼如下
module.exports=function(babel){
return{
visitor:{
CallExpression(path){
if(path.node.callee&&babel.types.isIdentifier(path.node.callee.object,{name:'console'})){
path.remove()
}
}
}
}
}