1

老旧的项目,日复一日的缝缝补补,没有大的动荡,其实一直维持着原有的样子也是可以的...

不过就这样子的话也太没意思了。

眼看着当前各种炫酷的框架操作,好生羡慕,旧项目积重难返,但心里却早已种草《大公司里怎样开发和部署前端代码》,想动手实现一番。文章里的思路,在评论里有人指出 Ruby on Rails 其实早就有这样的实现了,即 Asset Pipeline,这两都可以拜读一番。

趁着手上工作告一段落,暂时有空闲时间,跟后端小伙伴商量着怎么处理这个缓存问题,因每次发版都会引发缓存问题,运营人员、用户每次都要强刷,确实蛋疼。

期间找了现有的方案,找来找去还是百度出的 fis 满足一切需求,但是项目已经停滞了许久,便作罢。后面想着与其这样动找西找的,不如自己动手拼凑个!!!

现有问题

  1. 项目是旧有的合作方式:前端出稿、脚本交互,PHP 后端套版,资源引用都是写死耦合在 PHP 模板内,不可能抽离(前面咨询过后端的小伙伴,因为框架本身有提供缓存功能,但是因为自己定义了一套资源加载的方法所以自带的缓存管理就用不上了)。
  2. 前端有自己的静态资源发布权限,只是修改样式、脚本、图片时可以自己操作发布。但是问题是资源的版本管理是通过路径后附带查询字符串的形式去实现的,且由后端手动控制,这边修改一次需要后端手动修改版本号两边一起发版。
  3. 因这边的所有缓存管理都是通过附带同一个查询字符串实现,当修改一个时,其它的文件的版本号亦一同改变,所有缓存失效,需要重新来过,缓存形同虚设。
  4. 图片文件更新后,得不到有效更新,使用的还是旧内容。
  5. 修改版本查询字符串还不一定生效(后面会说明为什么会这样)

通过上述暴露的问题,就着手一个一个拆分解决了。

针对第一个问题,后端通过修改原有的加载方式,在加载的时候都去读取由前端这边生成的资源表匹配资源,在表上的就替换上去,不在的就回退到备用(原有)的资源那边。

可以看到关键词:资源表、匹配、替换,肯定不可能是人肉去搞这个了,这样神都救不了你。项目本身使用的 gulp 去做一些重复的操作的(压缩、混淆、复制搬运这些),那就继续在这上面捣鼓好了(webpack只是浅尝辄止)。

资源表的生成

这个可以通过 gulp-revgulp-rev-collector 插件来生成、替换内容,因学艺不精所以里面有些是人肉写的,未通过插件去实现。

生成文件指纹:

  • 创建时间(有隐患:本地 git 删除,重新拉取,其创建时间、修改时间、访问时间都是一样的)
  • 文件内容

借助于 nodecrypto 模块,我们可以通过以下方式简单的了解下指纹的生成(代码来自 npm 包插件:rev-hash):

'use strict';
const crypto = require('crypto');

module.exports = input => {
    if (typeof input !== 'string' && !Buffer.isBuffer(input)) {
        throw new TypeError('Expected a Buffer or string');
    }

    return crypto.createHash('md5').update(input).digest('hex').slice(0, 10);
};

喏,这就是指纹生成啦。

指纹的生成简单,但是替换上就比较麻烦了,因为路径都是写在后端模板内的,你前端也操作不了。所以这里就需要生成资源表给到后端,后端通过读取该资源表去匹配替换了。

后端处理好该问题,那么上述的第二、第三、第四的问题也就解决了。后端发版时,拉取新的资源表即可,无需再去手动修改版本号,前端可以更好的控制资源了。

因为项目中用到了 requirejs,所以其配置文件也需要替换,没有用到 gulp-rev-collector,人肉简单的使用了 replace 方法去替换资源表上的内容。

旧资源与新资源

期间就遇到这样的问题:每次更新资源时,删除整个版本目录,生成新的版本资源还是保留原有资源,生成新的资源。

整个删除(覆盖式发布):在资源发版后会有短暂的资源找不到的问题(有可能来不及刷新资源表),但是好处是不会有冗余的文件存在。

保留旧文件,生成新文件(非覆盖式发布):随着时间的推移,版本化的文件内容会日积月累,变得愈加庞大,单是我们这边设想的起码保留3+1份(上上次、上次、当前 + 回退用的未版本化的文件)。

当然,为了避免前者带来的短暂问题,我们选择了后者,通过定时任务去清理过期文件(git 重新拉取到本地的话,所有文件的创建时间、修改时间、访问时间都是一样的,所以不能在本地对其进行操作)。

查询字符串还是重命名文件

上面有提到过该问题,我们采用的重命名文件的形式,为什么不使用附带查询字符串的形式呢?

其实前面的知乎大神已解释了该原因,《高性能网站建设指南》的作者也在书中提到了,这是他博文上的观点:Revving Filenames: don’t use querystring,博文是10年前的了...10年前的...10年前...10年...10...1...。

非覆盖式发布的资源冗余处理

200 vs 304

通过查看 chromenetwork 面板,可以看到 size栏显示了第二次访问的资源,有些显示的是 from memory cache,有些则是 from disk cache,status栏显示的状态码都是 200

而在 firefox 下显示的却是 304 已缓存的状态,这又是为什么呢?


super_newbie
292 声望7 粉丝

路漫漫其修远兮,吾将上下而求索