通过babel操作AST精准插入配置代码全流程
babel修改js配置文件实现原理
像那些js配置文件,里面可能有很多的非配置代码,而且一次可能要修改好几个文件
比如我们在前端项目,要插入一个页面,需要修改router、menus等配置文件,还要手动拷贝页面模板等等
这些高重复机械化操作,人工修改非常容易出错
我们可以直接用babel来操作AST抽象语法树,通过工程化去精准修改。让babel去帮我们找到指定位置,并正确插入配置代码。我们在做工程化开发的时候,经常会用到babel去操作AST。
首先我们了解一下什么是AST
AST,抽象语法树(Abstract Syntax Tree)它是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构。
我们使用babel来转化和操作AST,主要分为三个步骤:解析(parser)、转换(traverse)、生成(generator)
操作AST三大阶段
如下图,如果我们想通过babel,在配置文件里面插入一段配置代码,应该怎么实现呢
解析(parser)
第一步:读取配置文件代码,并生成AST抽象语法树
let configJsData = fs.readFileSync(configJsPath, "utf8");
然后将配置文件代码生成AST抽象语法树
const parser = require("@babel/parser"); let configJsTree = parser.parse(`${configJsData}`,{ sourceType: "module", plugins: [ "jsx", "flow", ], });
configJsTree就是我们的AST了
加上sourceType: "module"
这个配置属性,是为了让babel支持解析export和import
转换(traverse)
转换(traverse)阶段,就是要遍历整个AST抽象语法树,找到指定的位置,然后插入对应的配置代码。
代码如下:
const traverse = require("@babel/traverse").default; traverse(configJsTree, { ObjectProperty(path) { // 插入配置文件代码 }, });
我们使用@babel/traverse
的traverse方法进行遍历整个AST
其中ObjectProperty
的作用是在遍历AST过程中,识别出所有的Object对象,因为我们是要将配置代码插入一个Object对象,所以使用的是ObjectProperty
。如果要将配置插入数组中,就使用ArrayExpression
。
然后我们开始进行配置代码的插入,将代码
{ key: "testPath", icon: HomeOutlined, exact: true, }
插入如下的位置
我们需要在traverse
的ObjectProperty
进行位置的查找和代码插入
首先我们要找到key: 'home'
的位置
代码如下:
traverse(configJsTree, { ObjectProperty(path) { if ( path.node.key.name === "key" && path.node.value.value === "home" ) { // 这就是 key: 'home'的位置 } }, });
通过path.node.key.name
和path.node.value.value
找到对象属性为key
并且对象值为home
的Object对象
找到位置后开始插入代码
traverse(configJsTree, { ObjectProperty(path) { // 搜索并识别出配置文件里key: "home" 这个object对象位置 if ( path.node.key.name === "key" && path.node.value.value === "home" ) { path.parent.properties.forEach(e=>{ if ( e.key.name === "children" ) { // 找到children属性 } }) } }, });
通过path.parent.properties
找到对象的父级,然后遍历父级下的所有属性,找到children
这个属性。这就是我们要插入的位置。
接下来我们要构造要插入的数据
{ key: "testPath", icon: HomeOutlined, exact: true, }
构造数据的代码如下:
const t = require("@babel/types"); const newObj = t.objectExpression([ t.objectProperty( t.identifier("key"), t.stringLiteral("testPath") ), t.objectProperty( t.identifier("icon"), t.identifier("HomeOutlined") ), t.objectProperty( t.identifier("exact"), t.booleanLiteral(true) ), ]);
可以看到用dentifier
来标识对象的属性,用stringLiteral
标识字符串数据,booleanLiteral
标识boolean值,这些类型都可以在@babel/types
查询到。
最后一步,将构造好的数据插入:
e.value.elements.push(newObj)
完成~!
将所有 traverse 阶段代码汇总起来如下:
const traverse = require("@babel/traverse").default; const t = require("@babel/types"); traverse(configJsTree, { ObjectProperty(path) { // 搜索并识别出配置文件里key: "home" 这个object对象位置 if ( path.node.key.name === "key" && path.node.value.value === "home" ) { path.parent.properties.forEach(e=>{ if ( e.key.name === "children" ) { const newObj = t.objectExpression([ t.objectProperty( t.identifier("key"), t.stringLiteral("testPath") ), t.objectProperty( t.identifier("icon"), t.identifier("HomeOutlined") ), t.objectProperty( t.identifier("exact"), t.booleanLiteral(true) ), ]); e.value.elements.push(newObj) } }) } }, });
生成(generator)
这个阶段就是把AST抽象语法树反解,生成我们常规的代码
const generate = require("@babel/generator").default; const result = generate(configJsTree, { jsescOption: { minimal: true } }, "").code; fs.writeFileSync(resultPath, result);
通过@babel/generator
将AST抽象代码语法树反解回原代码,jsescOption: { minimal: true }
配置是为了解决中文为unicode乱码的问题。
至此咱们就完成了对js配置文件插入代码,最终结果如下:
以上就是通过babel操作AST,然后精准插入配置代码的全流程。