程序员

代码自动迁移工具

2020-11-02  本文已影响0人  乘着风

有这样一个背景

当我们将老项目迁移,比如React15&webpack3 迁移到react16&webpack5, 而且要按照入口文件一个一个地迁移,不能完全复制。
但每个入口依赖的文件手动复制太麻烦,还容易出错;如果重写的话,成本有太大;
有没有可以根据一个入口文件自动分析它依赖的文件及其所依赖的文件呢?

实践1:React Native项目

RN代码的编译是要制定一个入口,它的打包不是webpack,是自带的metro-bundler, 它的原理也是通过babel将React代码通过AST相关方法编译的。

基于这个知识点思考自动迁移的目标,我们也可以用babel通过AST将一个文件依赖的文件分析再递归,去重,这样就可以将其所依赖的所有文件获取到,然后按照原有路径复制文件即可;


tree.png

那如何使用AST呢?
AST(abstract syntax code)即“抽象语法树”,是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。

比如一段代码可以通过AST解析成如下图所示的树形结构:


DEMO.png

通过图片我们可以看到,import相关代码对应的AST结构片段是右侧黄色的部分,对应的是ImportDeclaration;那么,我们迁移的目标就是要分析这个代码的依赖,那就遍历ImportDeclaration,将其source.value获取出来生成路径就可以了。

如何遍历AST呢?
可以用babel工具,@babel/parser可以将代码解析成AST,@babel/traverse可以遍历AST中的叶子节点。具体的方法可以查babel官网;在这里我帖一下这个项目的相关代码:

const parser = require("@babel/parser")
const traverse = require("@babel/traverse")

const getDependanceList = function({result, entry_path, astConfig}){
    const data = fs.readFileSync(entry_path)
    const entry_str = data.toString();
    const ast = parser.parse(entry_str, astConfig)
    const visitor = {
        ImportDeclaration(path) {
            const {value} = path.node.source;
            // 判断是否有本地文件
            if (value[0] === '.') {
                ...
                    if(result.relativeDep.indexOf(target) === -1 && target !== null){
                        result.relativeDep.push(target);    
                        if(typeof target === 'string') {
                            result = getDependanceList({
                                entry_path: target, 
                                result, 
                                astConfig,
                            })
                        }
                  ...
                    }
            } else {
                //npm处理
            }
        }
    }
    // 遍历
    traverse.default(ast, visitor)
    return result;
}

可见这个函数大致的流程是:

  1. 读取文件;
  2. @babel/parser解析成AST;
  3. @babel/traverse遍历ImportDeclaration
  4. 区别import的是本地文件路径还是NPM的包名
  5. 根据node.source.value确定依赖的路径
  6. 递归调用

这样来看,并没有什么复杂的;后面我还做了根据依赖文件列表复制文件,并将数据可视化,如下图:


mig报告.png

实践2 webpack项目

webpack项目之于RN项目的不同之处在于复杂的自定义编译流程,比如别名,分包,各种loader, plugins等配置;基于上文的思路,webpack config的逻辑功能还需要实现,这样成本太大;

这样就需要换个思路,webpack有个比较有名的插件webpack-bundle-analyzer。它通常是分析代码包的大小结构,我们可以看到它的输出结果包括了入口所依赖的文件;所以基于这个插件的产物,我们就可以把依赖文件列表直接进行复制,可视化的操作了。这个方案的成本最小,缺点是需要执行两次命令。

实现这样一个工具的核心能力是什么?

最主要的还是对基础知识的理解,要明白基本的流程,比如对代码的编译,AST的作用,babel的能力有所了解,剩下的就是制定计划,逐个步骤去执行,遇到具体问题去百度就好了,但是没有前者的知识积累,让你百度都不知道搜什么。

这篇文章主要目的是用分享的文字记录这个工具的实现方式;没有具体的介绍AST等技术细节;希望对读者在开发过程中有开拓思路的作用,不只是一个具体问题的解决方案。

链接分享

上一篇 下一篇

猜你喜欢

热点阅读