使用前端構建工具批量為頁面中引用的js檔案新增版本號的歷程
近日遇到有客戶反應,頁面開啟顯示不正常,不能完全的顯示出頁面。細問之下才得知,原來是有一個js檔案修改了一個方法,但是上線後由於瀏覽器快取的原因,還是載入的舊js檔案,導致頁面顯示不正常了。給客戶解釋由於系統更新,需要強制重新整理才可以。這天陸續又有客戶反映遇到此問題,而有些客戶由於對電腦瞭解的少,你說強制重新整理他也不知怎麼操作。思考是不是由網站自己解決此問題。先是考慮在頁面引用的js檔案後面加隨機數來解決,但是考慮到該js檔案引用頻繁,如果每次都請求伺服器,對伺服器壓力比較大,考慮著用版本號的方式在引用的時候實現,這樣每次js檔案有改變就更改版本號。如果js檔案不改變,由於請求的還是原地址,這樣就會從本地瀏覽器快取中讀取,而不會增加伺服器負擔。
原csthml中的js引用程式碼:
<script src="~/Scripts/app/member/practice.js"></script>
<script src="~/Areas/MM/Scripts/practice.js"></script>
更改後的js引用程式碼:
<script src="~/Scripts/app/member/practice.js?v=1.0"></script>
<script src="~/Areas/MM/Scripts/practice.js?v=1.0"></script>
在網站中更改後才發現,貌似頁面還不少,需要更改8個頁面,這樣如果每次更改都要找這8個頁面,感覺有點麻煩,也不利於以後的擴充套件,就考慮著找一個能批量更改的工具。這一找,還真找到一個:
找到一篇 gulp 自動新增版本號。由於以前裝過 nodejs 及 npm ,也知道 gulp ,但是在實際的專案中從來沒有使用過,就想著這次趁著這次的需求,研究下。在研究的過程中發現,想實際應用也不是那麼容易,中間要學習許多知識。由於考慮到第一次研究,關注點就集中到一點,就是解決js檔案的版本問題。但即使這樣,前前後後也費了將近5天的時間。中間歷經了許多問題。在這裡記錄下,便於以後查詢。
npm隨著nodejs安裝一起就安裝了。由於nodejs安裝的早,怕npm的版本過老,先更新了下npm的版本。用win+R開啟執行視窗,輸入cmd,開啟cmd視窗。輸入命令:
npm install -g
安裝後執行 npm -v 檢視 npm 的版本,是 4.5.0 版。
一、在cmd視窗中利用 cd 命令轉到網站專案的根目錄。然後執行命令
npm install module_name
此命令在專案中會建立一個名字為 node_modules 的模組。專案中需要的其他外掛安裝都會安裝到這個資料夾底下。
二、在專案根目錄建立package.json檔案。這個是必須的,專案的基本資訊及安裝到專案中的其他外掛版本號都會存在這裡面。
執行命令:
npm init
此命令會建立 package.json 檔案,執行此命令後,在 cmd 視窗中會顯示 name ,version ,description 等,等待你的輸入,這裡name必須輸入,而且不能有大寫字母,其他自己看著填寫,如果實在不知怎麼填,留空。
填寫完成後我的 package.json 檔案如下:
{
"name": "lmsoft.web",
"version": "1.0.0",
"description": "交規培訓",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "sxf359",
"license": "ISC",
}
三、全域性安裝 gulp :
npm install -g gulp
然後在專案目錄再次安裝 gulp :
npm install --save-dev gulp
這裡為了把 gulp 寫進專案 package.json 檔案的依賴中, 加上了 --save-dev
至於為什麼在全域性安裝gulp後,還需要在專案中本地安裝一次,有興趣的可以看下 stackoverflow 上有人做出的回答:
why-do-we-need-to-install-gulp-globally-and-locally、what-is-the-point-of-double-install-in-gulp 。大體就是為了版本的靈活性,但如果沒理解那也不必太去糾結這個問題,只需要知道通常我們是要這樣做就行了。
四、在專案目錄下分別安裝gulp-rev、gulp-rev-collerctor 、run-sequence
npm
install --save-dev gulp-rev
npm
install --save-dev gulp-rev-collector
都安裝完後我的 gulp 版本:3.9.1
gul-rev 版本:7.1.2
gulp-rev-collector: 1.1.1
run-sequence: 1.2.2
五、由於預設的 gulp 新增版本號的方法是直接更改 js 或 css 檔名,並不適用我的需求,因此要更改相應的檔案。
開啟網站專案目錄下的 node_modules\gulp-rev\index.js 檔案,搜尋程式碼:manifest[originalFile]
= revisionedFile
找到後註釋掉此行,然後在下面另起一行,寫如下程式碼:
manifest[originalFile] =originalFile + '?v=' +file.revHash;
這個格式就是我們需要的格式。
開啟node_modules\gulp-rev-collector\index.js,搜尋程式碼:path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' )
找到:
if ( !_.isString(json[key]) ||path.basename(json[key]).replace(newRegExp( opts.revSuffix ),'' ) !== path.basename(key) ) {
註釋掉,然後在這行下面寫新程式碼:
if (!_.isString(json[key]) ||path.basename(json[key]).split('?')[0] !== path.basename(key)) {
再在此頁面中搜索程式碼:new RegExp('([\/\\\\\'"])' + pattern + '(\\?v=\\w{10})?', 'g')
搜尋到此行:
regexp: newRegExp('([\/\\\\\'"])' +pattern, 'g'),
此行替換為:
regexp: newRegExp('([\/\\\\\'"])' +pattern, 'g'),
由於這裡的更改方法是在http://blog.csdn.net/zchcode/article/details/52421871這篇文章找到的,但是由於要替換的內容與我實際遇到的有所不同,導致後續出現錯誤,不能替換。這裡暫且不說。
六、在網站專案根目錄建立 gulpfile.js 空檔案。
輸入以下程式碼:
//引入gulp及gulp外掛
var gulp = require("gulp"),
runSequence = require('run-sequence'),
rev = require('gulp-rev'),
revCollector = require('gulp-rev-collector');
//定義js原始檔路徑
var jsSrc = "Scripts/app/member/practice.js";
//js生成檔案hash編碼並生成 rev-manifest.json檔名對照對映
gulp.task('revJs', function () {
return gulp.src(jsSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/js'));
});
//Html替換js檔案版本
gulp.task('revCshtml', function () {
return gulp.src(['rev/js/rev-manifest.json', 'views/member/*.cshtml'])
.pipe(revCollector())
.pipe(gulp.dest('views/member'));
});
//開發構建
gulp.task('dev', function (done) {
condition = false;
runSequence(
['revJs'],
['revCshtml'],
done);
});
gulp.task('default', ['dev']);
這個檔案程式碼也參考了http://blog.csdn.net/zchcode/article/details/52421871 檔案中的相關程式碼,結合了自己專案的實際情況進行了修改。這裡對程式碼的功能進行一些解釋。
gulp.task('revJs', function () {
return gulp.src(jsSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/js'));
});
這段程式碼, gulp.src是開啟相應的js檔案,pipe(rev()),執行 gulp-rev外掛,pipe(rev.manifest()) 生成 rev-mainfest.json檔案,pipe(gulp.dest('rev/js')),生成的rev-mainfest.json檔案存入專案根目錄下的 rev/js 目錄下面。
如果在cmd視窗執行
gulp revJs
則會執行revJs任務,生成的rev-mainfest.json檔案程式碼如下:
{
"practice.js": "practice.js?v=0f74d71923"
}
這裡要注意,revJs區分大小寫,如果你純小寫,此任務不會執行,會提示:
Task 'revjs' is not your gulpfile.js中
gulp.task('revCshtml', function () {
return gulp.src(['rev/js/rev-manifest.json', 'views/member/*.cshtml'])
.pipe(revCollector())
.pipe(gulp.dest('views/member'));
});
這個gulp任務是為了根據已經生成的rev-manifest.json檔案,遍歷views/member下的cshtml檔案,並搜尋practice.js?v=1.0,然後替換為practice.js?v=0f74d71923
gulp.task('dev', function (done) {
condition = false;
runSequence(
['revJs'],
['revCshtml'],
done);
});
此任務是按照先後順序,先執行revJs 任務,再執行 revCshtml 任務。因為 gulp 的任務執行是多執行緒並行的,而 revJs 與revCshtml有先後的次序,才需要這樣寫。
至於gulp.task('default', ['dev']); 則是以預設任務的形式執行 dev 任務。即在cmd視窗的專案目錄下面,輸入:
gulp
則直接執行dev 任務。
這裡執行後發現,rev-manifest.json 被生成,但是 views/member 目錄下的cshtml檔案中含有
<script src="~/Scripts/app/member/practice.js?v=1.0"></script> 的部分卻被轉換成了 <script src="~/Scripts/app/member/practice.js?v=0f74d71923?v=1.0"></script> ,顯然匹配規則出了問題。經過反覆修改,最終
被修改為:
regexp: newRegExp('([\/\\\\\'"])' +pattern + '(\\?v=[\\w\\.]{1,10})?','g'),這樣才正確替換了cshtml 檔案中的 practice.js 相關程式碼。
替換後很歡喜,然後在網頁中進行訪問,卻發現在不含practice.js 的 cshtml 檔案中出現了左括號“(”缺少對應的右括號“)”這樣的錯誤。 經過檢視分析,發覺是該頁面的 cshtml 檔案中的else if (Model.Role == "科目四") 沒有正確解析,出現的是亂碼。 首先想到是檔案編碼問題,然後經過檢視檔案編碼,是 utf8 格式, 但是我們知道, cshtml 檔案格式是 utf8+BOM 的編碼。真是一波未平,一波又起。經過百度,google搜尋答案,但並沒有可用的答案,只是有人提出是編碼問題,需要轉換,但並沒有說明如何轉換。
在網上搜索到 gulp-convert-encoding 這個外掛,可用從一個編碼轉換到另一個編碼。找到了這個外掛的官方文件頁面:
https://www.npmjs.com/package/gulp-convert-encoding
看了這個外掛的示例:
覺得應用比較簡單,可能會解決我的問題。然後在cmd視窗的專案目錄下執行:
npm install --save-dev gulp-convert-encoding
然後在gulpfile.js檔案的程式碼:
.pipe(gulp.dest('views/member'));
上面增加:
.pipe(convertEncoding({ to: 'utf-8+BOM' }))
執行,直接報錯,這好理解,表示支援 cshtml 的帶簽名的 utf8 的編碼字串不是這樣寫的。因為示例很簡單,只能根據官方文件一次一次的試錯。最終經過n次嘗試,終於找到了可行的程式碼:
.pipe(convertEncoding({ iconv: { decode: {}, encode: { addBOM: true}}, to: "utf8" }))
通過執行,這次解決了此問題,經過轉換,可以轉換為vs 認可的帶簽名的 utf8 編碼格式。網頁沒有問題了。並且經過試驗,也證實了只要 practice.js 檔案有修改,v=後面的hash值就會改變。最終完整的我的gulpfile.js檔案是這樣的:
//引入gulp及gulp外掛
var gulp = require("gulp"),
runSequence = require('run-sequence'),
rev = require('gulp-rev'),
revCollector = require('gulp-rev-collector');
var convertEncoding = require('gulp-convert-encoding');
//定義js原始檔路徑
var jsSrc = "Scripts/app/member/practice.js";
var jsSrcmm = "areas/mm/Scripts/practice.js";
//js生成檔案hash編碼並生成 rev-manifest.json檔名對照對映
gulp.task('revJs', function () {
return gulp.src(jsSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/js'));
});
gulp.task('revmmJs', function () {
return gulp.src(jsSrcmm)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/jsmm'));
});
//Html替換js檔案版本
gulp.task('revCshtml', function () {
return gulp.src(['rev/js/rev-manifest.json', 'views/member/*.cshtml'])
.pipe(revCollector())
//.pipe(convertEncoding({ to: 'utf-8+BOM' }))
.pipe(convertEncoding({ iconv: { decode: {}, encode: { addBOM: true}}, to: "utf8" }))
.pipe(gulp.dest('views/member'));
});
//Html替換js檔案版本
gulp.task('revMmCshtml', function () {
return gulp.src(['rev/jsmm/rev-manifest.json', 'areas/mm/views/member/*.cshtml'])
.pipe(revCollector())
.pipe(convertEncoding({ iconv: { decode: {}, encode: { addBOM: true } }, to: "utf8" }))
.pipe(gulp.dest("areas/mm/views/member"));
});
//開發構建
gulp.task('dev', function (done) {
condition = false;
runSequence(
['revJs'],
['revCshtml'],
done);
});
gulp.task('devmm', function (done) {
condition = false;
runSequence(
['revmmJs'],
['revMmCshtml'],
done);
});
gulp.task('default', ['dev','devmm']);
//gulp.task('default', function () {
// console.log('hello world');
//});