refactor: 统一版本信息注入方式

- feat(vue.config): 新增 VersionInjectPlugin,在 webpack emit 钩子中内联注入 __app_version 到 vendor.js / index.html
- refactor(scripts): mp-weixin.patch.js 精简为仅拷贝 site.js + ZIP 打包
- feat(index): 首页连击5次显示构建版本信息
- fix(util): 修复 openWxWorkServiceChat fail 回调语法错误
This commit is contained in:
2026-05-11 09:36:57 +08:00
parent 979384c2d6
commit 3bff91e421
4 changed files with 145 additions and 159 deletions

View File

@@ -62,5 +62,5 @@ const localDevConfig = ({
uniacid: 2075, uniacid: 2075,
domain: 'https://xcx.aigc-quickapp.com/', domain: 'https://xcx.aigc-quickapp.com/',
}, },
})['47']; // 选择要使用的环境配置 })['2811']; // 选择要使用的环境配置
export default localDevConfig; export default localDevConfig;

View File

@@ -111,49 +111,19 @@ export default {
}, 2000); }, 2000);
}, },
_showVersionInfo() { _showVersionInfo() {
// #ifdef MP-WEIXIN let v = null;
try { try {
const fs = uni.getFileSystemManager(); v = typeof __app_version !== 'undefined' ? __app_version : null;
fs.readFile({
filePath: 'version.json',
encoding: 'utf-8',
success: (res) => {
try {
const v = JSON.parse(res.data);
if (v && v.commitId) {
uni.showModal({
title: '构建版本信息',
content: `提交 ID: ${v.commitId}\n构建时间: ${v.buildTime}`,
showCancel: false
});
return;
}
} catch (e) {}
uni.showModal({ title: '构建版本信息', content: '版本信息不可用', showCancel: false });
},
fail: () => this._showVersionFromGlobal()
});
} catch (e) {
this._showVersionFromGlobal();
}
// #endif
// #ifndef MP-WEIXIN
this._showVersionFromGlobal();
// #endif
},
_showVersionFromGlobal() {
try {
const v = typeof __app_version !== 'undefined' ? __app_version : null;
if (v && v.commitId) {
uni.showModal({
title: '构建版本信息',
content: `提交 ID: ${v.commitId}\n构建时间: ${v.buildTime}`,
showCancel: false
});
return;
}
} catch (e) {} } catch (e) {}
uni.showModal({ title: '构建版本信息', content: '版本信息不可用', showCancel: false }); if (v && v.commitId) {
uni.showModal({
title: '构建版本信息',
content: `提交 ID: ${v.commitId}\n构建时间: ${v.buildTime}`,
showCancel: false
});
} else {
console.log('未获取到构建版本信息');
}
} }
} }
} }

View File

