前端静态资源部署
前几天厂里的网突然渣掉了,某些网页变得极度卡顿,但是划水网站依旧流畅;我觉得很有趣就打开 DevTools 对比了一下,结果看到某卡顿网页的 Network 状况如下,我大概猜到了一些缘由。这期就借机讲讲前端部署里的一些小技巧吧。
Network原始前端
OK,这期讨论的原始前端当然也没那么“原始”,我无意追述到 JSP,thymeleaf 这类传统后端渲染的技术。只是说“原始前端”开发比较简单,只需要一页文本编辑器,一份 html 模版和一款打包工具(webpack);部署更简单,webpack 构建并打包出 html、css、js,然后一股脑丢到服务器上就行。当请求到来时,服务器返回 index.html, 然后再把它的相关引用(js、css)返回给客户端即可。
Server但是这里有个问题是,html 的引用文件名——onion.js——是固定的:一旦部署更新后,就得防止客户端缓存旧文件,所以一般会设置Cache-Control: no-cache
。这就是文章开头提到的一个问题,每次打开页面都得重新加载一遍所有引用文件,一旦网络渣渣了,首页渲染就会十分缓慢。
版本管理
看样子缓存策略还是需要保留的,可如何兼顾更新呢?有人就想到了给引用路劲加个版本号:
Versionp.s. index.html 始终是 no-cache
新版本上线后,浏览器请求 html,发现资源路劲更改了,便主动放弃相关缓存,重新加载引用文件。这个小小的改变很实用,兼顾了缓存策略和新旧版本文件冲突。至于这个版本号怎么添加,各色打包工具都能实现,最简单的策略就是在 index.html 模版里加个时间戳。
摘要算法
当然,随着业务增长,js 和 css 文件会变得愈发巨大,仅仅加载单个文件就可能需要数秒。遇到问题就解决问题,把大文件拆成小份就行了!利用浏览器有异步加载多个文件的机制,拆分 JS 迅速提速。(当然,浏览器并发加载数有限,拆太细会适得其反)。
multi-link这时候有聪明人就发现了:每次更新时,某些 js 文件我并没有修改,为什么也要增加版本号呢?让客户无端加载一个一模一样的 js 文件,不是很浪费吗?
反正版本号就是一个 unique 值——防冲突用的,不如换成别的数值吧?比如,经典的MD5(消息摘要)算法生成的content hash值,该哈希值与文件内容一一对应的,这就有了精确到单个文件的版本控制信息了。
Content Hash我自己开发时,会利用 webpack 的 splitChunks 功能,把 Vue 全家桶打包成单个 js 文件、UI 框架也打包成单个 js;这种文件体积不小,但基本不会变动,cache-control/max-age
可以写大一些,客户端除了首次访问会慢一点,其他时候基本就disk cache
了。
CDN
上面提到服务器存放静态资源文件,但这种形式在性能上也有缺陷:
- 受地理环境影响,离服务器越远资源加载较慢
- 频繁请求资源对服务器压力较大
为了进一步提升性能,大家开始把动态网页(index.html)和静态资源(js、css、image...)分开部署。静态资源被会放置于 CDN 上。
CDN但是 CDN 也有缓存策略:新的静态资源发布后,需要一定的时间去覆盖各个边缘站点的旧资源。若某客户端获得了新的动态网页,但是附近的 CDN 节点尚未更新最近发布的静态资源,客户端即便放弃本地缓存,它加载到的依旧是位于 CDN 上的“脏数据”。怎么办呢?干脆把文件名也给改了——让摘要信息成为文件名的一部分!具体实现还是可以仰仗 webpack,将 output.filename
设为 [name].[contenthash].js
,输出文件和 html 模版都会帮你更改好。
用摘要信息重命名后的资源文件,不再需要以覆盖旧文件的形式主动更新各个地区的边缘站点。新版本发布后,浏览器首次请求资源,若 CDN 不存在该资源,便会向就近的边缘站点加载该文件,同时更新 CDN 缓存;这样就彻底避免了 CDN 脏数据的问题。
Build
关于构建,我自己项目的做法是前端单独部署:git 接收 MR 后触发 AWS 的 code pipeline 和 code build;Webpack 自动打包生成dist
文件夹(里面包含 html、js、css 等资源),html 部署到服务器上,其他静态资源发布到 CDN 上。这一套下来也不贵:一个月十几刀,每次部署也就几分钟。但是,但是!我后来看到有些厂的做法,觉得实在是太“抠”了——成本控制得真好!
他们的做法是这样的:build 在开发环境就完成了,然后把打包生成的dist
文件夹也 push 到 git 上。好处很明显:
- 节省了一套 code pipeline + code build 的花费:大公司就不是一个月十几刀的事了
- 大大缩短 build 时间:构建环境是不包含
node_modules
的,大型项目光npm install
就要很久,build 时间更是感人
而开发人员本地 build,可以很完美地绕开上面两个缺点。配置上写个 hook,在git commit
时多加如下三个 changes 即可:
modified: dist/index.html
delete: dist/garlic.6b58.js
new file: dist/garlic.e5i1.js
最后的牺牲只是 git repo 上包含了一个 dist 文件夹而已,真的是很“抠门”啊。
小结
这期内容很简单,讲了讲静态资源文件名的那些事,算是螺蛳壳里做道场吧。一直在想一件事,我们追求新技术的本质是什么?我觉得是降低成本!软件本质上还是属于“工业产品”的范畴,新闻上能看到一些重工业单位通过调整工艺大大降低成本的案例。我们做软件的也应该不断地调整工艺,降低成本、减少设备消耗,从而帮助公司赚取更多的利润。我曾看过有人形容软件开发就是一种缓慢地破坏+重建的过程,这种观点还是挺有道理的,分享给大家。