内容简介:在最开始准备制作小程序的时候,为了求开发的快速,所使用了直接page的引入方式,每次小程序加载都是全包下载。3月份之前小程序页面稳定在35个,下载耗时在1800ms左右,4月份初上线了新业务页面膨胀到52个页面时,下载耗时基本稳定在2300ms左右,随着用户反馈越来越多,所以开始准备小程序采用分包加载,来解决这个问题。5月份在分包加载之后,页面依旧稳定在52个,但是下载耗时稳定800ms左右,缩短了将近1500ms。基本做到了0业务入侵、加入npm script 来实现新建页面,同时兼容分包加载,page加
在最开始准备制作小程序的时候,为了求开发的快速,所使用了直接page的引入方式,每次小程序加载都是全包下载。3月份之前小程序页面稳定在35个,下载耗时在1800ms左右,4月份初上线了新业务页面膨胀到52个页面时,下载耗时基本稳定在2300ms左右,随着用户反馈越来越多,所以开始准备小程序采用分包加载,来解决这个问题。5月份在分包加载之后,页面依旧稳定在52个,但是下载耗时稳定800ms左右,缩短了将近1500ms。基本做到了0业务入侵、加入npm script 来实现新建页面,同时兼容分包加载,page加载。
操作流程
执行命令
npm run new 复制代码
弹出单选,并且输入路径
[Info] 帮助你快速创建page文件 [Info] 只局限于新目录创建 [Info] 请输入文件路径,已帮你省略pages/ [Info] 例子:address/addressList ? 请输入文件路径: 复制代码
输入路径之后创建文件的提示
[Info] 已创建base64.js [Info] 已创建index.js [Info] 已创建index.json [Info] 已创建index.wxml [Info] 已创建index.wxss [Info] 创建完成!!!! 复制代码
选择包,是主包、还是分包,并且提示,如果是tabBar中使用,只能选择主包,并且手动配置app.json的tabBar
? 你想生成什么样的包: (Use arrow keys) ❯ 主包,在pages中写入,如果是tabBar中使用,只能选择主包,并且手动配置app.json的tabBar 分包,在subpackages中写入 复制代码
单选完成之后提示新建成功
[Info] 写入成功 复制代码
第一次分包
首先我们对未分包和分包的app.json代码进行对比
// 未分包 "pages": [ "pages/a/index", "pages/b/index", "pages/b/list", ] 复制代码
// 实现分包 "subpackages": [ { "root": "pages/a", "pages": [ "index", ] }, { "root": "pages/b", "pages": [ "index", "list" ] } ] 复制代码
接下来看目录结构
pages |---- a |---- index.js |---- b |---- index.js |---- list.js app.json 复制代码
简单分一下,第一次分包的核心业务是,将pages这个普通列表,变成名为subpackages的二叉树状态结构,左子节点是root,右子节点是pages,并且右子节点是string类型,左子节点是array类型。好吧,我可以定下规则,首先我可以将 pages/a/index
这个字符串变为数组以 /
为分界的 ['pages','a','index']
数组,然后我将前两项作为root节点的参数,用slice操作截取出来,转为字符串类型,赋值给root。之后的所有参数,我都赋值或者push给pages参数,最后我就得到了这样一个符合要求的树,最后push进subpackages即可。至于数据源,我只需要读出app.json这个文件的pages参数,并且通过我的方法写入subpackages参数即可。
要点
- 去重,因为subpackages内,root节点是唯一的,但是在pages里面可能我会截取初重复的值
解决
- 使用Map结构,把截取出的root值作为key,剩下的作为value,Map数据这种数据结构的特点就是key值唯一。
let list = [ "pages/a/index", "pages/a/list", "pages/a/detail/index", "pages/c/list", "pages/b/index", ]; let m = new Map(); let packages = []; list.forEach(v=>{ let arr = v.split('/'); let root = arr.splice(0,2).join('/'); let pages = arr.join('/'); if(m.get(root)){ let s = m.get(root); m.set(root,[...s, pages]); }else{ m.set(root,[pages]); }; }); for(let [key,value] of m){ packages.push({ root: key, pages: value, }) } console.log(packages); 复制代码
// log 出的结构 [ { root: 'pages/a', pages: [ 'index', 'list', 'detail/index' ] }, { root: 'pages/c', pages: [ 'list' ] }, { root: 'pages/b', pages: [ 'index' ] } ] 复制代码
我已经在不入侵业务的情况下实现了小程序的第一次分包,节约了我手动去改的劳动力,我个人认为,解决问题,上策用数据结构,中策写兼容代码,下策手动去改。至于,读出写入文件,我就不赘述了,google即可。
创建pages的指令编写
好吧,我实现了第一次的分包。然后我要思考,如果我每次要加页面的话,是不是就要去查看subpackages,找到对应的root,并且添加pages。这么重复劳动力的操作,我为什么不用脚本替代呢,是吧。
核心需求
- 编写交互式的命令输入
- 检测输入的page是否存在
- 不存在创建page目录,复制template
- 根据用户选择,选择写入pages,或者写入subpackages
- 在page文件加中预留componet文件夹,用做业务拆分,不加检测page是否存在的方法
- 引入静态文件,保存pages级别的目录,也不被检测
设计npm script
{ "subcontract": "node ./config/subcontract", "new": "node ./config/new" } 复制代码
添加的package.json参数
{ "ignore-files": [ "**/common/**", "**/component/**", "<name>/**", "<name>/**", "<name>/**", ], "pages": [ "pages/<name>/index", "pages/<name>/index", "pages/<name>/index", ] } 复制代码
需求已经明确,我就要去找我需要用到的npm包了
colors 命令行颜色 inquirer 交互式命令行 glob 全局搜索文件 fs-extra 文件写入写出 path 路径 shelljs 执行 shell 命令 复制代码
分析 new.js
文件
const colors = require('colors'); const inquirer = require('inquirer'); const glob = require('glob'); const fs = require('fs-extra'); const path = require('path'); const shell = require('shelljs'); const PKG = require('../package.json'); const ROOT = path.resolve(__dirname, '../'); let appJson = require('../app.json'); const promps = [{ type: 'input', name: 'pagePath', message: '请输入文件路径:', }, { type: 'list', name: 'type', message: '你想生成什么样的包:', choices: [ { name: '主包,在pages中写入,如果是tabBar中使用,只能选择主包,并且手动配置app.json的tabBar', value: '1', }, { name: '分包,在subpackages中写入', value: '2', }, ], }]; const logger = { info(msg) { console.log(`[Info] ${colors.green(msg)}`); }, warn(msg) { console.log(`[Warn] ${colors.yellow(msg)}`); }, error(msg) { console.log(`[Error] ${colors.red(msg)}(/‵Д′)/~ ╧╧`); }, }; logger.info('帮助你快速创建page文件'); logger.info('只局限于新目录创建'); logger.info('请输入文件路径,已帮你省略pages/'); logger.info('例子:xxxxx/xxxx'); 复制代码
这是代码中的常量部分和默认提示部分,我写了logeer对象来作为提示输出的默认颜色,promps作为我交互命令行的基础配置。引入package.json我的主要目的是因为我屏蔽了一些文件 ignore-files
和 pages
,想这两个参数的文件夹我是不会检测的。
function checkFile(name) { const options = { ignore: [ '**/*.js', '**/*.wxss', '**/*.wxml', '**/*.json', ], cwd: 'pages/', }; const files = glob.sync('**', options); if (files.some((v) => v === name)) { logger.error('输入的目录已经存在,已终止!!!!'); return false; }; return name; }; 复制代码
检查文件夹是否存在这是检测文件是否存在的方法,我只需要得到路径,即可检测这个路径是否在目录中存在。
function buildFile(name) { const options = { cwd: 'template/page', }; const files = glob.sync(`**`, options); files.forEach((v)=>{ const file = v.split('.tp')[0]; fs.copy(`${ROOT}/template/page/${v}`, `${ROOT}/pages/${name}/${file}`, (err) => { if (err) { console.error(err); return false; } }); logger.info(`已创建${file}`); }); logger.info('创建完成!!!!'); return true; }; 复制代码
这是复制文件夹并且复制模版文件的方法,我准备了tempalte这个文件夹,用来存储我写的模版文件,创建完成之后,我直接复制进去即可
function subcontract(res) { inquirer.prompt(promps[1]).then((answers)=>{ if (answers.type === '1') { PKG['ignore-files'].push(`${res}/**`); PKG['pages'].push(`${res}/index`); appJson['pages'].push(`pages/${res}/index`); fs.writeFileSync(`${ROOT}/app.json`, JSON.stringify(appJson, null, 2)); fs.writeFileSync(`${ROOT}/package.json`, JSON.stringify(PKG, null, 2)); logger.info('写入成功'); }; if (answers.type === '2') shell.exec('npm run subcontract'); }); }; 复制代码
这是选择pages还是subcontract的方法,选择了subcontract,我直接执行我上面写的小程序分包方法 subcontract.js
即可。如果选择pages,我会将它加入package.json中的 ignore-files
对象,这个对象表式这些文件名不被subcontract脚本检测。
async function inquirers() { const {pagePath} = await inquirer.prompt(promps[0]); const path = pagePath.replace(/\s+/g, ''); if (!path) { logger.error('输入有失误,已终止!!!!'); return false; }; if (/.*[\u4e00-\u9fa5]+.*$/.test(path)) { logger.error('请不要输入中文符号,已终止!!!!'); return false; }; return path; }; 复制代码
检测输入值是否合法,并且去除空格
( async function() { const inquirerRes = await inquirers(); const checkFileRes = inquirerRes && checkFile(inquirerRes); const buildFileRes = checkFileRes && buildFile(checkFileRes); buildFileRes && subcontract(checkFileRes); })(); 复制代码
最后组装,inquirerRes变量负责判断输入值是否正确。然后进入checkFile,来检测文件夹是否重复。调用buildFile方法,创建文件夹,复制模版文件。最后调用subcontract来判断是分包还是主包。
subcontract.js 分析
const glob = require('glob'); const fs = require('fs'); const path = require('path'); const colors = require('colors'); const ROOT = path.resolve(__dirname, '../'); const PAG = require('../package.json'); let appJson = require('../app.json'); const ignoreFiles = PAG['ignore-files']; const pages = PAG['pages']; const logger = { info(msg) { console.log(`[Info] ${colors.green(msg)}`); }, warn(msg) { console.log(`[Warn] ${colors.yellow(msg)}`); }, error(msg) { console.log(`[Error] ${colors.red(msg)}(/‵Д′)/~ ╧╧`); }, }; const subcontract = () => { const options = { ignore: ignoreFiles, cwd: 'pages/', }; const files = glob.sync('**/index.js', options); let subcontractMap = new Map(); files.forEach((v)=>{ let arr = v.split('.')[0].split('/'); let root = arr.shift(); let page = arr.join('/'); if (subcontractMap.has(root)) { let pages = subcontractMap.get(root); pages.push(page); subcontractMap.set(root, pages); } else { subcontractMap.set(root, [page]); } }); let subcontractList = []; subcontractMap.forEach((v, k)=>{ subcontractList.push({ root: `pages/${k}`, pages: v, }); }); return subcontractList; }; appJson.subpackages = subcontract(); appJson.pages = pages; fs.writeFileSync(`${ROOT}/app.json`, JSON.stringify(appJson, null, 2)); logger.info('写入成功'); 复制代码
这个方法其实和小程序第一次分包的方法大同小异。只不过我修改了数据源的获取,第一次我是读取app.json的pages,这里我是根据目录来的,以及加入了ignoreFiles来做文件屏蔽。以及一些友好提示。
结尾
项目优化的道路还有很长这只是最最初步的方案。为什么我开始不直接选择分包呢?因为项目开始的时候还没有分包,而且如果有,我感觉分包机制可能会带给开发者出错的可能性,我为了将项目工期缩短,出错可能性降低,我也不会选择一开始就分包。当业务增长到一定量,以及业务逐渐趋向于稳定时候,我就可以根据业务的特性,去做相对应的事情。这种方式我称之为技术迭代。在何时的时候,选择何时的业务,坚决不过度设计。到此为止,其实我最想输出的是思路。我关于上中下,三策的理解,我对业务的理解。谢谢。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- mpvue 分包方案
- 小程序系列--如何使用分包加载
- 小程序多业务线融合【完整分包业务接入】
- 『互联网架构』软件架构-netty粘包分包编码解码(57)
- HashMap为何从头插入改为尾插入
- postgresql – 在postgres中将表列名更改为大写
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。