寫一個 babel 插件

1. 前言

babel 現在已成爲每個現代前端項目的標配, 有了它,咱們能夠肆無忌憚的使用 stage-xxxx 的語法, 加強咱們的生產力前端

咱們經過寫一個 支持 arr[-1] 的 babel插件 來加深理解node

https://user-gold-cdn.xitu.io/2019/4/15/16a20348ca64b3ff?w=720&h=818&f=png&s=114216

2. 須要實現的功能

如今咱們有以下的一個數組 arr, 咱們想獲取數組的最後一個下標,因爲 js 不支持 [-1] 的操做,因此咱們須要轉換成 arr[arr.length - 1]git

const arr = [1,2,3]
const value = arr[-1]

// ↓ 想要轉換成

const value = arr[arr.length - 1]
複製代碼

換做之前的我, 反手就是粗暴的 正則解析 去替換, 固然正規一點仍是老老實實用 AST(Abstract Syntax Tree)github

3. 查看抽象語法樹

站在巨人的肩膀上 咱們能夠 使用 在線的 AST 生成工具 astexplorer.net/ 能夠看到咱們剛纔寫的兩行代碼對應的 語法樹express

https://user-gold-cdn.xitu.io/2019/4/15/16a20348cec961c7?w=1122&h=1302&f=png&s=187072

固然你也可使用 @babel/core 來生成 語法樹 獲得的結果是一致的數組

import babel from '@babel/core'

const code = ` const arr = [1,2,3] const value = arr[-1] `
babel.transform(code,{},(result)=>{
    console.log(result.ast)
})
複製代碼

4. 編寫插件

關於 babel 插件的詳細介紹, 能夠參考 這篇文章 Babel 插件有啥用?bash

const babel = require('@babel/core')
const t = require('@babel/types')

const visitor = {
  MemberExpression(path) {
    const node = path.node
  }
}
  
module.exports = (babel) => {
  return {
    visitor
  }
}
複製代碼

@babel/types 主要幫助咱們判斷 當前節點是什麼類型, 十分強大, 根據觀察生成 的 AST, arr[-1] 是一個 MemberExpression 節點, 因此咱們只須要 將對應的節點 替換掉便可babel

想要將 arr[-1] 轉換成 arr[arr.length - 1], 咱們須要知道2點工具

  1. 數組的名字 => arr
  2. 數組的下標而且是一個數字 => -1

獲取 數組的名字測試

if (node.object && t.isIdentifier(node.object)) {
      const arrName = node.object.name
    }
複製代碼

獲取 數組的下標, 應該知足 是一個表達式, 而且參數是一個數字

let arrIndex
let operator
if(node.property && t.isUnaryExpression(node.property)){
  if(
    node.property.prefix && 
    node.property.operator && 
    node.property.argument && 
    t.isNumericLiteral(node.property.argument)
  ) {
    arrIndex = node.property.argument.value
    operator = node.property.operator
  }
}
複製代碼

最後拿到了咱們想要的參數, 組裝一下, 替換當前節點便可

const result = `${arrName}[${arrName}.length ${operator} ${arrIndex}]`
path.replaceWithSourceString(result)
複製代碼

5. 測試

import { transform } from '@babel/core'
import babelArrayLastValuePlugin from '../src/index'

describe('test array last value babel plugin', () => {
  beforeEach(() => {
    String.prototype.trimAll = function () {
      return this.replace(/\s/g, "")
    }
  })
  it('test expression', () => {
    const _code = `
      const arr = [1,2,3];
      const v = arr[-1];
    `
    const { code } = transform(_code, {
      plugins: [babelArrayLastValuePlugin]
    })

    expect(code.trimAll()).toEqual(`const arr = [1,2,3];const v = arr[arr.length - 1];`.trimAll())
  })
複製代碼

6. 特殊的場景

在實際場景中 可能還要直接賦值的狀況

arr[-1] = 4
複製代碼

或者使用 lodashget 的狀況

get(arr,"[0]")
複製代碼

這樣對應的 AST 會有變化, 還須要處理對應的節點

7. 使用

yarn add babel-plugin-array-last-index
複製代碼
// .babelrc
{
  "plugins": [
    "array-last-index"
  ],
}

複製代碼

8. 結語

GITHUB 地址

這是我第一次嘗試寫一個玩具插件,雖然實際做用不大, 不過仍是學到很多, 也由衷的佩服那些 寫 正兒八經 babel 的插件的做者, 是真的強!

相關文章
相關標籤/搜索