闺蜜圈 小程序版本一直落后很多,之所以没更新主要的问题在于 uni 打包小程序之后体积太大了,体积大一个原因是组件压缩到了 vendor.js 中,一个文件就到了 1.1m(主包限制大小2048kb)。
图片文件也有1m 左右,再加上其他的一些组件,主包的体积到了 4 m 左右。
虽然已经启用了分包,但是没啥效果,包括代码压缩组,所以最后发版的小程序靠的是压缩图片文件。
让 cursor 尝试写了个优化代码出了各种错误,最后决定采用将图片资源直接网络加载的方式来缩减体积,这样1m 的图片资源就不需要打包在本地资源中了。
本地资源文件都是通过 127 的地址来加载的,将资源移动到服务器之后,修改小程序资源地址之后:
此时加载的图片可以看到是从 cdn 加载了,
并且资源包大小已经基本可以忽略不计了
要实现上看的效果也简单,将static 目录上传到服务器,执行修改工具,修改资源路径删除本地资源。对于 tabbar 的图片不能通过网络加载,需要添加到排除列表,hbuilder 发行小程序后执行修改工具。此时基本就 ok 了,2.06mb,缺的那一点稍微弄一下也就解决了。
工具代码:
/**
* 将打包后产物里的 /static/** 路径替换为 CDN 前缀。
* 目前是替换为 https://cdn.guimiquan.cn/ 前缀。
* 使用方法:
* 1) 先发行构建微信小程序,生成 unpackage/dist/build/mp-weixin 或 unpackage/dist/dev/mp-weixin
* 2) 执行:node cdn-rewrite.js [--mode=dist|dev] [--remove-static]
* 3) 在 dist 内搜索或用开发者工具 Network 确认已变成 CDN 域名
*
* 参数说明:
* --mode=dist : 处理生产构建目录 (默认)
* --mode=dev : 处理开发构建目录
* --remove-static : 删除本地 static 目录(排除配置的目录和文件)
* By: obaby
* Date: 2025-12-12
* Version: 1.0.0
* https://oba.by
* https://h4ck.org.cn
* ------------------------------------------------------------
*/
const fs = require('fs');
const path = require('path');
// CDN 根路径,末尾带 /
const CDN = 'https://cdn.guimiquan.cn/';
// 路径配置
const DIST_ROOT = path.resolve(__dirname, 'unpackage/dist/build/mp-weixin');
const DEV_ROOT = path.resolve(__dirname, 'unpackage/dist/dev/mp-weixin');
// 处理的文件类型
const ALLOWED_EXTS = new Set(['.js', '.json', '.wxss', '.css', '.wxml', '.html']);
// 跳过的文件(app.json 里的 tabBar iconPath 不允许 http/https)
const SKIP_FILES = new Set(['app.json']);
// 排除删除的目录(相对于 static 目录)
const EXCLUDE_DIRS = [
'tabbar_icons', // tab栏图标必须使用本地文件
// 可以在这里添加更多需要排除的目录
];
// 排除删除的文件(相对于 static 目录,支持 glob 模式或完整路径)
const EXCLUDE_FILES = [
// 可以在这里添加需要排除的文件,例如:
// 'tabbar_icons/**/*',
// 'custom-icon.png',
'icons/record_love_add.png',
'icons/calendar_icon_project_start.png',
'icons/calendar_icon_project_end.png',
'icons/calendar_icon_project_start_invalid.png',
'apk_emotion_2.png',
'apk_emotion_1.png',
'apk_emotion_38.png',
'apk_emotion_9.png',
'apk_emotion_28.png',
];
// 解析命令行参数
function parseArgs() {
const args = {
mode: 'dist', // 默认使用 dist
removeStatic: false
};
process.argv.slice(2).forEach(arg => {
if (arg.startsWith('--mode=')) {
const mode = arg.split('=')[1];
if (mode === 'dist' || mode === 'dev') {
args.mode = mode;
} else {
console.warn(`警告: 未知的模式 "${mode}", 使用默认模式 "dist"`);
}
} else if (arg === '--remove-static') {
args.removeStatic = true;
}
});
return args;
}
// 获取目标根目录
function getTargetRoot(mode) {
return mode === 'dev' ? DEV_ROOT : DIST_ROOT;
}
function replaceInFile(file, targetRoot) {
const source = fs.readFileSync(file, 'utf8');
let output = source;
// 处理 JS/JSON 中的字符串形式 "static/xxx" 或 "/static/xxx"
output = output.replace(/(["'])\/?static\//g, `$1${CDN}static/`);
// 处理样式中的 url(static/xxx) 或 url('/static/xxx')
output = output.replace(/url\(\s*(['"]?)\/?static\//g, `url($1${CDN}static/`);
if (output !== source) {
fs.writeFileSync(file, output, 'utf8');
console.log('rewrote', path.relative(targetRoot, file));
}
}
function walk(dir, targetRoot) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full, targetRoot);
} else if (ALLOWED_EXTS.has(path.extname(entry.name)) && !SKIP_FILES.has(entry.name)) {
replaceInFile(full, targetRoot);
}
}
}
// 检查路径是否应该被排除
function shouldExclude(filePath, staticRoot) {
const relativePath = path.relative(staticRoot, filePath);
const normalizedPath = relativePath.replace(/\\/g, '/'); // 统一使用 / 分隔符
// 检查是否在排除目录中
for (const excludeDir of EXCLUDE_DIRS) {
if (normalizedPath.startsWith(excludeDir + '/') || normalizedPath === excludeDir) {
return true;
}
}
// 检查是否匹配排除文件模式
for (const excludeFile of EXCLUDE_FILES) {
// 简单的 glob 匹配(支持 * 和 **)
const pattern = excludeFile.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
const regex = new RegExp('^' + pattern + '$');
if (regex.test(normalizedPath)) {
return true;
}
// 精确匹配
if (normalizedPath === excludeFile) {
return true;
}
}
return false;
}
// 删除本地 static 目录(排除指定目录和文件)
function removeLocalStatic(targetRoot) {
const staticDir = path.join(targetRoot, 'static');
if (!fs.existsSync(staticDir)) {
console.log('static 目录不存在:', staticDir);
return;
}
let deletedCount = 0;
let skippedCount = 0;
function removeRecursive(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (shouldExclude(fullPath, staticDir)) {
skippedCount++;
console.log('跳过(排除):', path.relative(staticDir, fullPath));
continue;
}
if (entry.isDirectory()) {
removeRecursive(fullPath);
// 目录为空时才删除
try {
fs.rmdirSync(fullPath);
deletedCount++;
} catch (err) {
// 目录不为空,忽略错误
}
} else {
fs.unlinkSync(fullPath);
deletedCount++;
}
}
}
removeRecursive(staticDir);
// 如果 static 目录为空,尝试删除它
try {
const remaining = fs.readdirSync(staticDir);
if (remaining.length === 0) {
fs.rmdirSync(staticDir);
console.log('已删除空的 static 目录');
} else {
console.log(`static 目录保留,包含 ${remaining.length} 个排除项`);
}
} catch (err) {
// static 目录已被删除或无法访问
}
console.log(`删除完成: 已删除 ${deletedCount} 项, 跳过 ${skippedCount} 项`);
}
// 主函数
function main() {
const args = parseArgs();
const targetRoot = getTargetRoot(args.mode);
console.log(`模式: ${args.mode}`);
console.log(`目标目录: ${targetRoot}`);
if (!fs.existsSync(targetRoot)) {
console.error('目标目录不存在:', targetRoot);
process.exit(1);
}
walk(targetRoot, targetRoot);
console.log('路径替换完成');
if (args.removeStatic) {
console.log('\n开始删除本地 static 目录...');
removeLocalStatic(targetRoot);
} else {
console.log('\n提示: 使用 --remove-static 参数可删除本地 static 目录');
}
console.log('\n完成');
}
main();
使用方法,放到项目根目录下,打包之后执行:
node cdn-rewrite.js [--mode=dist|dev] [--remove-static]








19 comments
难得抢到沙发
这是一个人,干了一个团队的活啊!继续加油坚持下去
嗯嗯
不错,反正 app 也是要连服务器才能工作,图片放服务器也可以的
这体积限制太严格了,没办法
昨天网站是不是有一小段时间无法访问呀,过来显示错误
是的,昨天eo的自动证书过期了,多次尝试重新申请都失败了,卡在部署中。



后来去腾讯云申请免费的ssl证书依然失败,最开始显示域名认证通过,
后期处理证书的时候又成了认证失败
尝试多次之后依然失败,直接提了个工单,被告知不支持这个后缀申请免费域名:
其实,我也阿里试了,也是失败。所以我怀疑是lets encrypt禁止了免费域名。我用acme.sh申请提示是不支持这个后缀。
最后用的freessl的免费域名,暂时先用着吧。看看后期怎么自动化处理一下。
不容易,前两条我有个宝塔的自动续签失败了,神奇得很
好像微信小程序对包的大小有限制,我以前弄商城小程序的时候遇到过
是的,主包和总大小都有限制
这js也太大了吧,是unaipp编译后的产物吗
是的,感觉把各种组件也打包进去了
膜拜大佬
小程序还有体积限制啊?话说APP我启动后提示有新版本,点击下载结束后不跳出安装,是因为缺少哪个权限吗?
google的包没有install 权限,去play store 更新一下版本。
不明觉厉
我足足看着评论框右侧的小姐姐跳了几分钟,看她没有停下来的意思。☺️☺️不看了,打个水卡好跑路。