针对微信的前端文件版本管理原则

之前的文章里,我们讲到了微信的一个问题:缓存它认为是静态内容的文件,比如 javascript、css、html 还有图片。这给使用 Android 系统的开发人员带来了不便,因为 Android 中的微信没有刷新按钮,改完代码后想再测试很不方便。

但这是个契机,放大了一个被很多开发人员忽略的问题:前端文件是会被缓存的。不少开发人员甚至从未意识到这一点,因为在日常开发过程中,每次改完代码都会主动刷新一下再测试效果,如果不行就强制刷新一下,从来没有考虑普通用户会遇到什么问题。普通用户并不会在你的新代码上线后就刷新一下浏览器,知道强制刷新的就更少了,导致部分用户仍在用旧版的前端代码和你新版的后端交互,这经常会导致一些奇怪的 bug,而且是你永远无法查出来的 bug 。

因此,无论是不是针对微信,前端文件的版本管理都是十分重要的。

避免过时的缓存

浏览器缓存是一个非常有用的机制,可以明显减少浏览器和服务器间的数据传输,加快网页的加载速度,也能大大减少服务器的压力。但是缓存一旦过期,就会起到反作用。

有人会使用简单暴力的方法,通过在服务器配置文件头,告诉浏览器不要缓存某些文件,但这样做会牺牲缓存可以带来的好处,而且这一套在微信浏览器里完全行不通,它根据文件后缀来决定是不是要缓存,不管你的服务器是怎么跟它说的。

因此我们只能在迁就微信的情况下,合理的使用缓存,充分利用缓存的优点,同时避免缓存过期的问题。

两个原则
一、绝对不要缓存入口文件

除非你有信心,永远不需要修改这个页面以及所有被它使用的资源,否则绝对不要缓存入口文件。即使你对自己的代码有信心,你也很难保证需求不变化,呵呵。所以,绝对不要缓存入口文件

入口文件和其他文件是不同的,不能随意地变更地址,入口地址放在微信菜单里,会有最长 24 小时的生效时间,如果是分享类的活动,中途想换地址就更不可能。

实际操作中的规则,就是永远不要把入口文件命名成 .html 结尾。微信会无条件缓存 html 文件,导致你后续的修改无法传达到之前访问过的用户手中,他们会在很长一段时间里,只能看到旧版。

如果你的入口文件是用 php 之类的动态生成的,那么不要把地址重写成 .html 就好。

如果你的入口文件真的是静态的 html 文件,那么你可以考虑用你服务器端的编程语言加载并原样输出这个文件(以 php 为例):

1
2
<?php  
echo file_get_contents('index.html');

针对支持 php 的服务器,更简单的方法是直接将文件后缀改成 .php,asp 和 jsp 也类似。

如果你对性能有更高的要求,更好的方法是将文件后缀改掉,然后添加一个相应的 Mime 类型。例如将文件改成 index.nocache ,然后在 mime.conf (以 nginx 为例)里为 text/html 类别添加一个新后缀。

1
2
3
4
types {  
text/html html htm shtml nocache;
....
}

这样服务器就不必调用第三方的程序(如 php-fpm)尝试解析了,访问量大的时候也可以节约一些性能。

二、用版本管理其他资源

除了入口文件,所有其他资源都应该使用版本进行管理,主要包括 javascript、css、web 字体、还有图片。版本号可以包含在文件名里(如 app-0001.js,或者查询参数中(app.js?0001),两者各有好处,我个人更喜欢后者。

对版本号有两个要求:内容不同的文件,版本号一定是不同的;版本号不同的文件,内容一定是不同的

第一个要求用来避免用户使用过期的缓存。如果你改了内容,却不改版本号,用户就会继续使用缓存中的旧版本。

第二个要求用来避免缓存的浪费。因为你每次修改版本号,用户就需要重新加载一次那个文件,如果文件内容明明没有改变,缓存里的已经够用,你却更新了版本号,会让用户白白多下载一次。

因此,使用文件的哈希值来作为版本号是非常合适的,哈希值以文件内容作为计算依据,并且在有限的样本空间里,可以认为内容和哈希值是一一对应的,满足上面所说的两个条件。用哈希值还可以很方便的自动化,避免人工命名,我们都清楚,人迟早是会犯错的。

示例

结合上述两个原则,我的入口文件会被命名为 index.php ,简化后的内容如下:

1
2
3
4
5
6
7
8
9
10
<!doctype html>  
<head>
<title>缓存管理</title>
<script src="/app.js?4f05b2a7"></script>
...
</head>
<body>
...
</body>
</html>

下周,我们继续聊聊如何将今天提到的原则应用到实际的项目中,实现版本号的自动化管理,以及如何解决版本号带来的测试方面的问题。