Path
Path 是表示两个节点之间连接的对象
例如,如果有下面这样一个节点及其子节点︰
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "square"
},
...
}
将子节点 Identifier 表示为一个路径(Path)的话,看起来是这样的:
{
"parent": {
"type": "FunctionDeclaration",
"id": {...},
....
},
"node": {
"type": "Identifier",
"name": "square"
}
}
同时它还包含关于该路径的其他元数据:
{
"parent": {...},
"node": {...},
"hub": {...},
"contexts": [],
"data": {},
"shouldSkip": false,
"shouldStop": false,
"removed": false,
"state": null,
"opts": null,
"skipKeys": null,
"parentPath": null,
"context": null,
"container": null,
"listKey": null,
"inList": false,
"parentKey": null,
"key": null,
"scope": null,
"type": null,
"typeAnnotation": null
}
Babel插件开发
尝试空的插件
- npm init -y
- npm i @bale/core -D
- 编写插件
module.exports = () => {
return {
visitor: {
// 逻辑
}
}
}
- 测试
const {transformSync} = require('@babel/core')
const Myplugin = require('./index')
const code = `
let b = 90
let c =90
console.log(b+c)
`
let config = {
plugins: [Myplugin]
}
const out = transformSync(code,config)
console.log(out.code)
编写
module.exports = function ({ types: t }) {
return {
visitor: {
Identifier(path) {
const parentNodeIsIfStatement = t.isIfStatement(path.parent);
const isDebug = path.node.name === "DEBUG";
if (isDebug && parentNodeIsIfStatement) {
const stringNode = t.stringLiteral("DEBUG");
path.replaceWith(stringNode);
}
},
StringLiteral(path) {
const parentNodeIsIfStatement = t.isIfStatement(path.parent);
const isDebug = path.node.value === "DEBUG";
if (isDebug && parentNodeIsIfStatement) {
if (process.env.NODE_ENV === "production") {
path.parentPath.remove();
}
}
},
},
};
};
State
state是所有节点函数的第二个对象
- state.opts是在编写.babelrc 插件的参数
进阶
生产AST
const { transformSync, parse, traverse, types, transformFromAstSync } = require('@babel/core')
Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)
解析 (代码到AST)
const code1 = `
let a = 2
`
const ast = parse(code1)
转换(AST到代码)
const ast = parse('const a = 1')
const { code } = transformFromAstSync(ast)
console.log(code)
遍历操作
AST树官方链接 :实时观察代码产生的AST
traverse(ast, {
enter(nodePath) {
console.log(nodePath.type)
}
})
>>>output
Program
VariableDeclaration
VariableDeclarator
Identifier
NumericLiteral
操作
遍历的所有函数(函数与节点类型同名)都会得到两个变量 Nodepath, state
NodePath是用来描述节点关系的结构,通过traverse中的节点方法暴露出来。
常用的属性/方法包括:
- NodePath.node 节点本身
- NodePath.parentPath 父级节点
- NodePath.type 节点类型字符串
- NodePath.scope 语法上下文,能得到全局变量等信息
- NodePath.traverse() 自遍历
- NodePath.replaceWith() 节点替换
- NodePath.remove() 节点删除
使用types创建AST
- types.identifier("TEST")
定义插件
暴露的 visitor 就是 traverse 的第二个参数
module.exports = ({types: t}) => {
let count =0
return {
visitor: {
Identifier(path, state) {
const name = path.node.name
if(name !== "DEBUG"){
return
}
if(t.isIfStatement(path.parent)){
path.replaceWith(t.stringLiteral("DEBUG"))
}
},
StringLiteral(path, {opts:{useDebug}}) {
console.log(++count)
console.log(path.type)
const value = path.node.value
if(value !== "DEBUG"){
return
}
if(t.isIfStatement(path.parent) && useDebug ){
path.parentPath.remove()
}
}
},
}
}
调用插件
const ast = parse('const a = 1')
const { code } = transformFromAstSync(
ast,
null,
{ plugins: [ plugin ] } // 这里
)
console.log(code)
@babel/types
const { types } = require('@babel/core')
import * as types from '@babel/types'
既是这里的types也是定义插件第一个参数 ({types: t})
| generator | transfrom |
|---|---|
可以基于任何类型ast节点输出代码 | 只能基于完整ast树 |
| 代码压缩、引号选择等简单配置 | 完整代码配置,支持插件 |
创建节点
- 节点类型同名方法直接调用
import * as types from '@babel/types'
import gen from '@babel/generator'
const log = (node: types.Node) => {
console.log(gen(node).code)
}
log(types.stringLiteral('string'))
log(types.numericLiteral(10e4))
log(types.booleanLiteral(0.5 > Math.random()))
log(types.regExpLiteral('\.jsx?$', 'g'))
//创建Array
log(
types.arrayExpression([
types.stringLiteral('string'),
types.numericLiteral(10e4),
types.booleanLiteral(0.5 > Math.random()),
types.regExpLiteral('\.jsx?$', 'g')
])
)
// 创建Object
log(
types.objectExpression([
types.objectProperty(
types.identifier('a'),
types.nullLiteral()
),
types.objectProperty(
// 字符串类型 key
types.stringLiteral('*'),
types.arrayExpression([]),
),
types.objectProperty(
types.identifier('id'),
types.identifier('id'),
false,
// shorthand 对 { id: id } 简写为 { id }
true
),
types.objectProperty(
types.memberExpression(
types.identifier('props'),
types.identifier('class')
),
types.booleanLiteral(true),
// 计算值 key
true
)
])
)
// 创建函数
function foo(arg1) {
console.log(arg1);
}
log(
types.functionDeclaration(
types.identifier('foo'),
[types.identifier('arg1')],
types.blockStatement([
types.expressionStatement(
types.callExpression(
types.identifier('console.log'),
[types.identifier('arg1')]
)
)
])
)
)
源码
插件
发布npm包暴露需要使用的插件对象,babel插件的本质就是一个函数
module.exports = ({types: t}) => {
return {
visitor: {
Identifier(path, state) {
const name = path.node.name
if(name !== "DEBUG"){
return
}
if(t.isIfStatement(path.parent)){
path.replaceWith(t.stringLiteral("DEBUG"))
}
},
StringLiteral(path, {opts:{useDebug}}) {
const value = path.node.value
console.log('useDebug', useDebug, value)
if(value !== "DEBUG"){
return
}
if(t.isIfStatement(path.parentPath) && useDebug ){
path.parentPath.remove()
}
}
}
}
}
预设
预设的第二个参数是用户自定义的的参数
预设的制作主要:
- 创建一个package.json 把需要用的插件安装到
dependencies - 暴露一个文件用于声明预设即可
const Myplugin = require("babel-plugin-lf-debug");
module.exports = (api, opts= {})=>{
console.log('opts', opts)
return {
plugins: [[Myplugin ,{ useDebug: opts.useDebug }]]
}
}