遇到的問題
服務器端靜態資源更新,客戶端有緩存,未能獲取到最新的靜態資源,導致前端效果未達到預期。
期望
服務器端靜態資源更新后,客戶端訪問時能夠獲取到最新的靜態資源。客戶端盡量發送少的請求,服務器端未發生變化時,客戶端依然讀取本地緩存。盡量少的人工操作。
解決方案
借助基于nodejs的工具gulp,計算靜態資源的MD5值做為版本號追加在靜態資源的鏈接后,當靜態資源發生變更時,MD5亦會發生變化,客戶端會發起新的鏈接從而拉取到最新的靜態資源。
弊端:加載靜態資源的頁面必須不能緩存,否則無法加載到最新的靜態資源鏈接。頻繁更新靜態資源導致客戶端的緩存過大,不過對于現在PC或者手機來說,空間是足夠的。
實施步驟
以linux操作系統為例,注意各種版本號,目錄僅為示例,按實際需求位置安裝,注意操作用戶的權限。
為.html文件中引用的靜態添加版本號。
1.安裝nodejs
訪問NodeJS的官方網站,根據自己的操作系統下載對應的安裝包
http://nodejs.cn/download/
linux系統下載后解壓即可。
wget https://npm.taobao.org/mirrors/node/v8.4.0/node-v8.4.0-linux-x64.tar.gz
tar xf node-v8.4.0-linux-x64.tar.gz -C /opt/taobao/install/
配置全局命令
ln -s /opt/taobao/install/node-v8.4.0-linux-x64/bin/node /usr/local/bin
ln -s /opt/taobao/install/node-v8.4.0-linux-x64/bin/npm /usr/local/bin
2.可選步驟,npm為nodejs插件管理,因為是國外的下載源,可能網絡不穩定。淘寶建立了一個完整npmjs.org鏡像,可以用此代替官方版本(只讀),同步頻率目前為10分鐘一次以保證盡量與官方服務同步。
npm install cnpm -g --registry=https://registry.npm.taobao.org
配置全局命令
ln -s /opt/taobao/install/node-v8.4.0-linux-x64/bin/cnpm /usr/local/bin
cnpm和npm的功能一致,只是下載源不同。以下出現的npm和cnpm可以互換。
3.安裝全局的gulp
cnpm install gulp -g
配置全局命令
ln -s /opt/taobao/install/node-v8.4.0-linux-x64/bin/gulp /usr/local/bin
4.選擇一個目錄開始編寫腳本。此目錄為模塊位置,后續需要更改此位置下插件的源碼。
按默認配置創建模塊
cnpm init -y
會自動生成一個模塊描述文件package.json
5.安裝需要的插件
采用局部安裝,僅此模塊依賴
cnpm install --save-dev gulp
cnpm install --save-dev gulp-rev
cnpm install --save-dev gulp-rev-collector
cnpm install --save-dev gulp-asset-rev
cnpm install --save-dev run-sequence
安裝完成后package.json的文件內容如下(注意各插件版本號):
{
"name": "res_ver",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"gulp": "^3.9.1",
"gulp-asset-rev": "^0.0.15",
"gulp-rev": "^8.1.0",
"gulp-rev-collector": "^1.2.2",
"run-sequence": "^2.2.0"
}
}
6.編寫腳本
目錄未變更,創建一個文件gulpfile.js。文件內容如下
//引入gulp和gulp插件
var gulp = require('gulp'),
assetRev = require('gulp-asset-rev'),
runSequence = require('run-sequence'),
rev = require('gulp-rev'),
revCollector = require('gulp-rev-collector');
// 定義css、js源文件路徑
var opt = new Object();
//靜態資源的源目錄
opt.staticSrcFolder =?'/WEB-INF/classes/static';
//html文件的源目標
opt.htmlSrcFolder = '/WEB-INF/classes/templates';
//生成靜態資源的目錄。可與源目錄相同,會覆蓋。
opt.staticDestFolder = '/WEB-INF/classes/static';
//生成html文件的目錄。可與源目錄相同,會覆蓋。
opt.htmlDestFolder = '/WEB-INF/classes/templates';
// 為css中引入的圖片/字體等添加hash編碼
gulp.task('assetRev', function() {
return gulp.src(opt.staticSrcFolder + "/**/*.css") // 該任務針對的文件
.pipe(assetRev()) // 該任務調用的模塊
.pipe(gulp.dest(opt.staticDestFolder)); // 編譯后的路徑
});
// CSS、js生成文件hash編碼并生成 rev-manifest.json文件名對照映射。可加入.jpg、.png、.svg等
gulp.task('revStatic', function() {
return gulp.src([ opt.staticSrcFolder + "/**/*.css",opt.staticSrcFolder + "/**/*.js" ])
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest(opt.staticDestFolder));
});
// Html替換css、js文件版本
gulp.task('revHtml', function() {
return gulp.src([ opt.staticDestFolder + '/**/rev-manifest.json', opt.htmlSrcFolder + '/**/*.html' ])
.pipe(revCollector())
.pipe(gulp.dest(opt.htmlDestFolder));
});
// 開發構建
//gulp.run是最大限度的并行執行這些任務,而在添加版本號時需要順序執行這些任務,故使用了runSequence.也有不使用runSequence的寫法,閱讀起來比較費勁。
gulp.task('default', function(done) {
condition = false;
runSequence(
[ 'assetRev' ],
[ 'revStatic' ],
[ 'revHtml' ],
done);
});
7.修改部分插件源碼
1.打開 模塊位置/node_modules/gulp-rev/index.js
修改134行 manifest[originalFile] = revisionedFile;
改為 manifest[originalFile] = originalFile + '?v=' + file.revHash;
2.打開 模塊位置/node_modules/rev-path/index.js
修改10行 return filename + '-' + hash + ext;
更新為: return filename + ext;
3.打開 模塊位置/node_modules/gulp-rev-collector/index.js
修改40行 let cleanReplacement = path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' );
更新為:let cleanReplacement = path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' ).split('?')[0];
修改163行 regexp: new RegExp( prefixDelim + pattern, 'g' ),
修改為: regexp: new RegExp( prefixDelim + pattern + '(\\?v=\\w{10})?', 'g' ),
4.打開 模塊位置/node_modules/gulp-asset-rev/index.js
修改78~80行:var verStr = (options.verConnecter || "-") + md5;
src = src.replace(verStr, '').replace(/(\.[^\.]+)$/, verStr + "$1");
改為:var verStr = (options.verConnecter || "") + md5;
src = src + "?v=" + verStr;
8.運行腳本觀察結果
可以在模塊目錄下直接運行命令gulp
或者在任意位置使用gulp?--gulpfile <gulpfile path>指定一個腳本執行。
待續