@@ -2,64 +2,38 @@
/** /**
* fix-wechat-miniapp.js * fix-wechat-miniapp.js
* *
* 该脚本用于修复微信小程序的一些问题: * 该脚本用于微信小程序的构建后处理(仅在生产构建时由 vue.config.js 触发):
* 1. 将项目根目录下的 site.js 文件拷贝到 unpackage/dist/build/mp-weixin 目录下,如果文件存在则覆盖 * 1. 将 project.config.json 及 project.private.config.json 拷贝到构建产物目录
* 2. 将 project.config.json 及 project.private.config.json 拷贝到目标目录 * 2. 将 site.js 拷贝到构建产物目录
* 3. 生成 version.js(CommonJS 模块)和 version.json 到目标目录 * 3. 创建 ZIP 压缩包
* 4. 将 `const site = require("../site");` 和 `const __app_version = require("../version");` *
* 追加到 unpackage/dist/build/mp-weixin/common/vendor.js 文件内容开头部分, * 注意:version.js 生成和 vendor.js 注入已由 webpack 插件 (VersionInjectPlugin) 在 emit 钩子中自动处理。
* 如果文件开头已有则跳过
* *
* 使用: * 使用:
* node fix-wechat-miniapp.js # 打补丁并创建 ZIP 文件(默认 mode=production) * node fix-wechat-miniapp.js # 拷贝文件并创建 ZIP(默认 mode=production)
* node fix-wechat-miniapp.js --no-zip # 只打补丁,不创建 ZIP 文件 * node fix-wechat-miniapp.js --no-zip # 只拷贝文件,不创建 ZIP
* node fix-wechat-miniapp.js --mode development # 使用 development 模式打补丁
* node fix-wechat-miniapp.js --mode production # 使用 production 模式打补丁(默认)
*
* 注意:
* - 在 Windows 上路径使用反斜杠也是可以的;脚本使用 path.join 来兼容不同平台。
* - 运行前请确认在项目根目录运行(site.js 在当前工作目录下)。
*/ */
const fs = require('fs'); const fs = require('fs');
const fsp = fs.promises; const fsp = fs.promises;
const path = require('path'); const path = require('path');
const { execSync } = require('child_process');
const { openFileDirectory } = require('./open-directory'); const { openFileDirectory } = require('./open-directory');
const { createZipWithSystemCommand } = require('./create-zip'); const { createZipWithSystemCommand } = require('./create-zip');
async function commonPatch(mode = 'production') { async function copyAssets(mode = 'production') {
try { try {
// 根据当前脚本所在目录(scripts),定位到项目根目录
const cwd = path.join(__dirname, '..'); const cwd = path.join(__dirname, '..');
const srcSitePath = path.join(cwd, 'site.js');
const destDir = path.join(cwd, 'unpackage', 'dist', mode === 'production' ? 'build' : 'dev', 'mp-weixin'); const destDir = path.join(cwd, 'unpackage', 'dist', mode === 'production' ? 'build' : 'dev', 'mp-weixin');
const destSitePath = path.join(destDir, 'site.js');
const vendorPath = path.join(destDir, 'common', 'vendor.js');
// 1) 检查源文件是否存在
const srcExists = await exists(srcSitePath);
if (!srcExists) {
console.error(`源文件不存在: ${srcSitePath}`);
process.exitCode = 2;
return;
}
// 确保目标目录存在
await ensureDir(destDir); await ensureDir(destDir);
// 复制 project.config.json 及 project.private.config.json 文件到 destDir 下面 // 复制 project.config.json 和 project.private.config.json
const configFiles = ['project.config.json', 'project.private.config.json']; const configFiles = ['project.config.json', 'project.private.config.json'];
for (const fileName of configFiles) { for (const fileName of configFiles) {
const srcPath = path.join(cwd, fileName); const srcPath = path.join(cwd, fileName);
const destPath = path.join(destDir, fileName); const destPath = path.join(destDir, fileName);
if (await exists(srcPath)) {
// 检查源文件是否存在
const fileExists = await exists(srcPath);
if (fileExists) {
await fsp.copyFile(srcPath, destPath); await fsp.copyFile(srcPath, destPath);
console.log(`已拷贝: ${srcPath} -> ${destPath}`); console.log(`已拷贝: ${srcPath} -> ${destPath}`);
} else { } else {
@@ -67,73 +41,15 @@ async function commonPatch(mode = 'production') {
} }
} }
// 复制 site.js 到目标目录(覆盖) // 复制 site.js
await fsp.copyFile(srcSitePath, destSitePath); const srcSitePath = path.join(cwd, 'site.js');
console.log(`已拷贝: ${srcSitePath} -> ${destSitePath}`); if (await exists(srcSitePath)) {
await fsp.copyFile(srcSitePath, path.join(destDir, 'site.js'));
// 3) 生成 version.js(包含 Git 提交 ID 和构建时间,CommonJS 格式供 require 使用) console.log(`已拷贝: ${srcSitePath} -> ${destDir}`);
let commitId = 'unknown'; } else {
try { console.error(`源文件不存在: ${srcSitePath}`);
commitId = execSync('git rev-parse --short HEAD', { cwd }).toString().trim(); process.exitCode = 2;
} catch (e) {
console.warn('无法获取 Git 提交 ID,使用 unknown 代替');
} }
const versionData = {
commitId,
buildTime: new Date().toISOString()
};
// 同时生成 version.js(供 require)和 version.json(供文件系统读取)
const versionJsPath = path.join(destDir, 'version.js');
await fsp.writeFile(versionJsPath, `module.exports = ${JSON.stringify(versionData, null, 2)};\n`);
console.log(`已生成: ${versionJsPath} (commitId: ${commitId})`);
const versionJsonPath = path.join(destDir, 'version.json');
await fsp.writeFile(versionJsonPath, JSON.stringify(versionData, null, 2));
console.log(`已生成: ${versionJsonPath} (commitId: ${commitId})`);
// 4) 处理 vendor.js:在开头追加 require 语句(如果还没有的话)
const vendorExists = await exists(vendorPath);
if (!vendorExists) {
console.warn(`⚠ 目标 vendor.js 不存在,跳过追加 require。路径: ${vendorPath}`);
console.warn(' → 请先执行 HBuilder 构建(uniapp 构建),产生 vendor.js 后再运行此脚本。');
console.warn(' → version.json/version.js 已生成,可在首页连击 5 次标题查看版本信息。');
return;
}
let vendorContent = await fsp.readFile(vendorPath, 'utf8');
const requireLines = [
'const site = require("../site");',
'const __app_version = require("../version");'
];
// 清理已有的重复注入(兼容新旧格式:../version.json 和 ../version)
let cleaned = false;
const injectPatterns = [
/^\s*const\s+site\s*=\s*require\s*\(['"]\.\.\/site(\.js)?['"]\);?\s*\r?\n/gm,
/^\s*const\s+__app_version\s*=\s*require\s*\(['"]\.\.\/version(\.json)?['"]\);?\s*\r?\n/gm,
];
for (const pattern of injectPatterns) {
if (pattern.test(vendorContent)) {
vendorContent = vendorContent.replace(pattern, '');
cleaned = true;
}
}
// 把 require 语句 + newline 插入文件最前面。
// 保留原始换行风格(若可能)
const eol = vendorContent.includes('\r\n') ? '\r\n' : '\n';
let prefix = '';
// 保留可能的 BOM
if (vendorContent.charCodeAt(0) === 0xfeff) {
prefix = '\uFEFF';
vendorContent = vendorContent.slice(1);
}
const newContent = prefix + requireLines.join(eol) + eol + vendorContent;
await fsp.writeFile(vendorPath, newContent, 'utf8');
console.log(cleaned ? `已清理旧注入并重新注入: ${requireLines.join(', ')}` : `已在 vendor.js 开头追加: ${requireLines.join(', ')}`);
} catch (err) { } catch (err) {
console.error('发生错误:', err); console.error('发生错误:', err);
process.exitCode = 1; process.exitCode = 1;
@@ -141,33 +57,27 @@ async function commonPatch(mode = 'production') {
} }
async function main() { async function main() {
// 解析命令行参数
const argv = process.argv.slice(2); const argv = process.argv.slice(2);
const options = { const options = {
noZip: argv.includes('--no-zip'), noZip: argv.includes('--no-zip'),
mode: 'production' // 默认值 mode: 'production'
}; };
// 解析 --mode 参数
const modeIndex = argv.indexOf('--mode'); const modeIndex = argv.indexOf('--mode');
if (modeIndex !== -1 && modeIndex + 1 < argv.length) { if (modeIndex !== -1 && modeIndex + 1 < argv.length) {
options.mode = argv[modeIndex + 1]; options.mode = argv[modeIndex + 1];
} }
// 1) 打补丁 // 1) 拷贝文件
await commonPatch(options.mode); await copyAssets(options.mode);
// await commonPatch('development');
// 2) 创建 ZIP 文件(如果未指定 --no-zip) // 2) 创建 ZIP(仅 production 模式,且未指定 --no-zip)
if (!options.noZip) { if (!options.noZip && options.mode === 'production') {
const cwd = path.join(__dirname, '..'); const cwd = path.join(__dirname, '..');
const buildDir = options.mode === 'production' ? 'build' : 'dev'; const sourceDir = path.join(cwd, 'unpackage', 'dist', 'build', 'mp-weixin');
const sourceDir = path.join(cwd, 'unpackage', 'dist', buildDir, 'mp-weixin'); const destDir = path.join(cwd, 'unpackage', 'dist', 'build');
const destDir = path.join(cwd, 'unpackage', 'dist', buildDir);
const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir); const zipFilePath = await createZipWithSystemCommand(sourceDir, destDir);
console.log(`ZIP 文件路径: ${zipFilePath}`); console.log(`ZIP 文件路径: ${zipFilePath}`);
// 3) 自动打开zip所在的目录
await openFileDirectory(zipFilePath); await openFileDirectory(zipFilePath);
} else { } else {
console.log('跳过创建 ZIP 文件和打开目录'); console.log('跳过创建 ZIP 文件和打开目录');
@@ -187,7 +97,6 @@ async function ensureDir(dirPath) {
try { try {
await fsp.mkdir(dirPath, { recursive: true }); await fsp.mkdir(dirPath, { recursive: true });
} catch (e) { } catch (e) {
// ignore if exists
if (e.code !== 'EEXIST') throw e; if (e.code !== 'EEXIST') throw e;
} }
} }

View File

@@ -1,6 +1,9 @@
const path = require('path'); const path = require('path');
const { execSync } = require('child_process');
const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webpack'); const { zionUniMpLoadPackagePlugin } = require('zion-uniapp-mp-load-package/webpack');
console.log('[vue.config] 已加载');
// 打印出process.env // 打印出process.env
// for (let key in process.env) { // for (let key in process.env) {
// console.log(key, process.env[key]); // console.log(key, process.env[key]);
@@ -29,6 +32,107 @@ try {
console.log('NODE_ENV 环境变量未设置,默认使用开发模式'); console.log('NODE_ENV 环境变量未设置,默认使用开发模式');
} }
/**
* 自定义 webpack 插件:在通用产物(vendor.js)中内联注入版本数据
* 利用 emit 钩子在资源写入磁盘前处理,支持 HMR(每次重编译都会自动注入)
* 适用于所有平台(mp-weixin / h5 等)
*/
class VersionInjectPlugin {
constructor() {
this._cachedCommitId = null;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('VersionInjectPlugin', (compilation, callback) => {
const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
const platform = process.env.UNI_PLATFORM || '';
// 获取 Git 提交 ID(缓存,避免每次 HMR 都执行)
if (!this._cachedCommitId) {
try {
this._cachedCommitId = execSync('git rev-parse --short HEAD', { cwd: __dirname }).toString().trim();
} catch (e) {
this._cachedCommitId = 'unknown';
}
}
const versionData = { commitId: this._cachedCommitId, buildTime: new Date().toISOString() };
const inlineVersion = JSON.stringify(versionData);
// 尝试注入到 vendor.js(mp-weixin / mp-* 等平台)
const vendorKey = 'common/vendor.js';
const vendorAsset = compilation.assets[vendorKey];
if (vendorAsset) {
let content = typeof vendorAsset.source === 'function' ? vendorAsset.source() : vendorAsset._value;
if (typeof content !== 'string') content = content.toString();
// 清理旧注入
content = content.replace(/^\s*const\s+site\s*=\s*require\s*\(['"]\.\.\/site(\.js)?['"]\);?\s*\r?\n/gm, '');
content = content.replace(/^\s*const\s+__app_version\s*=\s*(require\s*\(['"]\.\.\/version(\.json|\.js)?['"]\)|.+);?\s*\r?\n/gm, '');
// 只注入版本信息(通用),site.js require 仅在 mp-weixin 生产构建时注入
const lines = [`const __app_version = ${inlineVersion};`];
if (platform === 'mp-weixin' && mode === 'production') {
lines.unshift('const site = require("../site.js");');
}
const eol = content.includes('\r\n') ? '\r\n' : '\n';
let prefix = '';
if (content.charCodeAt(0) === 0xfeff) {
prefix = '\uFEFF';
content = content.slice(1);
}
content = prefix + lines.join(eol) + eol + content;
compilation.assets[vendorKey] = {
source: () => content,
size: () => content.length
};
console.log(`[VersionInject] ✅ vendor.js 已注入 (platform=${platform}, mode=${mode}, commit=${this._cachedCommitId})`);
} else {
// H5 等平台可能没有 common/vendor.js,改为尝试注入到 index.html
// #ifdef H5
const htmlKey = 'index.html';
const htmlAsset = compilation.assets[htmlKey];
if (htmlAsset) {
let html = typeof htmlAsset.source === 'function' ? htmlAsset.source() : htmlAsset._value;
if (typeof html !== 'string') html = html.toString();
// 在 </head> 前注入 script 标签
const scriptTag = `<script>var __app_version = ${inlineVersion};</script>`;
if (!html.includes('__app_version')) {
html = html.replace('</head>', `${scriptTag}\n</head>`);
compilation.assets[htmlKey] = {
source: () => html,
size: () => html.length
};
console.log(`[VersionInject] ✅ index.html 已注入 (platform=${platform}, commit=${this._cachedCommitId})`);
}
}
// #endif
}
callback();
});
// 生产模式下(仅 mp-weixin),额外执行外部脚本完成 site.js 拷贝 + ZIP 打包
if (process.env.NODE_ENV === 'production') {
compiler.hooks.afterEmit.tapAsync('MpWeixinZipPack', (compilation, callback) => {
const platform = process.env.UNI_PLATFORM || '';
if (platform !== 'mp-weixin') return callback();
console.log('\n🚀 mp-weixin 生产构建完成,运行补丁脚本(site.js 拷贝 + ZIP 打包)...');
try {
execSync(`"${process.execPath}" scripts/mp-weixin.patch.js --mode production`, { cwd: __dirname, stdio: 'inherit' });
} catch (e) {
console.error('补丁脚本执行失败:', e.message);
}
callback();
});
}
}
}
// 导出配置 // 导出配置
module.exports = { module.exports = {
productionSourceMap: false, productionSourceMap: false,
@@ -118,5 +222,8 @@ module.exports = {
} }
} }
} }
// 所有模式下添加版本注入插件(适用于所有平台)
config.plugins.push(new VersionInjectPlugin());
} }
}; };