参考文档:
AST详解与运用:https://zhuanlan.zhihu.com/p/266697614https://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插件
事例:在生产环境下去除特定的代码

  1. // 在产环境下去除
  2. if (DEBUG) {
  3. consta = 10;
  4. constb = 20;
  5. console.log(a + b);
  6. }

第一步:创建一个plugin,创建一个index.js文件

  1. // 写法参考:https://babel.docschina.org/docs/en/,可插件化
  2. module.exports = ({
  3. types: t
  4. }) => {
  5. return {
  6. visitor: {
  7. // 处理对于代码
  8. Identifier(path) {
  9. const isDebug = path.node.name === "DEBUG";
  10. // console.log(path.node.name,'path.node.name===')
  11. const parentIsIf = t.isIfStatement(path.parentPath);
  12. if (isDebug && parentIsIf) {
  13. const stringNode = t.stringLiteral("DEBUG");
  14. path.replaceWith(stringNode);
  15. }
  16. },
  17. StringLiteral(path, state) {
  18. console.log(state.opts, "state=======");
  19. const isDebug = path.node.value === "DEBUG";
  20. const parentIsIf = t.isIfStatement(path.parentPath);
  21. if (isDebug && parentIsIf) {
  22. // 生产环境移除
  23. if (state.opts.isRemove) {
  24. path.parentPath.remove();
  25. }
  26. }
  27. },
  28. // remove- console.log
  29. // https://github.com/babel/minify/blob/master/packages/babel-plugin-transform-remove-console/src/index.js
  30. // 自己的
  31. CallExpression(path, state) {
  32. // console.log(path.node, "222222");
  33. // console.log(path.node.callee.object.name, "object");
  34. // console.log(path.node.callee.property.name, "3333333");
  35. // const callee = path.get("callee");
  36. // console.log(callee, "=====");
  37. if (
  38. path.node.callee.object.name === "console" &&
  39. path.node.callee.property.name === "log"
  40. ) {
  41. path.remove()
  42. }
  43. },
  44. // 第三方
  45. // CallExpression(path, state) {
  46. // const callee = path.get("callee");
  47. // if (!callee.isMemberExpression()) return;
  48. // if (isIncludedConsole(callee, state.opts.exclude)) {
  49. // // console.log()
  50. // if (path.parentPath.isExpressionStatement()) {
  51. // path.remove();
  52. // } else {
  53. // path.replaceWith(createVoid0());
  54. // }
  55. // } else if (isIncludedConsoleBind(callee, state.opts.exclude)) {
  56. // // console.log.bind()
  57. // path.replaceWith(createNoop());
  58. // }
  59. // },
  60. }
  61. };
  62. };
  63. // 第三方
  64. function isGlobalConsoleId(id) {
  65. console.log(id, '=========11111111111')
  66. const name = "console";
  67. // https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
  68. return (
  69. id.isIdentifier({
  70. name
  71. }) &&
  72. !id.scope.getBinding(name) &&
  73. id.scope.hasGlobal(name)
  74. );
  75. }
  76. function isExcluded(property, excludeArray) {
  77. return (
  78. excludeArray && excludeArray.some(name => property.isIdentifier({
  79. name
  80. }))
  81. );
  82. }
  83. function isIncludedConsole(memberExpr, excludeArray) {
  84. const object = memberExpr.get("object");
  85. const property = memberExpr.get("property");
  86. if (isExcluded(property, excludeArray)) return false;
  87. if (isGlobalConsoleId(object)) return true;
  88. return (
  89. isGlobalConsoleId(object.get("object")) &&
  90. (property.isIdentifier({
  91. name: "call"
  92. }) ||
  93. property.isIdentifier({
  94. name: "apply"
  95. }))
  96. );
  97. }
  98. function isIncludedConsoleBind(memberExpr, excludeArray) {
  99. const object = memberExpr.get("object");
  100. if (!object.isMemberExpression()) return false;
  101. if (isExcluded(object.get("property"), excludeArray)) return false;
  102. return (
  103. isGlobalConsoleId(object.get("object")) &&
  104. memberExpr.get("property").isIdentifier({
  105. name: "bind"
  106. })
  107. );
  108. }
  109. function createNoop() {
  110. return t.functionExpression(null, [], t.blockStatement([]));
  111. }
  112. function createVoid0() {
  113. return t.unaryExpression("void", t.numericLiteral(0));
  114. }

第二步,测试文件,创建一个test.js文件

  1. const { transformSync } = require("@babel/core");
  2. const parse = require("@babel/parser");
  3. const babelConfig = {
  4. plugins: [
  5. [
  6. "./index.js",
  7. { "exclude": ["log"] }
  8. ]
  9. ]
  10. };
  11. const code = `function add () {
  12. console.log('aaaaaa')
  13. console.err('bbbbb')
  14. console.warn('bbbbb')
  15. const a = '12345'
  16. if(DEBUG){
  17. const title = '需要去除的代码'
  18. }
  19. };
  20. const b = 'bbbbb'
  21. console.log(b)
  22. const inc = () => {
  23. const c = 111
  24. console.log(c)
  25. }`;
  26. const ast = parse.parse(code)
  27. console.log(parse.parse(code),'ast===')
  28. // const outPut = transformSync(code, babelConfig);
  29. // console.log(outPut, '78798');
  30. const outPut = transformSync(code, babelConfig);
  31. console.log(outPut,'outPut')

第三部引入插件:创建一个babel.config.js文件

  1. module.exports = {
  2. presets: [
  3. [
  4. "@babel/preset-env",
  5. {
  6. targets: {
  7. node: "current"
  8. }
  9. }
  10. ],
  11. ],
  12. plugins: [
  13. ["./test-babel/index.js"]
  14. ]
  15. };