写一个 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 的插件的做者, 是真的强!

相关文章
相关标签/搜索