背景
公司各团队管理自己的项目,分别有卖家中心、管理中心、商城、h5、...等项目。项目多达40+个。需要统一规划配置项目对应的路由、构建后静态资源存放地址。
前端页面请求过程
浏览器输入url
请求页面,经过路由服务,根据路由配置去拉取OSS
上相应的index.html
前端项目CI/CD
、请求页面到渲染,都会根据路由服务中的路由配置,结构如下:
[
{
"gitRepo": "cassfrontend/seller-merchant-react", // 项目仓库地址
"cdnRoot": "/www/seller/merchant", // 构建后项目CDN存放目录
"url": ["/seller/merchant"] // 浏览器请求的url
},
{
"gitRepo": "cassfrontend/seller-test-react",
"cdnRoot": "/www/seller/test",
"url": ["/any/path", "/alias/path"]
}
]
为什么路由服务在返回index.html
之前需要改写PUBLIC_PATH
呢?
因为生产构建时设置静态资源引用路径是'./'
,相对于当前路径获取静态资源,而实际情况请求页面地址有配置pathname
,比如上图访问页面https://ec-test.casstime.com/seller/merchant/
,但是页面资源不是存放在/seller/merchant
目录,而是存放在构建后的目录 /seller/merchant/prod/
。访问域名也会不一样,资源是在指定的bucket
下,访问域名应该是桶配置的CDN链接https://mstatic.cassmall.com
。因此,需要动态改写PUBLIC_PATH
,向OSS请求静态资源。
webpack
支持动态PUBLIC_PATH
,在项目主入口文件index.ts
中引入import './public-path';
// public-path.ts
declare var __webpack_public_path__: string;
__webpack_public_path__ = (window as any).__PUBLIC_PATH as string;
export default __webpack_public_path__;
路由服务返回index.html
,向其中注入script
脚本设置window.__PUBLIC_PATH = "xxx"
<!-- index.html 处理前 -->
<script src="/js/main.54b2835a.chunk.js"></script>
<!-- index.html 处理后 -->
<script>
window.__PUBLIC_PATH = "https://mstatic.cassmall.com/admin/merchant/prod/"
</script>
<script src="https://mstatic.cassmall.com/admin/merchant/prod/js/main.54b2835a.chunk.js"></script>
页面加载具体流程
部署及回滚方案
1、发布前端项目时,采用非覆盖式发布。每次构建后,入口文件index.html
按版本号复制一份。CDN
目录结构如下:
/seller/merchant/prod
css
js/
main.fade23.js
main.aeb3ef.js
index.html
index-v2.0.0.html
index-v1.0.0.html
2、每个项目可以配置唯一的目录(cdnRoot
),不同环境的静态文件部署到cdnRoot
对应的子目录中。
环境 | 目录 |
---|---|
灾备环境 | ${cdnRoot}/backup/ |
演示环境 | ${cdnRoot}/demo/ |
生产环境 | ${cdnRoot}/prod/ |
测试环境 | ${cdnRoot}/test/ |
3、构建/发布示意图如下:
4、需要回滚到指定版本时,只需要将对应版本的html
重命名为 index.html
即可快速回滚,回滚示意图如下:
解决了哪些问题
- 路由服务业务逻辑简单,但区分 admin、seller,需要为每个项目单独编写路由,配置复杂。
- 部署目录不规范,项目多后不好管理
- 不同环境的公共路径(publicPath)不一致,导致在不同的环境需要单独构建。有可能存在测试环境和线上环境代码不一致的问题。
- 构建和发布在同一个流水线,存在外部环境影响构建结果的风险。
- 当回滚静态资源时,需要重新构建。
Q&A
为什么忽略路由缓存
内网获取OSS页面非常快(10ms内),而且页面请求量不会特别高(单页应用,应用内【项目内】切换页面不会刷新),没必要做太复杂的缓存,缓存只作为容错的补救措施。
为什么不再构建时触发更新
这种操作加大CI复杂度,发布的同时更新路由配置关系(Redis),系统变得复杂。路由服务希望像一般反向代理一样,文件变化了,就给前端返回变化后的文件。文件本身的变化可以通过 HTTP 304 机制优化。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。