你根本不知道“她“的全貌,「可视化」前端项目内部依赖 🍉

简介: 你根本不知道“她“的全貌,「可视化」前端项目内部依赖 🍉

前言


书接上回:这个夏天,给你的代码减个肥 🍉|let-s-refactor 插件开发之路(一)

在我上次给我的代码完成减肥之后,我有了一个新的想法:


💡:我既然已经具有了识别 export 与 import 的能力,为何不将项目内部完整的依赖关系可视化展现,以便发现项目结构中的不合理导入导出


于是本周末我就开始引入 let-s-refactor 插件的第四个能力:可视化前端项目内部依赖


⚠️:本插件暂时仅支持本公司框架下的项目,本文后续内容为设计与开发思路,以供各位参考。


设计思路


网络异常,图片无法展示
|


最初我打算把项目的完整引用全部展示,但是项目可能存在很多根节点:

  • src/views/pages/...
  • src/expose/...
  • src/main.js
  • ...


于是存在两个问题:

  • 根节点过多导致图的展示混乱
  • 当我要查看某个组件的依赖路径的时候,其实并不关心其他引用分支上的导入导出关系

所以我做了如下设计:


网络异常,图片无法展示
|


先选择左侧的根文件,之后在右侧的区域展示该根文件的引用关系。


之后,还需要根据文件或者内容定义的不同做区分:


  • root:根文件
  • vueComponent:vue 组件
  • constant:常量
  • config:配置信息
  • service:api
  • util:工具方法
  • other:其他信息


网络异常,图片无法展示
|


当然,就算我们只在视窗显示一个根文件下的引用关系,也有可能极为复杂,于是,我有个想法:


💡:动态的根据每个节点(文件)的类型(vue组件/常量/api...)去计算每个节点的位置


于是我就会去思考,我们项目中的业务文件就类型区分,基本可以分为:

  • .vue 文件
  • .js 文件

而在我们常见的项目中:js 文件相对于 vue 文件基本处于引用关系的下游,即基本上为 vue 文件引用 js 文件,鲜有 js 引用 vue 文件的情况。

于是在我的设计中引用树自上而下的顺序为:

  • root 根节点
  • vueComponent 节点
  • jsconfig/util/...)节点
  • ...

即:


网络异常,图片无法展示
|


最终效果


网络异常,图片无法展示
|


当然依然可能存在数据量巨大的情况,我做了鼠标悬停的效果:


网络异常,图片无法展示
|


技术实现


网络异常,图片无法展示
|


上面我们已经看完了我对这个功能的设计,下面请各位跟随我的视角一起把这个功能实现出来。


获取业务根文件


入口:


const viewProjectArchitecture = vscode.commands.registerCommand('let-s-refactor.viewProjectArchitecture', () => {
    const businessRootFileList = getBusinessRootFileList();
    ArchitectureViewProvider.initTreeView(businessRootFileList);
});


getBusinessRootFileList 方法:


就是提取 pagesexposemain.js 文件列表


const getBusinessRootFileList = () => {
    const projectRoot = getProjectRoot();
    if (!projectRoot) return [];
    const fileList = [];
    const pagePath = path.join(projectRoot, '/views/pages');
    if (fs.existsSync(pagePath)) {
        fileList.push(...getFileList(pagePath));
    }
    const exposePath = path.join(projectRoot, '/expose');
    if (fs.existsSync(exposePath)) {
        fileList.push(...getFileList(exposePath));
    }
    const mainPath = path.join(projectRoot, '/main.js');
    if (fs.existsSync(mainPath)) {
        fileList.push(mainPath);
    }
    return fileList;
}


getBusinessRootFileList 方法使用了 getFileList 方法:


一个小递归,入参是目录路径


const getFileList = (dirPath) => {
    let dirSubItems = fs.readdirSync(dirPath);
    const fileList = [];
    for (const item of dirSubItems) {
        const childPath = path.join(dirPath, item);
        if (_isDir(childPath) && !excludedDirs.has(item)) {
            fileList.push(...getFileList(childPath));
        } else if (!_isDir(childPath) && includedFileSubfixes.has(path.extname(item))) {
            fileList.push(childPath);
        }
    }
    return fileList;
}


