构建工具
使用rollup进行项目打包
思路整理
发版流程:项目打包 -> push到oss -> 自动化部署
一开始设想的是在上传到OSS的这一步去操作,拿到Etag去判断上传的文件是否被修改了。但是发现响应头里没带上Etag(现在想想有可能是nginx或者oss关于Etag的配置没开启),之后看到了 juejin.cn/post/749463… 这位大佬的文章,发现在项目打包阶段直接对dist目录里的文件内容哈希化,生成文件存储哈希值,比对哈希值就可以实现发版通知。
实现步骤
1. 生成版本文件
首先是读取打包目录下的index.html和public文件夹下的所有静态资源。
可能会疑惑为什么要读取public文件夹下的静态资源,只读index.html不行吗?
这是因为public文件夹不经过rollup的构建钩子,在打包出来之后也可以看到public文件夹下的文件是完全原封不动的,没有被处理过。因此,只读取index.html是无法覆盖所有情况的。
/**
* 用于读取index.html文件信息,创建版本号文件
*/
function createVersionFile(){
//读取dist下index.html的文件内容转化成utf-8的字符串
const html = fs.readFileSync('dist/index.html', 'utf-8');
//使用crypto生成MD5哈希,将html注入哈希计算,并设置生成16进制格式的哈希值
const htmlHash = crypto.createHash('md5').update(html).digest('hex');
//计算dist/public文件夹下的所有静态资源的哈希值
const assetsHash = calcDirHash('dist/public');
//生成最终的版本哈希值
const versionHash = crypto.createHash('md5').update(htmlHash).update(assetsHash).digest('hex');
//将生成的哈希值存入dist下的version.json,记录版本变化
fs.writeFileSync('dist/version.json', JSON.stringify({ version: versionHash}));
}
/**
* 计算指定文件夹内所有文件的哈希值
* @param dir {String} - 指定文件夹
*/
function calcDirHash(dir){
// 创建MD5哈希
const hash = crypto.createHash('md5');
const filesContent = [];
const traverse = (currentDir) => {
//读取当前目录下的所有文件
const files = fs.readdirSync(currentDir);
files.forEach(file => {
//获取文件完整路径
const filePath = path.join(currentDir,file);
//获取文件信息
const stat = fs.statSync(filePath);
//如果是文件夹
if(stat.isDirectory()){
//递归执行
traverse(filePath);
}
//如果不是文件夹
else{
//读取文件信息并push到filesContent
const fileContent = fs.readFileSync(filePath);
filesContent.push({ path: filePath,content: fileContent });
}
})
};
traverse(dir);
//使用localeCompare本地化排序,排序后的文件比较符合直觉
filesContent.sort((a, b) => a.path.localeCompare(b.path))
//遍历filesContent,更新到hash
filesContent.forEach(({ content }) => {
hash.update(content)
})
//返回混合所有文件的16进制哈希
return hash.digest('hex')
}
在vite.config.js的defineConfig.build.rollupOptions.output中
plugins: [
{
name: 'createVersion',
// hook: 打印最终构建结果前
writeBundle: () => {
createVersionFile();
}
}
]
2. 轮询比对版本文件
新建一个js文件,引入到App.vue里
import {env} from '@/view/api/server.js'
(async () => {
if(env === 'production'){
await nextTick(async() => {
//轮询时间间隔60 * 1000代表一分钟
const CHECK_INTERVAL = 1000
//先拿一遍版本号,用于和轮询的做对比
const version = await getVersion();
//获取版本号
async function getVersion(){
try {
const time = dayjs().unix();
const res = await fetch(`/version.json?t=${ time }`)
return (await res.json()).version
}catch(e){
console.error("获取版本号失败:" + e.message);
}
}
//轮询检查版本号
async function pollingCheckVersion(){
try {
const pollingVersion = await getVersion();
if(version !== pollingVersion){
throw new Error('版本号不一致,请刷新页面');
}
}catch(e){
console.error(e.message);
$message.warning({
content: '版本更新啦,请刷新以更新页面'
})
}
}
setInterval(pollingCheckVersion,CHECK_INTERVAL)
})
}
})();
补充
其实如果不在意public文件夹下的文件与每次build出来的哈希都会变化的话,实际上是不需要去生成这样一个version文件的,完全可以直接读取index.html的Last-Modified的,代码的实现也简单很多,只需要轮询即可。