前端通过babel精准操作js文件技巧
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,然后精准插入配置代码的全流程,完整代码参考:github。
参考文章:链接
码云笔记 » 前端通过babel精准操作js文件技巧