获取根节点引用的所有后代节点


获取到的节点就是所有展示在引用树上的所有节点~后续我们也需要读取这些文件内容拿到引用的 sourcetarget


入口:


const importedFileList = [...getImportedFileSet([path])];


getImportedFileSet 方法:


此方法在 这个夏天,给你的代码减个肥 🍉|let-s-refactor 插件开发之路(一) 中有介绍,主要是使用正则拿到所有引用路径的


const getImportPathRegs = () => {
    // TODO: 无法检测运行时生成的路径
    return [
        // import * from './example'
        /(?<statement>import\s+.*?\s+from\s+['"](?<modulePath>.+?)['"])/g,
        // import('./example')
        /(?<statement>import\(['"](?<modulePath>.+?)['"]\))/g,
        // import './example'
        /(?<statement>import\s+['"](?<modulePath>.+?)['"])/g
    ]
}
const getImportedFileSet = (fileList, set = new Set([])) => {
    const _fileList = [];
    for (const file of fileList) {
        if (set.has(file)) {
            continue;
        }
        set.add(file);
        const content = fs.readFileSync(file, {
            encoding: 'utf-8'
        });
        const regReferences = getImportPathRegs();
        for (const reg of regReferences) {
            let matchResult;
            while ((matchResult = reg.exec(content))) {
                const { modulePath } = matchResult.groups;
                const filePath = speculatePath(modulePath, file);
                if (filePath && !set.has(filePath)) {
                    _fileList.push(filePath);
                }
            }
        }
    }
    if (_fileList.length) getImportedFileSet(_fileList, set);
    return set;
}


获取内部引用关系


既然我们已经拿到了全部的文件信息,那么我们就可以读取他们,拿到引用的 sourcetarget 了~


入口:


const { links, fileValueMap } = getRelationshipMapLinkInfo(importedFileList);


getRelationshipMapLinkInfo 方法:


这里我们的目标是拿到引用的 sourcetarget,以及引用的数量


const getRelationshipMapLinkInfo = (fileList) => {
    const links = [];
    const fileValueMap = new Map();
    for (const targetFile of fileList) {
        const content = fs.readFileSync(targetFile, {
            encoding: 'utf-8'
        });
        const regReferences = getImportPathRegs();
        let value = 0;
        for (const reg of regReferences) {
            let matchResult;
            while ((matchResult = reg.exec(content))) {
                const { modulePath } = matchResult.groups;
                const sourceFile = speculatePath(modulePath, targetFile);
                if(!sourceFile) continue;
                value ++;
                links.push({
                    source: sourceFile,
                    target: targetFile,
                })
            }
        }
        fileValueMap.set(targetFile, value);
    }
    return { links, fileValueMap };
}


获取关系图节点信息


此处我们使用的是 echarts 进行图表渲染,再加上前文设计思路中所说的,我要动态计算节点的位置,于是诞生了这个方法


入口:


const nodes = getRelationshipMapNodes(importedFileList, path);


getRelationshipMapNodes 方法:


此处注意,我为图表左侧和顶部预留了 50px 宽度,以及每个节点都在我设计的 160 * 100 四边形中的随机一点


const getRelationshipMapNodes = (fileList, rootBusinessPath) => {
    const rootPath = getProjectRoot();
    const bufferLeft = 50;
    const bufferTop = 50;
    let rootFile = '';
    const componentFileList = [];
    const noncomponentFileList = [];
    fileList.forEach(file => {
        if (file == rootBusinessPath) {
            rootFile = file;
        } else if (path.extname(file) === '.vue') {
            componentFileList.push(file);
        } else {
            let category = 'Other';
            if (file.startsWith(`${rootPath}/constant`)) {
                category = 'Constant';
            } else if(file.startsWith(`${rootPath}/config`)) {
                category = 'Config';
            } else if(file.startsWith(`${rootPath}/service`)) {
                category = 'Service';
            } else if(file.startsWith(`${rootPath}/util`)) {
                category = 'Util';
            }
            noncomponentFileList.push({
                file,
                category
            })
        }
    });
    const nodes = [];
    if(!rootFile) return nodes;
    const columnCount = 10;
    const columnWidth = 1000 / columnCount;
    const rowHeight = 160;
    const getRandomPosition = (row, column) => {
        return {
            x: bufferLeft + (column + 0.3 + 0.4 * Math.random()) * columnWidth,
            y: bufferTop + (row + + 0.2 + 0.6 * Math.random()) * rowHeight
        }
    } 
    let maxColumn = 0;
    let row = 1;
    let column = 0;
    for(const file of componentFileList) {
        nodes.push({
            id: file,
            name: file.replace(rootPath, ''),
            path: file,
            category: 'VueComponent',
            ...getRandomPosition(row, column)
        })
        if(column == columnCount - 1) row ++; 
        maxColumn = Math.max(column, maxColumn);
        column = (column + 1) % columnCount
    };
    if(column != 0) row ++;
    column = 0;
    for(const fileInfo of noncomponentFileList) {
        nodes.push({
            id: fileInfo.file,
            name: fileInfo.file.replace(rootPath, ''),
            path: fileInfo.file,
            category: fileInfo.category,
            ...getRandomPosition(row, column)
        })
        if(column == columnCount - 1) row ++; 
        maxColumn = Math.max(column, maxColumn);
        column = (column + 1) % columnCount
    };
    nodes.push({
        id: rootFile,
        name: rootFile.replace(rootPath, ''),
        path: rootFile,
        category: 'Root',
        x: bufferLeft + maxColumn * columnWidth / 2,
        y: bufferTop + rowHeight / 2 
    });
    return nodes;
}

随后我们就可以根据返回的节点去渲染 echarts 的关系图了~

结束语

网络异常,图片无法展示
|


那么,这篇文章的全部内容就到此结束了。接下来打算先用语法分析优化 exportimport 分析准确性,以及增加本插件的普遍适用性。


各位年薪百万的读者,我们下次再见!


✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

流波将月去,潮水带星来。

杨广《春江花月夜》

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

相关文章
|
4月前
|
前端开发 JavaScript Java
踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录
在软件开发的道路上,我们总是会遇到各种问题和挑战,这些问题可能是技术的限制,也可能是配置的误差。解决这些问题的过程通常是开发者成长的一部分。今天,我将与大家分享在开发过程中,涉及到 WordPress、MyBatis-Plus 和 前端依赖问题 时,我遇到的一些“坑”以及如何一步步解决它们的经验。
|
6月前
|
存储 缓存 资源调度
前端瘦身革命:告别臃肿的依赖管理
前端瘦身革命:告别臃肿的依赖管理
255 79
|
9月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
514 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
11月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
393 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
10月前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
684 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
10月前
|
安全 前端开发 开发工具
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
507 5
【01】鸿蒙实战应用开发-华为鸿蒙纯血操作系统Harmony OS NEXT-项目开发实战-优雅草卓伊凡拟开发一个一站式家政服务平台-前期筹备-暂定取名斑马家政软件系统-本项目前端开源-服务端采用优雅草蜻蜓Z系统-搭配ruoyi框架admin后台-全过程实战项目分享-从零开发到上线
|
10月前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
552 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
11月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
385 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
10月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
345 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
9月前
|
人工智能 JavaScript 前端开发
Vue 性能革命:揭秘前端优化的终极技巧;Vue优化技巧,解决Vue项目卡顿问题
Vue在处理少量数据和有限dom的情况下技术已经非常成熟了,但现在随着AI时代的到来,海量数据场景会越来越多,Vue优化技巧也是必备技能。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    586
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    238
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    226
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    164
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    275
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    408
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    177
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    112
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    192
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    262