如何将项目中的依赖文件解析出来?

该文章阅读需要7分钟,更多文章请点击本人博客halu886

.1. 需求

将当前项目中所有JS文件的依赖解析出来。

在前端工程化中,打包是很基础的一个功能点。打包的第一步则是获得所有入口文件(单页面/多页面)的所有依赖。

这篇总结重点则在于如何实现以及优化这一个需求。

.2. 分解

实现这个功能首先需要解决以下几个问题

  1. 遍历当前项目结构,根据文件后缀获得出JS文件。
  2. 递归回朔分别对JS文件进行正则解析获取当前文件依赖文件路径
  3. 将正则解析出来的文件路径转换成绝对路径(兼容客制化配置)

.3. 实现思路

.3.1. 入口文件

通过命令行参数获取项目路径,解析成绝对路径。同时调用core function。

入口文件 ./index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const arg3 = process.argv.slice(2)[0];

//项目路径
const projectDir = path.join(__dirname,arg3);

parsePath(projectDir,0,projectDir).then(res=>{
fs.writeFileSync(
'./output.json',
JSON.stringify(res,null,5)
)
}).catch(console.error);
```

### .3.2. 遍历当前项目所有文件

根据项目地址,遍历所有目录下所有对象,返回文件对应的依赖
当为目录时继续递归,否则进行文件解析。
同时整理每次的解析的返回,最后返回一个清晰明了的结果

封装于 **./core.js**

`
``js
// 核心源码

/**
* 遍历当前路径下所有对象,返回文件对应的依赖
* @params currentPath 当前文件路径
* @params index 依赖树层级
* @params __projectDir 根目录地址
* @return {[key:string]:[]} {当前 文件/目录下所有的对象 的路径:[key值的依赖树文件列表]}
**/

async function parsePath(currentPath,index,__projectDir){
try {
const state = fs.statSync(currentPath);

let dirSrc={};
if(state.isDirectory()
&&
path.basename(currentPath)
!=="node_modules"
){

const res = fs.readdirSync(currentPath)
for(const child of res){

// 路径为目录时递归对象
const pathObj = await parsePath(
path.join(currentPath,child),
index,__projectDir);

// 合并返回结果
dirSrc = {...dirSrc,...pathObj};
}
return dirSrc;
}

// 当为文件对象时,进行依赖树解析

// 依赖树字典
const mapRequire = new Map();

await analysisRequire(
currentPath,
index,
__projectDir,
mapRequire)

return {
[path.relative(
__projectDir,currentPath
)]:
[...mapRequire.keys()]}
}catch (error) {
console.error(error);
return {}
}
}

.3.3. 解析文件依赖树

读取文件内容,首先通过/require\('.*'\)/g全局匹配require(“xxxx”)

对每个匹配到到require(“xxx”)通过正则/'(.*)'/获取第一个子句,

从而拿到src

然后将src加工成可访问的绝对路径进行回朔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 递归回朔解析文件依赖树
* @params currentPath 文件路径
* @params index 依赖树层级
* @params __projectDir 根目录地址
* @params mapRequire 依赖树字典
*/

async function analysisRequire(
currentPath,
index,
__projectDir,
mapRequire
)
{

const content = await fs.
readFileSync(currentPath).
toString('UTF-8');

// 全局正则匹配
const requieSrc= content.match(
/require\('(.*)'\)/g
);

if(!(requieSrc&&requieSrc.length)){
return false
}
const currentDirname = path.dirname(
currentPath
)

for(requireMatch of requieSrc){

// 解析路径
let src = requireMatch.match(
/'(.*)'/
)[1];
const parseSrc = path.parse(src);
/**
* 加工src
*/


// 将路径推入路径字典进行记录
mapRequire.set(
path.relative(__projectDir,src),
true);

// 回朔
await analysisRequire(
src,
1+index,
__projectDir,
mapRequire)
}
}

.3.4. 对路径进行加工

通过正则解析出的src的转换成绝对路径,同时实现一些客制化规则

例如 dialog.js第一步转成./dialog.js

然后被转成/module/dialog.js

再然后被转成/module/dialog/dialog.js

blabla…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const fileName = src.split('.').shift();
if(fs.existsSync(
path.join(
currentDirname,
fileName+'.js'))
){

// ./dialog.js
src =path.join(
currentDirname,
fileName+'.js');

}else if(fs.existsSync(
path.join(
__projectDir+'/module',
fileName+'.js'))
){

// module/dialog.js
src =path.join(
__projectDir+'/module',
fileName+'.js');

}else if(fs.existsSync(
path.join(
__projectDir,
'/module',
fileName,
parseSrc.name+'.js'))
){

// module/dialog/dialog.js
src = path.join(
__projectDir,
'/module',
fileName,
parseSrc.name+'.js');

}

.4. 总结

demo是通过Node.js编写的,功能虽然并不复杂。

但是对比目前现有的框架,还是非常的粗糙,优化点也是非常多。

正则解析目前只兼容一种格式,

而且每次解析路径都使用正则匹配了两遍路径,

客制化路径转换规则设计的太过于耦合 blabla…

后续应该会开帖继续优化吧

源码地址 requireTreeHandle

谢谢老板,老板必发大财!💰💰💰💰💰💰