babel插件开发

babel插件开发
简介
babel本身是一个将JavaScriptg高版本的新语法编译成低版本语法以提升兼容性的编译器,babel除了自身的兼容编译以外也提供了接口允许用户编写插件扩展功能,在这个基础上我们可以利用babel提供的api实现代码检查、代码生成、自定义语法等功能。

babel是如何工作的

babel对源代码的处理主要有解析、转换、生成 三个阶段,这里简单聊一下转换功能的使用

编写简单的babel插件
@babel/core中已包含编写插件所需要的基础工具,也可以单独安装一下几个包单独处理

// 处理源代码的解析生成ast,内置了多种解析插件
yarn add -D @babel/parser
// 遍历ast进行自定义处理
yarn add -D @babel/traverse
// 生成代码
yarn add -D @babel/generator
// ast节点类型,节点创建
yarn add -D @babel/types
// 生成指向源代码的错误信息
yarn add -D @babel/code-frame
1
2
3
4
5
6
7
8
9
10
导入依赖包

import { parse } from '@babel/parser'
import traverse, { NodePath } from '@babel/traverse'
import {isImportDefaultSpecifier, classMethod, blockStatement, identifier, isClassMethod, isClassProperty, Identifier, expressionStatement, ClassMethod} from '@babel/types'
import generate from '@babel/generator'
import codeFrame from '@babel/code-frame'
1
2
3
4
5
准备一段代码片段


let codeText = `
import fs from 'fs'

@testDecorators()
export class Index {
a: number
private b: number = 1
constructor (a: number) {
this.a = a
}
private sum (): number {
return this.a + this.b
}

getB (): number {
return this.b
}

getSum (): number {
return this.sum()
}
}
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
解析源代码生成ast,这里sourceType设置为了module以支持import,export等语法解析,并添加了内置的typescript、decorators等插件以支持相应的解析

const astTree = parse(codeText, {
sourceType: 'module',
plugins: [
'typescript',
['decorators', { decoratorsBeforeExport: true }],
'classProperties',
'classPrivateProperties'
]
})
1
2
3
4
5
6
7
8
9
示例:分析代码给出提示
遍历ast检查包导入时是否使用了默认导入模式,如果使用了该模式则打印一个带语法高亮的源代码信息标注异常位置

traverse(astTree, {
ImportDeclaration: (path) => {
path.node.specifiers.forEach((specifier) => {
if (isImportDefaultSpecifier(specifier)) {
console.log('禁止使用默认导入')
const errorMsg = codeFrame(testText, path.node.loc.start.line, specifier.local.loc.end.column, {
highlightCode: true
})
console.log(errorMsg)
}
})
console.log(path.node.source.value)
}
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如上面准备的代码片段则会输出以下提示

禁止使用默认导入
> 1 | import fs from 'fs'
| ^
2 |
3 | @testDecorators()
4 | export class Index {
1
2
3
4
5
6
示例:处理成员方法绑定
javascript的class有别于java等语言,this的指向可以在运行时改变,如果不希望调用方改变this的指向,这需要在构造函数为成员方法绑定this,我们可以使用使用traverse遍历ast,找到构造方法并在内部添加代码处理,或者可以生成构造方法并添加对应代码处理

traverse(ast, {
ClassBody (path) {
let isExistConstructor = false
let propertys = []
let methods = []
// 判断是否存在构造方法,并收集成员方法列表
path.node.body.forEach((node) => {
if (isClassMethod(node)) {
if (node.kind === 'constructor') {
isExistConstructor = true
} else {
methods.push((node.key as Identifier).name)
}
} else if (isClassProperty(node)) {
propertys.push((node.key as Identifier).name)
}
})

// 在构造方法中添加绑定代码
path.get('body').forEach((subNode) => {
if (isClassMethod(subNode)) {
if ((subNode.node as ClassMethod).kind === 'constructor') {
isExistConstructor = true
const currentNode = (subNode as NodePath<ClassMethod>)
let body = currentNode.get('body')
body.pushContainer('body', methods.map((name) => expressionStatement(identifier(`this.${name}.bind(this)`))))
currentNode.set('body', body.node)
}
}
})
// 添加自定义的成员方法,并添加绑定代码
path.pushContainer('body', classMethod('method', identifier('getTest'), [], blockStatement(methods.map((name) => expressionStatement(identifier(`this.${name}.bind(this)`))))))
},
})
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
编译完成后会得到以下结果

import fs from 'fs'

@testDecorators()
export class Index {
a: number
private b: number = 1
constructor (a: number) {
this.a = a
this.sum.bind(this)
this.getB.bind(this)
this.getSum.bind(this)
}
private sum (): number {
return this.a + this.b
}

getB (): number {
return this.b
}

getSum (): number {
return this.sum()
}

getTest () {
this.sum.bind(this);
this.getB.bind(this);
this.getSum.bind(this);
}
}
————————————————

posted @ 2021-07-26 09:08  方东信  阅读(206)  评论(0编辑  收藏  举报