babel 能够做静态分析,分析代码然后得出一些信息。我们经常用的打包工具就需要通过静态分析的方式得出模块间的依赖关系,然后构造成依赖图,之后对这个依赖图做各种处理,最后输出成文件。
比如 webpack 的打包过程:从入口模块分析依赖,构造模块依赖图,然后把一些模块合并到同个分组(chunk)里,生成 chunk 依赖图,最后把 chunk 通过模版打印为 assets,输出为文件。

从入口模块开始,对每个模块的依赖关系的分析就是基于 AST,这种就可以用 babel parser (或者直接用 acorn)来处理。
这一节我们就来实现下依赖分析的功能,也就是遍历所有的模块。
写这个的好处一个是能够加深我们对打包工具的认识,二是当做一些独立的工具的时候,可能也需要分析模块依赖关系。
# 思路分析
模块依赖分析也就是要分析 import 和 export,从入口模块开始,读取文件内容,通过 babel parser 把内容 parse 成 ast,之后通过 babel traverse 来对 AST 进行遍历。分别对 ImportDeclaration、ExportDeclaration 做处理:
ImportDeclaration:收集 import 信息,确定依赖的模块和引入的变量,之后再递归处理该模块 ExportDeclaration:收集 export 信息,确定导出的变量
我们可以设计这样一个结构来表示每个模块的信息:
class DependencyNode {
constructor(path = '', imports = {}, exports = []) {
this.path = path;
this.imports = imports;
this.exports = exports;
this.subModules = {};
}
}
path 表示当前模块路径, imports 表示从什么模块引入了什么变量,exports 表示导出了什么变量。
接下来我们要完成 traverseModule 这个方法,也就是对每个模块的处理
const dependencyGraph = traverseModule(入口模块路径);
具体处理的过程就是:
- 读取文件内容
- 通过 babel parser 把文件内容 parse 成 ast
- 遍历 AST,对 ImportDeclaration、ExportDeclaration 分别做处理
- 对分析出的依赖路径进行处理,变成绝对路径,并尝试补全
- 递归处理分析出来的依赖路径
如果没有后缀名的依赖路径,要分别尝试 .js、.jsx、.ts、.tsx 的路径,如果存在就补全成该路径,并且目录还要补全 index 文件名。
通过递归处理依赖模块,就可以完成依赖图的构建,我们可以保存根节点和所有模块的信息:
const dependencyGraph = {
root: new DependencyNode(),
allModules: {}
};
当处理完所有模块后,就得到了完整的 dependencyGraph。
接下来我们来写下代码。
# 代码实现
首先我们定义要返回的 dependencyGraph,
class DependencyNode {
constructor(path = '', imports = {}, exports = []) {
this.path 