sunnyxujian's blog sunnyxujian's blog
首页
  • JavaScript
  • CSS
  • HTML和浏览器
  • Vue
  • Vue3
  • React
  • 基础知识
  • webpack
  • rollup
  • vite
  • 其他
  • Nodejs
  • 网络
  • TypeScript
  • 架构设计
  • 性能优化
  • 数据结构和算法
  • JavaScript教程 笔记
  • ES6 教程 笔记
  • TypeScript 从零实现 axios
  • Git学习 笔记
  • 渲染器原理
  • TypeScript 笔记
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网站
  • 资源
  • Vue资源
  • 友情链接
  • 分类
  • 标签
  • 归档
  • 友链
  • 关于
GitHub (opens new window)

xujian

劝君惜取少年时。
首页
  • JavaScript
  • CSS
  • HTML和浏览器
  • Vue
  • Vue3
  • React
  • 基础知识
  • webpack
  • rollup
  • vite
  • 其他
  • Nodejs
  • 网络
  • TypeScript
  • 架构设计
  • 性能优化
  • 数据结构和算法
  • JavaScript教程 笔记
  • ES6 教程 笔记
  • TypeScript 从零实现 axios
  • Git学习 笔记
  • 渲染器原理
  • TypeScript 笔记
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网站
  • 资源
  • Vue资源
  • 友情链接
  • 分类
  • 标签
  • 归档
  • 友链
  • 关于
