参考文档:
AST详解与运用:https://zhuanlan.zhihu.com/p/266697614,https://zhuanlan.zhihu.com/p/486548159
AST笔记(技巧,插件):https://www.jianshu.com/p/a3857fa5c899
babel文档:https://babel.docschina.org/docs/en/
babel插件开发手册:https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
revoe-console.log插件:https://github.com/babel/minify/blob/master/packages/babel-plugin-transform-remove-console/src/index.js
ast语法树:https://astexplorer.net/
Babel 是一个 JavaScript compiler,Babel 是一个工具链,主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码
Babel 可以做的主要事情:
- 转换语法
- Polyfill 目标环境中缺少的功能(通过如 core-js 的第三方 polyfill)
- 源代码转换(codemods)
AST:抽象语法树是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,之所以说是抽象的,抽象表示把js代码进行了结构化的转化,转化为一种数据结构。这种数据结构其实就是一个大的json对象
AST的用途:
AST的作用不仅仅是用来在JavaScript引擎的编译上,我们在实际的开发过程中也是经常使用的,比如我们常用的babel插件将 ES6转化成ES5、使用 UglifyJS来压缩代码 、css预处理器、开发WebPack插件、Vue-cli前端自动化工具等等,这些底层原理都是基于AST来实现的,AST能力十分强大, 能够帮助开发者理解JavaScript这门语言的精髓
如何开发一个自己的babel插件
事例:在生产环境下去除特定的代码
// 在产环境下去除
if (DEBUG) {
consta = 10;
constb = 20;
console.log(a + b);
}
第一步:创建一个plugin,创建一个index.js文件
// 写法参考:https://babel.docschina.org/docs/en/,可插件化
module.exports = ({
types: t
}) => {
return {
visitor: {
// 处理对于代码
Identifier(path) {
const isDebug = path.node.name === "DEBUG";
// console.log(path.node.name,'path.node.name===')
const parentIsIf = t.isIfStatement(path.parentPath);
if (isDebug && parentIsIf) {
const stringNode = t.stringLiteral("DEBUG");
path.replaceWith(stringNode);
}
},
StringLiteral(path, state) {
console.log(state.opts, "state=======");
const isDebug = path.node.value === "DEBUG";
const parentIsIf = t.isIfStatement(path.parentPath);
if (isDebug && parentIsIf) {
// 生产环境移除
if (state.opts.isRemove) {
path.parentPath.remove();
}
}
},
// remove- console.log
// https://github.com/babel/minify/blob/master/packages/babel-plugin-transform-remove-console/src/index.js
// 自己的
CallExpression(path, state) {
// console.log(path.node, "222222");
// console.log(path.node.callee.object.name, "object");
// console.log(path.node.callee.property.name, "3333333");
// const callee = path.get("callee");
// console.log(callee, "=====");
if (
path.node.callee.object.name === "console" &&
path.node.callee.property.name === "log"
) {
path.remove()
}
},
// 第三方
// CallExpression(path, state) {
// const callee = path.get("callee");
// if (!callee.isMemberExpression()) return;
// if (isIncludedConsole(callee, state.opts.exclude)) {
// // console.log()
// if (path.parentPath.isExpressionStatement()) {
// path.remove();
// } else {
// path.replaceWith(createVoid0());
// }
// } else if (isIncludedConsoleBind(callee, state.opts.exclude)) {
// // console.log.bind()
// path.replaceWith(createNoop());
// }
// },
}
};
};
// 第三方
function isGlobalConsoleId(id) {
console.log(id, '=========11111111111')
const name = "console";
// https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
return (
id.isIdentifier({
name
}) &&
!id.scope.getBinding(name) &&
id.scope.hasGlobal(name)
);
}
function isExcluded(property, excludeArray) {
return (
excludeArray && excludeArray.some(name => property.isIdentifier({
name
}))
);
}
function isIncludedConsole(memberExpr, excludeArray) {
const object = memberExpr.get("object");
const property = memberExpr.get("property");
if (isExcluded(property, excludeArray)) return false;
if (isGlobalConsoleId(object)) return true;
return (
isGlobalConsoleId(object.get("object")) &&
(property.isIdentifier({
name: "call"
}) ||
property.isIdentifier({
name: "apply"
}))
);
}
function isIncludedConsoleBind(memberExpr, excludeArray) {
const object = memberExpr.get("object");
if (!object.isMemberExpression()) return false;
if (isExcluded(object.get("property"), excludeArray)) return false;
return (
isGlobalConsoleId(object.get("object")) &&
memberExpr.get("property").isIdentifier({
name: "bind"
})
);
}
function createNoop() {
return t.functionExpression(null, [], t.blockStatement([]));
}
function createVoid0() {
return t.unaryExpression("void", t.numericLiteral(0));
}
第二步,测试文件,创建一个test.js文件
const { transformSync } = require("@babel/core");
const parse = require("@babel/parser");
const babelConfig = {
plugins: [
[
"./index.js",
{ "exclude": ["log"] }
]
]
};
const code = `function add () {
console.log('aaaaaa')
console.err('bbbbb')
console.warn('bbbbb')
const a = '12345'
if(DEBUG){
const title = '需要去除的代码'
}
};
const b = 'bbbbb'
console.log(b)
const inc = () => {
const c = 111
console.log(c)
}`;
const ast = parse.parse(code)
console.log(parse.parse(code),'ast===')
// const outPut = transformSync(code, babelConfig);
// console.log(outPut, '78798');
const outPut = transformSync(code, babelConfig);
console.log(outPut,'outPut')
第三部引入插件:创建一个babel.config.js文件
module.exports = {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current"
}
}
],
],
plugins: [
["./test-babel/index.js"]
]
};