参考文档:
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.mdreturn (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 = 111console.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"]]};
