1. 程式人生 > >使用前端構建工具批量為頁面中引用的js檔案新增版本號的歷程

使用前端構建工具批量為頁面中引用的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

安裝後執行 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

npm install--save-dev run-sequence

都安裝完後我的 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{10})?','g'),

被修改為:

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

看了這個外掛的示例:

var gulp =require('gulp'); var convertEncoding =require('gulp-convert-encoding'); gulp.task('default',function(){ returngulp.src('src/file.txt') .pipe(convertEncoding({to:'iso-8859-15'})) .pipe(gulp.dest('dist')); });

覺得應用比較簡單,可能會解決我的問題。然後在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');
//});