小程序发布自动化

小程序构建

获取环境参数

设计核心:

  1. 环境参数两种获得方式:
    1. 手动输入环境:用于本地执行命令,生成环境文件
    2. gitlab tag:用于发布构建
  2. tag 控制,保证流程稳定
  3. 环境文件 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

核心设计:

  1. 工厂模式初始化 ci.Project
  2. 移除无依赖文件
  3. 递归移除空目录
  4. 不操作源码,输出移除日志,源码改动需要手动修改,不能使用脚本移除

源码分析

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`
);
}

注意如果是异步任务,时间的计算需要等到异步执行完毕。


小程序发布自动化
https://www.wobushi.top/2022/小程序发布自动化/
作者
Pride Su
发布于
2022年10月28日
许可协议