动态

详情 返回 返回

偏愛console.log的你,肯定會覺得這個插件泰褲辣! - 动态 详情

前言

毋庸置疑,要説前端調試代碼用的最多的,肯定是console.log,雖然我現在 debugger 用的比較多,但對於生產環境、小程序真機調試,還是需要用到 log 來查看變量值,比如我下午遇到個場景:選擇完客户後返回頁面,根據條件判斷是否彈窗:

if (global.isXXX || !this.customerId || !this.skuList.length) return

// 到了這裏才會執行彈窗的邏輯

這個時候只能真機調試,看控制枱打印的值是怎樣的,但對於上面的條件,如果你這樣 log 的話,那控制枱只會顯示:

console.log(global.isXXX, !this.customerId, !this.skuList.length)
false false false

且如果參數比較多,你可能就沒法立即將 log 出的值對應到相應的變量,還得回去代碼裏面仔細比對。

還有一個,我之前遇到過一個項目裏一堆 log,同事為了方便看到 log 是在哪一行,就在 log 的地方加上代碼所在行數,但因為 log 那一刻已經硬編碼了,而代碼經常會添加或者刪除,這個時候行數就不對了:

比如你上面添加了一行,這裏的所有行數就都不對了

所以,我希望 console.log 的時候:

  1. 控制枱主動打印源碼所在行數
  2. 變量名要顯示出來,比如上面例子的 log 應該是 global.isXXX = false !this.customerId = false !this.skuList.length = false
  3. 可以的話,每個參數都有分隔符,不然多個參數看起來就有點不好分辨

即源碼不做任何修改:

而控制枱顯示所在行,且有變量名的時候添加變量名前綴,然後你可以指定分隔符,如換行符\n

因為之前有過 babel 插件的經驗,所以想着這次繼續通過寫一個 babel plugin 實現以上功能,所以也就有了babel-plugin-enhance-log,那究竟怎麼用?很簡單,下面 👇🏻 我給大家説説。

babel-plugin-enhance-log

老規矩,先安裝插件:

pnpm add babel-plugin-enhance-log -D
# or
yarn add babel-plugin-enhance-log -D
# or
npm i babel-plugin-enhance-log -D

然後在你的 babel.config.js 裏面添加插件:

module.exports = (api) => {
  return {
    plugins: [
    'enhance-log',
    ...
  ],
  }
}

看到了沒,就是這麼簡單,之後再重新啓動,去你的控制枱看看,小火箭咻咻咻為你刷起~

options

上面瞭解了基本用法後,這裏再給大家説下幾個參數,可以看下注釋,應該説是比較清楚的:

interface Options {
  /**
   * 打印的前綴提示,這樣方便快速找到log 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀
   * @example
   * console.log('line of 1 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀', ...)
   */
  preTip?: string
  /** 每個參數分隔符,默認空字符串,你也可以使用換行符\n,分號;逗號,甚至豬豬🐖都行~ */
  splitBy?: boolean
  /** 
   * 是否需要endLine
   * @example
   * console.log('line of 1 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀', ..., 'line of 10 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀')
   *  */
  endLine?: boolean
}

然後在插件第二個參數配置即可(這裏偷偷跟大家説下,通過/** @type {import('babel-plugin-enhance-log').Options} */可以給配置添加類型提示哦):

return {
    plugins: [
      ['enhance-log', enhanceLogOption],
      ],
      ...
  }

比如説,你不喜歡小 🚀,你喜歡豬豬 🐖,那可以配置 preTip 為 🐖🐖🐖🐖🐖🐖🐖🐖🐖🐖:

比如説,在參數較多的情況下,你希望 log 每個參數都換行,那可以配置 splitBy 為 \n

或者分隔符是;:

當然,你也可以隨意指定,比如用個狗頭🐶來分隔:

又比如説,有個 log 跨了多行,你希望 log 開始和結束的行數,中間是 log 實體,那可以將 endLine 設置為 true:

我們可以看到開始的行數是13,結束的行數是44,跟源碼一致

實現思路

上面通過多個例子跟大家介紹了各種玩法,不過,我相信還是有些小夥伴想知道怎麼實現的,那我這裏就大致説下實現思路:

老規格,還是通過babel-ast-explorer來查看

1.判斷到 console.log 的 ast,即 path 是 CallExpression 的,且 callee 是 console.log,那麼進入下一步

2.拿到 console.log 的 arguments,也就是 log 的參數

3.遍歷 path.node.arguments 每個參數

  • 字面量的,則無須添加變量名
  • 變量的,添加變量名前綴,如 a =
  • 如果需要分隔符,則根據傳入的分隔符插入到原始參數的後面

