见素包朴,少私寡欲,绝学无忧

github: miniapp-shaking

上一章我们介绍了遍历js文件的方法,接下来我们介绍其他文件的遍历。

1. 遍历JSON文件

对于json文件,我们直接读取json文件,然后转化为json对象来处理。json文件中我们主要处理的是组件的json和app.json,微信小程序中有一些特殊的字段会对外产生依赖:pagesusingComponentscomponentGenericscomponentPlaceholder,这里需要特别注意后面两个,即抽象节点组件和分包异步化之后的占位组件。另外我还添加了一个自定义的字段replaceComponents用于处理组名的问题,在上一章中我曾经介绍过config里面有一个字段叫groupName,这里就是用于定义不同业务组中组件的地方。

/*** 收集json文件依赖* @param file* @returns {[]}*/jsonDeps(file) {const deps = [];const dirName = path.dirname(file);// json中有关依赖的关键字段const { pages, usingComponents, replaceComponents,  componentGenerics, componentPlaceholder} = fse.readJsonSync(file);// 处理有pages的json,一般是主包if (pages && pages.length) {pages.forEach(page => {this.addPage(page);});}// 处理有usingComponents的json,一般是组件if (usingComponents && typeof usingComponents === 'object' && Object.keys(usingComponents).length) {// 获取改组件下的wxml的所有标签,用于下面删除无用的组件const tags = this.getWxmlTags(file.replace('.json', '.wxml'));Object.keys(usingComponents).forEach(key => {// 对于没有使用的组件,不需要依赖if (tags.size && !tags.has(key.toLocaleLowerCase())) return;let filePath;// 如有需要,替换组件const rcomponents = replaceComponents ? replaceComponents[this.config.groupName] : null;const component = getReplaceComponent(key, usingComponents[key], rcomponents);if (component.startsWith('../') || component.startsWith('./')) {// 处理相对路径filePath = path.resolve(dirName, component);} else if (component.startsWith('/')) {// 处理绝对路径filePath = path.join(this.config.sourceDir, component.slice(1));} else {// 处理npm包filePath = path.join(this.config.sourceDir, 'miniprogram_npm', component);}// 对于json里面依赖的组价,每一个路径对应组件的四个文件: .js,.json,.wxml,wxssthis.config.fileExtends.forEach((ext) => {const temp = this.replaceExt(filePath, ext);if (this.isFile(temp)) {deps.push(temp);} else {const indexPath = this.getIndexPath(temp);if (this.isFile(indexPath)) {deps.push(indexPath);}}});});}// 添加抽象组件依赖const genericDefaultComponents = this.getGenericDefaultComponents(componentGenerics, dirName);// 添加分包异步化占用组件const placeholderComponents = this.getComponentPlaceholder(componentPlaceholder, dirName);deps.push(...genericDefaultComponents);deps.push(...placeholderComponents);return deps;}

一般来说,pages只存在于app.json中,usingComponents存在于组件的json中,因此这两者在同一个文件中是互斥的,但我们这里用同一个函数来处理。对于pages的处理后面再统一说明,我们先说usingComponents的处理。

1.1 删除不使用的组件

对于usingComponents的处理,我先调用了一个getWxmlTags的方法来获取组件对应的wxml文件的标签,为什么要多于做这一步?因为在公司中我发现很多在json中定义的组件并没有真正的用于wxml中,或许是由于疏忽还是在迭代中忘记了,而且一个组件可能依赖很多其他的组件,依赖组件在依赖其他组件,这种深层次的递归所引用的文件是很庞大的 。因此在这里我会把在usingComponents中定义但没有实际在wxml中使用的组件给删除掉,这样就可以节省很大的空间,算是细节点之一。

/*** 获取wxml所有的标签,包括组件泛型* @param filePath* @returns {Set<unknown>}*/getWxmlTags(filePath) {let needDelete = true;const tags = new Set();if (fse.existsSync(filePath)) {const content = fse.readFileSync(filePath, 'utf-8');const htmlParser = new htmlparser2.Parser({onopentag(name, attribs = {}) {if ((name === 'include' || name === 'import') && attribs.src) {// 不删除具有include和import的文件,因为不确定依赖的wxml文件是否会包含组件needDelete = false;}tags.add(name);// 特别处理泛型组件const genericNames = getGenericName(attribs);genericNames.forEach(item => tags.add(item.toLowerCase()));},});htmlParser.write(content);htmlParser.end();}if (!needDelete) {tags.clear();}return tags;}

获取wxml的标签需要使用到htmlparser2包,在这里我对于使用了includeimport导入外部文件的wxml不做递归的深入处理了,直接跳过偷下懒吧,以后有时间在做。在这里我们还要特别注意范型组件,json中定义的组件可能并不是作为一个标签使用,而是作为范型组件使用,这里也算是一个细节点之一。要想做好一个东西,需要考虑的东西实在太多了,cry。

/*** 解析泛型组件名称* @param attribs* @returns {[]}*/
function getGenericName(attribs = {}) {let names = [];Object.keys(attribs).forEach(key => {if (/generic:/.test(key)) {names.push(attribs[key]);}});return names;
}

1.2 取代业务组的组件

上面我们说到了groupNamereplaceComponents字段,为什么我要新增这样一个字段呢?如果你的公司很庞大,有很多的业务组,为了减少重复开发和复用逻辑或者是客开逻辑,他们的代码往往是可能放在同一个页面或者组件的,例如你可能会做一个详情页,这个详情页有不同业务组的详情组件,通常你会通过wx:if来判断使用哪个详情组件,但这样也把其他业务组的组件打包进来了,实际上别的业务的组件对你来说毫无用处,这些代码是应该去掉的。举个的例子:
detail.wxml

<detail-a wx:if="{{ flag === 'A' }}"></detail-a>
<detail-b wx:elif="{{ flag === 'B' }}"></detail-b>
<detail-c wx:else></detail-c>

detail.json

{"usingComponents": {'detail-a': A,'detail-b': B,'detail-c': C}
}

如果你是业务组A,那么你就把业务组B和业务组C的代码都打包进来了。当然这是对于大公司的复杂逻辑而言,一般情况不必考虑这么复杂的场景。

现在我们换一种写法,添加一个replaceComponents字段:
detail.wxml

<detail></detail>

detail.json

{"usingComponents": {"detail": detail-c,},"replaceComponents": {"A": {"detail": detail-a},"B": {"detail": detail-c}}
}

在这里我们只有一个详情页,打包工具在打包的时候对于groupNameA的会直接使用detail-a,对于B同理。即打包之后会变成:

{"usingComponents": {"detail": detail-a,}
}

其他组B、组C的代码会被忽略,从而大大减少包的大小,对于大公司的复杂业务逻辑超包的问题尤其有用。但是有利也有弊,在开发的时候由于默认使用的是detail-c,所以A开发的时候需要暂时替换成detail-a,但是相对于超包发布不了或者提高加载性能而言,这些好像也能够接受,全看自己取舍吧。

1.3 一个路径4个文件

接下来就是路径的判断了,和js的处理差不多。

对于json中定义的组件,我们知道微信的组件是由4个文件组成的js,json,wxml,wxss,所以接下来我们对于每个组件,需要生成四个依赖,即这段代码:

// 对于json里面依赖的组价,每一个路径对应组件的四个文件: .js,.json,.wxml,wxssthis.config.fileExtends.forEach((ext) => {const temp = this.replaceExt(filePath, ext);if (this.isFile(temp)) {deps.push(temp);} else {const indexPath = this.getIndexPath(temp);if (this.isFile(indexPath)) {deps.push(indexPath);}}});
/**** @param filePath* @param ext* @returns {string}*/replaceExt(filePath, ext = '') {const dirName = path.dirname(filePath);const extName = path.extname(filePath);const fileName = path.basename(filePath, extName);return path.join(dirName, fileName + ext);}
/*** 获取index文件的路径* @param filePath* @returns {string}*/getIndexPath(filePath) {const ext = path.extname(filePath);const index = filePath.lastIndexOf(ext);return filePath.substring(0, index) + path.sep + 'index' + ext;}

1.4 处理范型默认组件

需要注意范型组件支持默认组件,我们也同样需要处理,这也是细节之一。

/*** 处理泛型组件的默认组件* @param componentGenerics* @param dirName* @returns {[]}*/getGenericDefaultComponents(componentGenerics, dirName) {const deps = [];if (componentGenerics && typeof componentGenerics === 'object') {Object.keys(componentGenerics).forEach(key => {if (componentGenerics[key].default) {let filePath = componentGenerics[key].default;if (filePath.startsWith('../') || filePath.startsWith('./')) {filePath = path.resolve(dirName, filePath);} else if (filePath.startsWith('/')) {filePath = path.join(this.config.sourceDir, filePath.slice(1));} else {filePath = path.join(this.config.sourceDir, 'miniprogram_npm', filePath);}this.config.fileExtends.forEach((ext) => {const temp = this.replaceExt(filePath, ext);if (this.isFile(temp)) {deps.push(temp);} else {const indexPath = this.getIndexPath(temp);if (this.isFile(indexPath)) {deps.push(indexPath);}}});}});}return deps;}

1.5 处理分包异步化的占位组件

分包异步化之后,又有了占位组件,也需要同样处理。

/*** 处理分包异步化的站位组件* @param componentPlaceholder* @param dirName* @returns {[]}*/getComponentPlaceholder(componentPlaceholder, dirName) {const deps = [];if (componentPlaceholder && typeof componentPlaceholder === 'object' && Object.keys(componentPlaceholder).length) {Object.keys(componentPlaceholder).forEach(key => {let filePath;const component = componentPlaceholder[key];// 直接写view的不遍历if (component === 'view' || component === 'text') return;if (component.startsWith('../') || component.startsWith('./')) {// 处理相对路径filePath = path.resolve(dirName, component);} else if (component.startsWith('/')) {// 绝对相对路径filePath = path.join(this.config.sourceDir, component.slice(1));} else {// 处理npm包filePath = path.join(this.config.sourceDir, 'miniprogram_npm', component);}this.config.fileExtends.forEach((ext) => {const temp = this.replaceExt(filePath, ext);if (this.isFile(temp)) {deps.push(temp);} else {const indexPath = this.getIndexPath(temp);if (this.isFile(indexPath)) {deps.push(indexPath);}}});});}return deps;}

到此为止json的处理基本讲完了,有点长,细节也非常多,做这种工具需要平心静气,认真思考,不然一招不慎满盘皆输。还剩下一个pages字段的处理留到最后再说,这涉及到入口的遍历。

欲知后文请关注下一章。

连载文章链接:
手写小程序摇树工具(一)——依赖分析介绍
手写小程序摇树工具(二)——遍历js文件
手写小程序摇树工具(三)——遍历json文件
手写小程序摇树工具(四)——遍历wxml、wxss、wxs文件
手写小程序摇树工具(五)——从单一文件开始深度依赖收集
手写小程序摇树工具(六)——主包和子包依赖收集
手写小程序摇树工具(七)——生成依赖图
手写小程序摇树工具(八)——移动独立npm包
手写小程序摇化工具(九)——删除业务组代码

手写小程序摇树优化工具(三)——遍历json文件相关推荐

  1. 手写小程序:字符串分隔

    手写小程序:字符串分隔 题目 描述: •连续输入字符串,请按长度为8拆分每个字符串后输出到新的字符串数组: •长度不是8整数倍的字符串请在后面补数字0,空字符串不处理. 输入描述: 连续输入字符串(输 ...

  2. 微信小程序打开项目提示读取project.config.json文件失败

    前几天一个月薪35k的兄弟,给我推了一个人工智能学习网站,看了一段时间挺有意思的.包括语音识别.机器翻译等从基础到实战都有,很详细,分享给大家.大家及时保存,说不定啥时候就没了. 微信小程序打开项目提 ...

  3. 【微信小程序导入项目报错:[app.json文件内容错误]app.json未找到】解决方法

    今天打开微信小程序导入一个项目时,控制台弹出下面的错误: 在第一级文件目录里确实找不到app.json文件,但是打开二级目录可以看到app.json.在网上看到大致有两种解决方法: 配置project ...

  4. 微信原生小程序导入项目报错:[ app.json 文件内容错误] app.json: app.json 未找到

    问题: 导入小程序运行报错: 原因:在第一级文件夹下没有找到app.json文件,但是在二级文件夹里可以找到,如图我的文件结构: 解决:网上有两种方法 1.在第一级文件夹project.config. ...

  5. 用python手写KNN算法+kd树及其BBF优化(原理与实现)(下篇)

    用python手写KNN算法+kd树及其BBF优化(原理与实现)(下篇) 接上一篇用python手写KNN算法+kd树及其BBF优化(原理与实现)(上篇) 我们使用training2和test2两个数 ...

  6. 微信小程序(微信开发者工具及工程创建、小程序配置、逻辑层、模块化)

    一.阶段概述 1.课程安排 课程市场:14+1 课程安排:微信小程序:5uniapp:5数据可视化:4答辩:1 2.项目展示 小u商城 数据可视化 3.阶段目标 1.培养学员能力独立完成小程序原生开发 ...

  7. 微信小程序开发❤手摸手撸小程序一篇就够!

    手摸手撸小程序!!超简单,一篇就够! 微信小程序介绍 微信小程序,简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的梦想,用 ...

  8. 为何我们要用 React 来写小程序 - Taro 诞生记

    在互联网不断发展的今天,前端程序员们也不断面临着新的挑战,在这个变化多端.不断革新自己的领域,每一年都有新的美好事物在发生.从去年微信小程序的诞生,到今年的逐渐火热,以及异军突起的轻应用.百度小程序等 ...

  9. 结合DvaJS来写小程序

    小程序结合Redux,对于复杂的小程序,是很好的状态管理利器, 而Redux写起来相对复杂费力, 相比阿里爸爸的Dva.js把各种概念组合成model, 是很容易上手的, 在写小程序的时候, 很希望能 ...

最新文章

  1. eclipse使用working set
  2. mysql 不要统计null_浅谈为什么Mysql数据库尽量避免NULL
  3. polycom安卓手机客户端_安卓 emoji 表情:全变了
  4. 100w条数据插入Mysql 数据库,耗时仅10s
  5. Java项目:JSP网上零食销售系统
  6. 心电信号的特征提取、分析与处理
  7. bug提单 java_bug提单规范
  8. 如何在python中获得当前时间前几天的日期
  9. 翻译小窍门-谢谢你勾引我老公
  10. 落花已去,相思成冢。十月的杜鹃雨,下得纷纷扬扬。我走在花瓣雨下,回忆我们曾经的甜蜜温馨,一回首,一抬头,仿佛你就在灯火阑珊处。那些掉落在地上的杜鹃,成了相思的墓,也许是为了祭奠我们曾经的美好。 杜鹃
  11. 【POI2013】bzoj3426 Tower Defence Game
  12. 协方差意味着什么_微服务意味着我们可以使用所需的任何语言? 真?
  13. 黄佳《零基础学机器学习》chap3笔记
  14. 接鸡蛋小游戏【终极版】【C语言】【原创】
  15. 服务器端名片识别/ocr识别
  16. cubemx配置正点原子lcd屏-完整版
  17. js的三种弹出对话框
  18. 最新8月份编程语言排行榜详情
  19. MySQL 左连接、右连接、内连接
  20. CPU mask机制变化

热门文章

  1. 真给 IT 人丢脸啊!看完我直接蚌埠住了!
  2. 【C语言期末/实践/大作业】成绩管理系统日程表管理系统
  3. 在Mac下防范“P2P终结者”限速
  4. 对Transformer中的MASK理解
  5. 2021-2027全球与中国音圈马达驱动市场现状及未来发展趋势
  6. creator 跳跃弧线_CocosCreator零基础制作游戏《极限跳跃》六、制作游戏障碍物实现碰撞检测...
  7. python django XX在线笔记系统
  8. 挽歌.怀念光辉岁月(转载)
  9. chm文件打开空白如何解决?
  10. 小巧的Mac软件卸载工具,App Cleaner的优点