该文章阅读需要7分钟,更多文章请点击本人博客halu886
.1. 需求
将当前项目中所有JS文件的依赖解析出来。
在前端工程化中,打包是很基础的一个功能点。打包的第一步则是获得所有入口文件(单页面/多页面)的所有依赖。
这篇总结重点则在于如何实现以及优化这一个需求。
.2. 分解
实现这个功能首先需要解决以下几个问题
- 遍历当前项目结构,根据文件后缀获得出JS文件。
- 递归回朔分别对JS文件进行正则解析获取当前文件依赖文件路径
- 将正则解析出来的文件路径转换成绝对路径(兼容客制化配置)
.3. 实现思路
.3.1. 入口文件
通过命令行参数获取项目路径,解析成绝对路径。同时调用core function。
入口文件 ./index.js
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
// 核心源码
/**
* 遍历当前路径下所有对象,返回文件对应的依赖
* @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加工成可访问的绝对路径进行回朔
/**
* 递归回朔解析文件依赖树
* @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...
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