小程序构建
获取环境参数
设计核心:
- 环境参数两种获得方式:
- 手动输入环境:用于本地执行命令,生成环境文件
- gitlab tag:用于发布构建
- tag 控制,保证流程稳定
- 环境文件 ignore,避免互相冲突和手动修改影响源码仓库
源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const args = [...process.argv]; let jsEnv = "";
try { jsEnv = getEnv().env; } catch { jsEnv = args[2] || "online"; }
const scssEnv = args[3] || jsEnv;
logger.info("生成环境配置..."); fs.writeFileSync("_env.js", `export default '${jsEnv}'`); fs.writeFileSync( "scss/_env.scss", ` $env: "${scssEnv}"; ` );
|
通过 fs 模块分别写入 js 文件和 scss 文件,同时将文件配置到 gitignore 中
getEnv 核心逻辑:
1 2 3 4 5 6 7 8 9 10 11
| const args = [...process.argv]; const tag = args[2] || ""; const tagMatches = tag.match(/^(\w+)-(\d{1,2})-(\d+.\d+.\d+)$/); if ( !["test", "pre", "online"].includes(env) || !(+robot >= 1 && +robot <= 30) ) { throw new Error( "请填写正确的环境版本号,格式为:环境-机器人编号-版本号,如: online-1-1.0.0" ); }
|
通过正则匹配 tag 格式,对非法的 tag 进行过滤。
preview 与 upload
核心设计:
- 工厂模式初始化 ci.Project
- 移除无依赖文件
- 递归移除空目录
- 不操作源码,输出移除日志,源码改动需要手动修改,不能使用脚本移除
源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const projectAnalyse = createCiProject();
let usedFilePath: string[] = [];
const anaRes = await ci.analyseCode(projectAnalyse);
if (anaRes) { usedFilePath = anaRes.modules.map((o) => o.path); } const uselessFiles = klawSync("./dist", { nodir: true, traverseAll: true, filter: ({ path: curPath }: { path: string }) => { return ( !curPath.startsWith(path.resolve("./dist/icons")) && !usedFilePath.includes( curPath.replace(path.resolve("./dist"), "").replace(/\\/g, "/").slice(1) ) ); }, }).map((o) => o.path);
fs.writeJSON("analyse.json", { uselessFiles });
uselessFiles.forEach((p) => { logger.chalk(`${chalk.yellow("删除无依赖文件: ")}${chalk.green(p)}`); fs.removeSync(p); });
|
klawSync 用于递归遍历文件夹,对比小程序 ci 工具分析的依赖文件和当前文件是否一致,收集非依赖文件(注意需要排除小程序本地引入的图片)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const removeUselessDirs = () => { const uselessDirs = klawSync("./dist", { nofile: true, traverseAll: true, filter: ({ path: curPath, stats }) => { if (stats.isDirectory()) { const files = fs.readdirSync(curPath); return files.length === 0; } else { return false; } }, }).map((o) => o.path); uselessDirs.forEach((p) => { logger.chalk(`${chalk.yellow("删除空文件夹: ")}${chalk.green(p)}`); fs.removeSync(p); }); return uselessDirs; };
let retry = 10; let hasUseless = true; while (hasUseless && retry--) { hasUseless = removeUselessDirs().length !== 0; }
|
删除目录需要多次递归目录,因为递归顺序是从外到内,若子目录被删除,在下次遍历时才能检测到目录为空,需要控制遍历次数避免执行时间过长。
(其实这步也可以不用做,强迫症觉得留着空文件夹不舒服。。。)
1
| const projectAnalyse = createCiProject();
|
初始化 ci 工具需要两次,在做文件分析后需要重新初始化而不能使用之前的实例,因为 ci 初始化后就记录了工作目录下的所有文件,当做了无依赖文件删除处理后,在使用同一实例上报会出现错误
任务调度,TASKS
一套完整的 CI 流程需要执行非常多的逻辑,以及可能会存在很多分支流程,为了保证执行过程稳定可追踪,需要将流程的每一步骤拆分成独立的最小单元,也就是 TASKS,
每个任务可以单独运行,也可以组合成一个新的任务。
run.ts 负责所有任务的执行和执行情况收集,实时打印任务进度和执行时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export default async function run( task: { name: string; func: Function }, options?: any ) { const start = new Date(); logger.info( `Starting '${task.name}${ options ? ` (${JSON.stringify(options)})` : "" }'...` ); const res = task.func(options); if (res instanceof Promise) { await res; } const end = new Date(); const time = end.getTime() - start.getTime(); logger.info( `Finished '${task.name}${ options ? ` (${JSON.stringify(options)})` : "" }' after ${time} ms` ); }
|
注意如果是异步任务,时间的计算需要等到异步执行完毕。