4.拿到 console.log 的開始行數,創建一個包含行數的 StringLiteral,同時加上 preTip,比如上面的 🚀🚀🚀🚀🚀🚀🚀,或者 🐖🐖🐖🐖🐖🐖🐖🐖🐖🐖,然後 unshift,放在第一個參數的位置

5.拿到 console.log 的結束行數,過程跟第 4 點類似,通過 push 放到最後一個參數的位置

6.在這過程中需要判斷到處理過的,下次進來就要跳過,防止重複添加

以下是源碼的實現過程,有興趣的可以看看:

import { declare } from '@babel/helper-plugin-utils'
import generater from '@babel/generator'
import type { StringLiteral } from '@babel/types'
import { stringLiteral } from '@babel/types'


const DEFAULT_PRE_TIP = '🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀'
const SKIP_KEY = '@@babel-plugin-enhance-logSkip'

function generateStrNode(str: string): StringLiteral & { skip: boolean } {
  const node = stringLiteral(str)
  // @ts-ignore
  node.skip = true
  // @ts-ignore
  return node
}

export default declare<Options>((babel, { preTip = DEFAULT_PRE_TIP, splitBy = '', endLine = false }) => {
  const { types: t } = babel
  const splitNode = generateStrNode(splitBy)
  return {
    name: 'enhance-log',
    visitor: {
      CallExpression(path) {
        const calleeCode = generater(path.node.callee).code
        if (calleeCode === 'console.log') {
          // add comment to skip if enter next time
          const { trailingComments } = path.node
          const shouldSkip = (trailingComments || []).some((item) => {
            return item.type === 'CommentBlock' && item.value === SKIP_KEY
          })
          if (shouldSkip)
            return

          t.addComment(path.node, 'trailing', SKIP_KEY)

          const nodeArguments = path.node.arguments
          for (let i = 0; i < nodeArguments.length; i++) {
            const argument = nodeArguments[i]
            // @ts-ignore
            if (argument.skip)
              continue
            if (!t.isLiteral(argument)) {
              if (t.isIdentifier(argument) && argument.name === 'undefined') {
                nodeArguments.splice(i + 1, 0, splitNode)
                continue
              }
              // @ts-ignore
              argument.skip = true
              const node = generateStrNode(`${generater(argument).code} =`)

              nodeArguments.splice(i, 0, node)
              nodeArguments.splice(i + 2, 0, splitNode)
            }
            else {
              nodeArguments.splice(i + 1, 0, splitNode)
            }
          }
          // the last needn't split
          if (nodeArguments[nodeArguments.length - 1] === splitNode)
            nodeArguments.pop()
          const { loc } = path.node
          if (loc) {
            const startLine = loc.start.line
            const startLineTipNode = t.stringLiteral(`line of ${startLine} ${preTip}:\n`)
            nodeArguments.unshift(startLineTipNode)
            if (endLine) {
              const endLine = loc.end.line
              const endLineTipNode = t.stringLiteral(`\nline of ${endLine} ${preTip}:\n`)
              nodeArguments.push(endLineTipNode)
            }
          }
        }
      },
    },
  }
})

對了,這裏有個問題是,我通過標記 path.node.skip = true 來跳過,但是還是會多次進入:

if (path.node.skip) return
path.node.skip = true

所以最終只能通過尾部添加註釋的方式來避免多次進入:

有知道怎麼解決的大佬還請提示一下,萬分感謝~

總結

國際慣例,我們來總結一下,對於生產環境或真機調試,或者對於一些偏愛 console.log 的小夥伴,我們為了更快在控制枱找到 log 的變量,通常會添加 log 函數,參數變量名,但前者一旦代碼位置更改,打印的位置就跟源碼不一致,後者又得重複寫每個參數變量名的字符串,顯得相當的麻煩。

為了更方便地使用 log,我們實現了個 babel 插件,功能包括:

  1. 自動打印行數
  2. 可以根據個人喜好加上 preTip,比如刷火箭 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀,或者可愛的小豬豬 🐖🐖🐖🐖🐖🐖🐖🐖🐖🐖
  3. 同時,對於有變量名的情況,可以加上變量名前綴,比如 const a = 1, console.log(a) => console.log('a = ', a)
  4. 還有,我們可以通過配置 splitBy、endLine 來自主選擇任意分隔符、是否打印結束行等功能

最後

不知道大家有沒有在追不良人,我是從高三追到現在。今天是週四,不良人第六季也接近尾聲了,那就謹以此文來紀念不良人第六季的完結吧~

好了,再説一句,如果你是個偏愛 console.log 的前端 er,那請你喊出:泰褲辣(逃~)

Add a new 评论

Some HTML is okay.