vue多页面项目实现版本快照功能示例详解
作者:渐行渐远 发布时间:2024-04-28 09:23:25
背景
App落地页迭代频繁,且需兼容App与各小App,目前是单向前进迭代,会存在以下问题:
跳转原生交互;如:某个落地页增加了只有主App的才有的原生功能,小App就无法投放;
历史版本归因与复用,历史测试版本的落地页都在主链上进行版本迭代,不利于落地页成功、失败版本的区分统计以及沉淀;
所以需实现一个快照版本的功能,将构建产物以特定的命名规则给保留下来,并且支持链接访问。可以解决已上问题;
若主App落地页迭代了新的原生功能,小App可以更换投放历史的链接,等小App支持了该原生功能,再恢复成主链接;
将每次迭代的链接利用管理后台维护,生成完资源后将changelog等信息上传至后台,方便产品管理。
需要解决的问题:
如何保存页面资源
只是项目中的几个页面需要支持这个功能,如何设计这个配置的开启与关闭
每次只生成改动的页面,实现增量生成。
实现思路:
在需要配置快照版本的页面新增配置文件,构建过程中获取到这些需要快照版本功能的入口,由于迭代可能只更新到其中的某个页面,还需要做到增量生成,利用hashchunk对比构建上一次与这一次是否一致,若一致,则删除,若不一致,则保留,最后以当前提交的commitid作为目录将产物保存至dist/目录中,一同上传服务器中。
实现过程:
资源命名;因为每次迭代都会生成新的commitid,符合唯一性与可追溯性,适合用于当资源目录。再以特定的目录名标识此文件夹属于快照版本;最终命名snaps/{commitid}/pages/{页面}.html
文件保存:
在以往项目配置项目打包会清除dist目录内容,打包完成再传到服务器;为了实现历史资源保存,需要改一下clean-webpack-plugin
的配置,每次只清除上一次的主构建产物,而不是整个dist
目录。
版本快照的webpack配置;主要思路为:每个页面的配置文件新增快照版本标识{snapshot: true}, 获取到这些页面的入口文件,传入webpack的entry配置。
配置产物路径; 产物文件名filename、产物路径path、静态资源路径assetModuleFilename、静态资源基础路径publicPath
filename: [name]/assets/js/main.[chunkhash:8].js
, 利用chunkhash做浏览器缓存
path:path.join(path.resolve(),
dist/snaps/${commit})
,以snaps作为父目录,commit标识每次生成快照版本的版本号。
assetModuleFilename: 将资源放入对应页面的目录,如dist/snaps/${commit}/assets/images/a.png,再后续实现增量构建时才能直接删除。
assetModuleFilename: ({ module }) => {
let ctx = module.context;
let origin = path.resolve("src/assets");
let dir = ctx.replace(origin, "").replace(/\\/g, "/")
return `${dir}/assets/images/[hash][ext]`
}
publicPath: /snaps/{{${COMMIT_ID_PACEHOLDER}}}/
, 因为webpack静态资源基础路径默认配置是/
,需要将其指向快照版本目录对应的版本,确保资源能正常访问。COMMIT_ID_PACEHOLDER
是commitid占位符,因为后续需要通过hashchunk
对比产物是否更新,如果这里指定commitid
,每次构建产物hashchunk
都不一样,就无法做到增量更新;所以这里需要一个唯一的字符串,在构建完后用脚本去替换真实的commitid
。
实现增量生成与页面信息上传至管理后台:SnapshotPlugin
在plugin的done钩子,获取到此次入口的chunkHash(compilation.entrypoints[i].chunks)
与上次的hashchunk记录进行对比,若不相同,则保留文件,若相同,说明此次该页面无改动,则将改页面的资源删除,则将该页面生成的删除删除,最后将此次生成的hashchunk值保存下来,以供下次对比;对比文件数据结构如下:
{
"test-page-1": "6e364aeafe957d7929223750b326a4f3",
"test-page-2": "f586eeef3a1f37672d54b0e5a1d5c0c7",
}
若是需要生成的页面,还需要替换掉COMMIT_ID_PACEHOLDER
,遍历此次生成的目录中的js、css文件,将COMMIT_ID_PACEHOLDER
替换成此次的commitid
,确保资源能够访问。
最后通过调取接口存储页面链接、changelog、标题等信息。
关键代码:
module.exports = class SnapshotPlugin {
constructor({ commitId }) {
this.curCommitId = commitId
}
apply(compiler) {
compiler.hooks.done.tap('snapshot-plugin', ({ compilation: { outputOptions, entrypoints } }) => {
console.log('快照对比----snapshot-plugin');
const PAGES_ROOT = outputOptions.path
// 获取上次构建的chunkhash值
const snapHashMap = getLastTimeSnapHash()
// 获取上次没有上传至管理后台的页面信息数据,此次一同发送
const snapPages = getLastTimeSnapPages()
let count = entrypoints.size
for (const [key, val] of entrypoints) {
// key是入口name, val中可以获取此次的hash值
const curChunkHash = (val.chunks.map(item => item.hash).filter(item => item) || []).join(':')
// 如果不相同,则说明有改动,将此添加到变更json中
if (!snapHashMap[key] || snapHashMap[key] !== curChunkHash) {
snapHashMap[key] = curChunkHash
// 获取页面的配置文件,得到页面配置信息,如标题等
const config = getPageConfig([key]) || {}
// 生成管理后台数据
snapPages.push(genSnapConfig(getSnapLink(this.curCommitId, key), config[key].title || key))
} else {
// 如果相同,则删除对应的文件目录,因为此个版本没有更新,不需要生成快照
removeDir(`${PAGES_ROOT}/${key}`)
count--
}
}
if (count > 0) {
// 替换commitid
replaceCommitHolder(PAGES_ROOT, this.curCommitId)
// 更新页面的chunchHash值
setCurrTimeSnapHash(snapHashMap)
} else {
removeDir(`${outputOptions.path}`)
}
if (snapPages.length > 0) {
// 调取接口存储页面信息
postSnapData(snapPages).then(() => {
console.log('postSnapData --- success');
// 新增成功,删除缓存文件
removeDir(resolve(PATH_CONFIG.SNAPS_PAGES))
}).catch(() => {
console.log('postSnapData --- fail');
// 新增失败,则将数据存储只本地
setCurrTimeSnapPages(snapPages)
})
}
})
}
}
来源:https://juejin.cn/post/7189172783564210235
猜你喜欢
- 1. 算法描述二分法是一种效率比较高的搜索方法回忆之前做过的猜数字的小游戏,预先给定一个小于100的正整数x,让你猜猜测过程中给予大小判断的
- Jinja是组成Flask的模板引擎。可能你还不太了解它是干嘛的,但你对下面这些百分号和大括号肯定不陌生:{% block body %}
- 详细:1.闵可夫斯基距离(Minkowski Distance)2.欧氏距离(Euclidean Distance)3.曼哈顿距离(Manh
- 利用networkx,numpy,matplotlib,将邻接矩阵输出为图形。1,自身确定一个邻接矩阵,然后通过循环的方式添加变,然后输出图
- 1、开始->运行,输入SERVICES.MSC到服务里,停止所有Oracle服务; 2、开始->程序->Oracle - OraHome81
- 本文实例讲述了Python装饰器decorator用法。分享给大家供大家参考。具体分析如下:1. 闭包(closure)闭包是Python所
- 模块化分页1.查询语句块<% 取得当前文件名 temp = Split(request.ServerV
- 前言mitmproxy 是 man-in-the-middle proxy 的简称,译为中间人代理工具,可以用来拦截、修改、保存 HTTP/
- 本文实例讲述了Python2比较当前图片跟图库哪个图片相似的方法。分享给大家供大家参考,具体如下:# -*- coding: utf-8 -
- 本文实例讲述了Python 字符串、列表、元组的截取与切片操作。分享给大家供大家参考,具体如下:demo.py(字符串、列表、元组的截取):
- golang判断元素是否在数组内众所周知,golang里没有像python的in来判断元素是否在list里存在,可替代的办法是将list放到
- python的matplotlib包支持我们画图,有点非常多,现学习如下。首先要导入包,在以后的示例中默认已经导入这两个包import ma
- 只是粗略的知道yield可以用来为一个函数返回值塞数据,比如下面的例子:def addlist(alist): &nbs
- 前言为了往我们写好的Python代码传入参数,有很多种方法,比如使用input获取从DOS 输入的参数,又或者读取txt 文件中的字符作为参
- 在vue中使用ant-design-vue组件官方地址:Ant Design Vue1. 安装首先使用vue-cli创建项目,然后进入项目,
- 这次自己做了一个小程序来玩,在登录方面一直有些模糊,网上看了很多文档后,得出以下一种解决方案。环境说明:1、小程序只需要拿到openid,其
- 使用Keras训练好的模型用来直接进行预测,这个时候我们该怎么做呢?【我这里使用的就是一个图片分类网络】现在让我来说说怎么样使用已经训练好的
- 一、Tkinter的介绍和简单教程Tkinter 是 Python 的标准 GUI 库。Python 使用 Tkinter 可以快速的创建
- 平时工作过程中,git在push代码的时候有时会遇到如下的错误错误原因文件冲突,本地的代码和远程Repository中的文件个数不一致(即远
- Timestamp只保留日期不显示时间Timestamp.date()拿到DataFrame中的一个时间戳后,加一个**.date()**即