1.0.1 • Published 4 years ago

less-import-babel-plugin v1.0.1

Weekly downloads
2
License
ISC
Repository
github
Last release
4 years ago

less-import

痛点: 当我们导入lodash中指定的工具函数时 会将整个lodash打包进来

import {flattenDeep, chunk} from 'lodash'

换成按需引入的写法 但是这样写有些麻烦 我们想由上面写法 自动分解为下面写法 所以我们就编写一个babel插件 less-import

该 babel 插件会将import {flattenDeep, chunk} from 'lodash' 转化为下面这种写法:

import flattenDeep from 'lodash/flattenDeep'
import chunk from 'lodash/chunk'

使用

npm install less-import-babel-plugin --save

配置.babelrc

{
    "presets": [
        "env",
        "stage-0"
    ],
    "plugins": [
        [
            "less-import-babel-plugin", // 配置插件
            {
                "library": "lodash" // 指定处理的库
            }
        ]
    ]
}

原理解析

@babel/parser

babelParser 把js解析为 ast tree,然后我们对 tree 进行修改

const script = babelParser.parse(sfc.script.content, {
  sourceType: 'module',
});

可以在线 ast 转换 javascript 的网站 https://astexplorer.net/

@babel/trasform

当我们需要对 ast 改造的时候的,肯定会涉及到遍历。

主角是@babel/traverse,@babel/types和@babel/template是辅助工具

traverse 支持两个参数,第一个是 ast 树,第二个是 visitor

visitor

  • visitor 是用于 AST 遍历的跨语言的模式。
  • 就是一个对象,会在遇到合适类型节点时被执行

具体的语法:

const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default

const code = `function mirror(something) {
  return something
}`
const ast = parser.parse(code, {
  sourceType: 'module',
})
const visitor = {
  Identifier (path) {
    console.log(path.node.name)
  }
}
traverse(ast, visitor)

path

  • path是对当前访问的node的一层包装

  • 使用path.node可以访问到当前的节点,使用path.parent可以访问到父节点,这里列出了path所包含的内容

  • path中还提供了一系列的工具函数,例如traverse(在当前path下执行递归), remove(删除当前节点), replaceWith(替换当前节点)等等。

@babel/types

  • 包含了判断,新建以及变换 AST 节点的方法

比如说判断 isIdentifier

const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const t = require('@babel/types')

const code = `function mirror(something) {
  return something
}`
const ast = parser.parse(code, {
  sourceType: 'module',
})
const visitor = {
  enter(path) {
    if (t.isIdentifier(path.node)) {
      console.log('Identifier!')
    }
  }
}
traverse(ast, visitor)

比如说,生成:

const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const t = require('@babel/types')

const code = `function mirror(something) {
  return something
}`
const ast = parser.parse(code, {
  sourceType: 'module',
})
const strNode = t.stringLiteral('mirror')
const visitor = {
  ReturnStatement (path) {
    path.traverse({
      Identifier(cpath){
        cpath.replaceWith(strNode)
      }
    })
  }
}
traverse(ast, visitor)
const transformedCode = generate(ast).code
console.log(transformedCode)

使用@babel/type创建一些简单节点会很容易,但是如果是大段代码的话就会变得困难了,这个时候我们可以使用@babel/template。

const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const template = require('@babel/template').default
const t = require('@babel/types')

const code = `function mirror(something) {
  return something
}`
const ast = parser.parse(code, {
  sourceType: 'module',
})
const visitor = {
  FunctionDeclaration(path) {
    // 在这里声明了一个模板,比用@babel/types去生成方便很多
    const temp = template(`
      if(something) {
        NORMAL_RETURN
      } else {
        return 'nothing'
      }
    `)
    const
 = path.node.body.body[0]
    const tempAst = temp({
      NORMAL_RETURN: returnNode
    })
    path.node.body.body[0] = tempAst
  }
}
traverse(ast, visitor)
const transformedCode = generate(ast).code
console.log(transformedCode)