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

1,979 阅读3分钟

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

.1. 需求

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

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

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

.2. 分解

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

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

.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