GitHub (opens new window)
  • 基础知识

  • webpack

  • rollup

  • vite

  • 其他

    • 手写babel插件优化打印体验
      • Git subtree
      • npm package.json属性详解
    • 工程化
    • 其他
    xujian
    2022-03-01

    手写babel插件优化打印体验

    image.png

    # 场景

    在写代码的时候时常有个问题困扰着我,在打印某个变量时,经常会忘记之前打印的是哪个变量,在同一段代码里面打印多个变量尤为明显。经常会一脸懵逼,咦,我刚刚要打印的变量是第几个来着?所以一版会在前面加个标识例如 :console.log('someObject ==>', someObject),这样在控制台就会直观一点哪些值是哪些变量的。

    # 思考

    每次都要在变量前面加一个标识感觉好麻烦啊,本着能偷懒就偷懒的原则,有没有什么方法能够自动帮我加上某个变量的标志,这样每次就不用自己手写了。这个应该不是很难,只需在编译的的时候做点“手脚”应该就行,接下来就实现的过程。

    # 实现

    我们的项目是webpack打包的所以编写个loader,在loader里面做正则字符串替换就行,例如吧字符串的console.log(this.someObject)替换成 console.log('this.someObject ==>', this.someObject),就可以实现类似的效果。但是面对复杂的情况,loader处理起复杂的情况好像有点不称手,例如变量keythis.object[key],多个变量连续打印,做起来不太好用。所以写一个babel-plugin在抽象语法树上做文章或许是更好的选择,接下来就是实现过程。

    # babel

    我们都知 Babel (opens new window) 能够转译 ECMAScript 2015+ 的代码,使它在旧的浏览器或者环境中也能够运行,它的工作流程分为三个部分:

    1. Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点 (opens new window)。
    2. Transform(转换) 对抽象语法树进行转换。
    3. Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码。

    image.png所以只要操作 抽象语法树 上的节点就可以实现想要的效果。

    babel插件机制是通过访问者模式实现的:

    • 访问者模式 Visitor 对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同
    • Visitor 的对象定义了用于 AST 中获取具体节点的方法
    • Visitor 上挂载以节点 type 命名的方法,当遍历 AST 的时候,如果匹配上 type,就会执行对应的方法

    # 实现方法

    所以通过查看AST的type类型,就知道对应的方法名,在里面改写AST语法树就可以完成想要的效果,我们可以通过https://astexplorer.net/ (opens new window)这个网站看到代码的AST语法树,然后安装Babel提供的babel-types (opens new window)工具库操作抽象语法树(AST)的节点。 例如console.log(something)的AST是这样的: image.png 从抽象语法树上可以看到,console.log()的type是CallExpression,然后callee的类型是MemberExpression,它的成员分别是console和log,这个方法调用的入参就是arguments数组, 所以只要在arguments插入对应的字符串节点就能实现我想要的效果。

    # 代码

    // babel-plugin-log
    
    // 用来生成或者判断节点的AST语法树的节点
    const types = require('@babel/types');
    
    // 递归解析表达式获取变量字符串
    function getCallString(obj, name = []) {
      if (obj.object) {
        name = getCallString(obj.object, name)
      }
      let realName = ''
      if (obj.type === 'ThisExpression') {
        realName = 'this'
      }
      if (obj.type === 'Identifier') {
        realName = obj.name
      }
      if (obj.type === 'MemberExpression') {
        realName = obj.property.name || obj.property.value
      }
      if (realName.indexOf('this') > -1) {
        realName = 'this'
      }
      name = name.concat(realName)
      return name
    }
    
    // 将字符串插入到表达式前面
    function disposeArguments(args = []) {
      const newArgs = [] ;
      for (let i = 0; i < args.length; i++) {
        const item = args[i];
        if (item.type === 'StringLiteral') {
          newArgs.push(item)
        } else {
          const callString = getCallString(item, []).join('.')
          const notExit = newArgs.some(item => item.type === 'StringLiteral' && item.value.indexOf(callString) > -1)
          if (callString && !notExit) {
            // 插入遍历字符串节点
            const stringLiteral = types.stringLiteral(callString + ' ===>')
            newArgs.push(stringLiteral)
          }
          newArgs.push(item)
          if (callString && !notExit) {
            // 多个节点增加换行符
            newArgs.push(types.stringLiteral('\n'))
          }
        }
      }
      return newArgs
    }
    
    // babel plugin 访问器对象
    const visitor = {
      CallExpression(nodePath, state) {
        // 当前节点
        const { node } = nodePath;
        // 判断方法调用节点
        if (types.isMemberExpression(node.callee)) {
          if (node.callee.object.name === 'console') {
            // 判断console.xxx()方法
            if (['log', 'info', 'warn', 'error', 'debug'].includes(node.callee.property.name)) {
              // 处理抽象语法树
              node.arguments = disposeArguments(node.arguments);
            }
          }
        }
      }
    }
    
    module.exports = function () {
      return {
        visitor
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75

    在babel.config.js里或者 webpack的babel-loader里面配置上这个插件:

    // babel.config.js
    const path = require('path');
    
    module.exports = {
      // ...
      plugins:[
        [path.resolve(__dirname, 'babel-plugin-log.js')]
      ]
      // ...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    这是原来的源代码:

    image.png

    经过插件后打包编译后就是这样的效果:

    image.png

    # 后记

    1. 可以用process.env.xxx === 'development' ? ['babel-plugin-log'] : []来规避生产环境打包的话使用到了这个插件:
    2. 还可以用 log(...)代替console.log(...),然后在这个插件里面补全前面console.的抽象语法树,这样能少写几个字母...
    3. 用logRed(...)这样的语法,在插件里面补全对应的语法树信息,让你的log在控制台显示出红色等等...

    本文代码已发布到npm和github:https://www.npmjs.com/package/babel-plugin-better-log (opens new window)

    编辑 (opens new window)
    #babel#编译
    上次更新: 2022/05/23, 15:08:15
    vite核心原理
    Git subtree

    ← vite核心原理 Git subtree→

    最近更新
    01
    😍Vue3和React都在用的函数式编程,学起来~
    02-03
    02
    链表
    11-24
    03
    基础类型
    11-18
    更多文章>
    Theme by Vdoing | Copyright © 2020-2023 xujian | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式