之前,我介紹了學習安裝並配置前端自動化工具Gulp,覺得gulp確實比grunt的配置簡單很多,於是我決定再深入學習一下gulp,就去網上查了資料,發現gulp還可以自動添加版本號,這個功能就為我平時在更新css或js時老是在客戶端存在緩存導致更新后的效果無法實時展現的苦惱。所以就趕緊去試了一下,果真可以,很高興啊,真是為項目開發,為效果的快速展現提供了很多的便利。
實現原理:
1、修改js和css文件;
2、通過對js,css文件內容進行hash運算,生成一個文件的唯一hash字符串(如果文件修改則hash號會發生變化);
3、替換html中的js,css文件名,生成一個帶版本號的文件名。
原html文件代碼
<link rel="stylesheet" href="../css/default.css">
<script src="../js/app.js"></script>
預期效果:在原目錄結構下html文件代碼
<link rel="stylesheet" href="../css/default.css?v=5a636d79c4">
<script src="../js/app.js?v=3a0d844594"></script>
background:url("../images/none.png?v=8f204d4")
實現方法:
1、安裝gulp和gulp插件
npm install --save-dev gulp
npm install --save-dev gulp-rev
npm install --save-dev gulp-rev-collector
npm install --save-dev gulp-asset-rev
npm install --save-dev run-sequence
2、編寫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 cssSrc = 'css/*.css',
jsSrc = 'js/*.js';
//為css中引入的圖片/字體等添加hash編碼
gulp.task('assetRev', function(){
return gulp.src(cssSrc) //該任務針對的文件
.pipe(assetRev()) //該任務調用的模塊
.pipe(gulp.dest('src/css')); //編譯后的路徑
});
//CSS生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revCss', function(){
return gulp.src(cssSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/css'));
});
//js生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revJs', function(){
return gulp.src(jsSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('rev/js'));
});
//Html替換css、js文件版本
gulp.task('revHtml', function () {
return gulp.src(['rev/**/*.json', 'View/*.html'])
.pipe(revCollector())
.pipe(gulp.dest('View'));
});
//開發構建
gulp.task('default', function (done) {
condition = false;
runSequence( //需要說明的是,用gulp.run也可以實現以上所有任務的執行,只是gulp.run是最大限度的並行執行這些任務,而在添加版本號時需要串行執行(順序執行)這些任務,故使用了runSequence.
['assetRev'],
['revCss'],
['revJs'],
['revHtml'],
done);
});
執行gulp命令后的效果
//rev目錄下生成了manifest.json對應文件
{
"default.css": "default-803a7fe4ae.css"
}
<link rel="stylesheet" href="../css/default-803a7fe4ae.css">
<script src="../js/app-3a0d844594.js"></script>
很顯然這不是我們需要的效果
3、更改gulp-rev和gulp-rev-collector
打開node_modules\gulp-rev\index.js
第144行 manifest[originalFile] = revisionedFile;
更新為: manifest[originalFile] = originalFile + '?v=' + file.revHash;
打開nodemodules\gulp-rev\nodemodules\rev-path\index.js
10行 return filename + '-' + hash + ext;
更新為: return filename + ext;
打開node_modules\gulp-rev-collector\index.js
31行 if ( !_.isString(json[key]) || path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' ) !== path.basename(key) ) {
更新為: if ( !_.isString(json[key]) || path.basename(json[key]).split('?')[0] !== path.basename(key) ) {
打開node_modules\gulp-asset-rev\index.js
78行 var verStr = (options.verConnecter || "-") + md5;
更新為:var verStr = (options.verConnecter || "") + md5;
80行 src = src.replace(verStr, '').replace(/(\.[^\.]+)$/, verStr + "$1");
更新為:src=src+"?v="+verStr;
再執行gulp命令,得到的結果如下(效果正確):
<link rel="stylesheet" href="../css/default.css?v=803a7fe4ae">
<script src="../js/app.js?v=3a0d844594"></script>
background:url("../images/none.png?v=8f204d4")
但是假如我們更改了css和js后,再執行gulp命令,得到的結果會如下:
<link rel="stylesheet" href="../css/default.css?v=33379df310?v=803a7fe4ae">
<script src="../js/app.js?v=3a0d844594?v=3a0d844594"></script>
有沒有發現,會在版本號后面再添加一個版本號,因為gulp只替換了原來文件名,這樣又不符合預期效果了,所以我們想到,還需要修改插件的替換正則表達式。
4、繼續更改gulp-rev-collector
打開node_modules\gulp-rev-collector\index.js
第107行 regexp: new RegExp( '([\/\\\\\'"])' + pattern, 'g' ),
更新為: regexp: new RegExp( '([\/\\\\\'"])' + pattern+'(\\?v=\\w{10})?', 'g' ),
現在你不管執行多少遍gulp命令,得到的html效果都是
<link rel="stylesheet" href="../css/default.css?v=5a636d79c4">
<script src="../js/app.js?v=3a0d844594"></script>
以下是本人自己寫的一個既可以編譯less,又可以壓縮、重命名css和js,同時可以壓縮html並自動添加版本號的gulp.js配置文件,當然也是參考了原作者的方法:
//引入gulp和gulp插件
var gulp = require('gulp'),
less = require('gulp-less'),
assetRev = require('gulp-asset-rev'),
minifyCss = require('gulp-minify-css'),
uglify = require('gulp-uglify'),
htmlmin = require('gulp-htmlmin'),
rename = require('gulp-rename'),
imagemin = require('gulp-imagemin'),
runSequence = require('run-sequence'),
rev = require('gulp-rev'),
zip = require('gulp-zip'),
revCollector = require('gulp-rev-collector');
//定義css、js源文件路徑
var cssOld = 'css/*.css',
cssSrc = 'src/*.css',
cssMinSrc = 'dist/css/*.css',
jsSrc = 'js/*.js',
jsMinSrc = 'dist/js/*.js',
lessSrc = 'less/*.less',
//imgMinSrc = 'dist/images/*.{png,jpg,jpeg,gif,ico}', //這是導致無法給圖片添加版本號時所用的路徑
imgSrc = 'images/*.{png,jpg,jpeg,gif,ico}', //這是修改后的路徑
htmlSrc = '*.html';
//編譯less 定義一個less任務(自定義任務名稱)
gulp.task('less', function(){
return gulp.src(lessSrc) //該任務針對的文件
.pipe(less()) //該任務調用的模塊
.pipe(gulp.dest('src'));//編譯后的路徑
});
//為css中引入的圖片/字體等添加hash編碼
gulp.task('assetRev', function(){
return gulp.src(cssOld) //該任務針對的文件
.pipe(assetRev()) //該任務調用的模塊
.pipe(gulp.dest('src')); //為css中引入的圖片/字體等添加hash編碼后的路徑
});
//壓縮css 這里必須從“編譯less”以及“為css中引入的圖片/字體等添加hash編碼”后輸出的src文件夾下拿需要壓縮的css,因為要先把上邊的步驟按順序走完了,才能去壓縮
gulp.task('cssMin', function() {
return gulp.src(cssSrc) //壓縮的文件
.pipe(rename({suffix: '.min'}))
.pipe(minifyCss()) //執行壓縮
.pipe(gulp.dest('dist/css')); //輸出文件夾
});
//CSS生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revCss', function(){
return gulp.src(cssMinSrc)
.pipe(rev()) //文件名加MD5后綴
.pipe(rev.manifest()) //必須有這個方法 生成一個rev-manifest.json
.pipe(gulp.dest('dist/css')); //將rev-manifest.json 保存到 dist/css 目錄內
});
//壓縮js
gulp.task('uglify',function(){
return gulp.src(jsSrc)
.pipe(rename({suffix: '.min'}))
.pipe(uglify())
.pipe(gulp.dest('dist/js'));
});
//js生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revJs', function(){
return gulp.src(jsMinSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('dist/js'));
});
//壓縮html
gulp.task('htmlMin',function(){
var options = {
collapseWhitespace:true, //從字面意思應該可以看出來,清除空格,壓縮html,這一條比較重要,作用比較大,引起的改變壓縮量也特別大。
collapseBooleanAttributes:true, //省略布爾屬性的值,比如:<input checked="checked"/>,那么設置這個屬性后,就會變成 <input checked/>。
removeComments:true, //清除html中注釋的部分,我們應該減少html頁面中的注釋。
removeEmptyAttributes:true, //清除所有的空屬性。
removeScriptTypeAttributes:true, //清除所有script標簽中的type="text/javascript"屬性。
removeStyleLinkTypeAttributes:true, //清楚所有Link標簽上的type屬性。
minifyJS:true, //壓縮html中的javascript代碼。
minifyCSS:true //壓縮html中的css代碼。
};
return gulp.src(htmlSrc)
.pipe(htmlmin(options))
.pipe(gulp.dest('dist/html'));
});
//Html替換css、js、img文件版本
gulp.task('revHtml', function () {
return gulp.src(['dist/**/*.json', 'dist/html/*.html'])
.pipe(revCollector())
.pipe(gulp.dest('dist/html'));
});
//壓縮image
gulp.task('imageMin', function () {
gulp.src('images/*.{png,jpg,jpeg,gif,ico}')
.pipe(imagemin())
.pipe(gulp.dest('dist/images'));
});
//img生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revImage', function(){
return gulp.src(imgSrc)
.pipe(rev())
.pipe(rev.manifest()) //必須有這個方法
.pipe(gulp.dest('dist/images'));
});
//打包
gulp.task('zip', function(){
gulp.src('dist/**/*')
.pipe(zip('zipName.zip'))
.pipe(gulp.dest('dist'));
});
gulp.task('default', function (done) {
//condition = false;
runSequence( //此處不能用gulp.run這個最大限度並行(異步)執行的方法,要用到runSequence這個串行方法(順序執行)才可以在運行gulp后順序執行這些任務並在html中加入版本號
'less',
'assetRev',
'cssMin',
'revCss',
'uglify',
'revJs',
'imageMin',
'revImage',
'htmlMin',
'revHtml',
'zip',
done);
});
目前,不知為何必需要運行兩次gulp才可以給html中引入的圖片添加版本號,所以還在摸索中,也請大神給指點指點,謝謝!
關於需要運行兩次gulp才可以給html中引入的圖片添加版本號的問題,今天我終於知道了原因所在,原因是在給圖片添加版本號之前我先做了壓縮圖片這一步,同時給圖片添加版本號時用到的圖片路徑恰好又是壓縮圖片后輸出的那個路徑,而壓縮圖片花費的時間比較長,導致后續給圖片添加版本號的任務revImage在執行時沒有找到其路徑下的圖片,所以第一次執行任務時,給圖片添加版本號的效果沒有出來,而第二次執行時由於圖片已經壓縮過,文件夾里也有了壓縮后的圖片,所以就給引入的圖片添加上了版本號,這是我當時考慮問題的思路不對。這里我們可以將給圖片添加版本號的任務revImage針對的文件改成圖片壓縮前所在文件就可以了。其實,我們在用PS切圖時已經對圖片做了處理,這里再用gulp壓縮,雖然經過gulp壓縮后的圖片的體積相對小了一些,但圖片的清晰度應該也是下降了,所以個人覺得是沒有必要再壓縮了。
最后,關於只壓縮修改過的圖片的問題:
由於壓縮圖片比較耗時,在很多情況下我們只修改了某些圖片,沒有必要壓縮所有圖片,所以可以使用”gulp-cache”只壓縮修改的圖片,沒有修改的圖片直接從緩存文件讀取。
具體解決辦法可參考(我沒有試過,哈哈):gulp教程之gulp-imagemin
這里提醒一下:
有同學在執行了gulp命令后發現html文件中並沒有加上版本號,如果你是按照我的配置文件去執行的,那么可能是你html文件中引入的css或js不是打包生成后的css或js。如果你說我就想在原來的html文件中引入的css或js的基礎上加版本號,那么你就要修改你的配置了,大概的配置如下:
//引入gulp和gulp插件
var gulp = require('gulp'),
rev = require('gulp-rev'),
revCollector = require('gulp-rev-collector');
//定義css、js源文件路徑
var cssSrc = 'css/*.css',
jsSrc = 'js/*.js',
maniFestJsonSrc = 'dist/**/*.json', //所有rev-manifest.json的路徑
htmlSrc = './*.html'; //根目錄下所有的html
//CSS生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revCss', function(){
return gulp.src(cssSrc)
.pipe(rev()) //文件名加MD5后綴
.pipe(rev.manifest()) //必須有這個方法 生成一個rev-manifest.json
.pipe(gulp.dest('dist/css')); //將rev-manifest.json 保存到 dist/css 目錄內
});
//js生成文件hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revJs', function(){
return gulp.src(jsSrc)
.pipe(rev())
.pipe(rev.manifest())
.pipe(gulp.dest('dist/js'));
});
//Html替換css、js、img文件版本
gulp.task('revHtml', function(){
return gulp.src([maniFestJsonSrc, htmlSrc])
.pipe(revCollector())
.pipe(gulp.dest('./')); //輸出到根目錄
});
//按順序執行
gulp.task('default',gulp.series(
'revCss',
'revJs',
'revHtml'
));
目錄結構如下:
----------------------------------------------------- 這里是分隔符 ---------------------------------------------------------
寫在2017年11月28日,加了一個zip打包的任務。
寫在2018年7月18日,目前gulp版本已經到4了,而且很多所依賴的插件的版本也都更新了很多,所以再用之前的gulp配置文件中的某些配置來打包壓縮文件已經不可能了,除非你還安裝之前版本的gulp和其依賴的插件。那么現在就來看看如何更改gulp配置文件來適應gulp4的打包功能。
首先,新版本的gulp所依賴的很多插件都改由es6來實現了,但某些插件的配置文件還是需要我們來手動修改的,以達到我們想要的效果(如果你不想修改也可以),比如這樣式的:
但是上圖中需要修改的某些地方在其版本更新后,有些我們就找不到了,或者找到了卻跟之前的不太一樣,不知怎么修改,那么下邊就貼出到目前為止最新的這些插件的修改方法。
首先前文中提到的“更改gulp-rev和gulp-rev-collector”的修改方法如下:
第一處、
打開node_modules\gulp-rev\index.js
找到: manifest[originalFile] = revisionedFile;
更新為: manifest[originalFile] = originalFile + '?v=' + file.revHash;
上邊這里的修改沒有變,還是原來的改法。
第二處、
打開nodemodules\gulp-rev\nodemodules\rev-path\index.js
找到: return filename + '-' + hash + ext;
更新為: return filename + ext;
上邊這里的修改就變了,我試了好幾次,都沒有成功,就索性把之前老版本的代碼拷過來了,發現也能用,如下:
打開nodemodules\gulp-rev\nodemodules\rev-path\index.js
找到:return modifyFilename(pth, (filename, ext) => `${filename}-${hash}${ext}`); //這里是es6的寫法
更新為:return modifyFilename(pth, (filename, ext) => filename + ext);
第三處、
打開node_modules\gulp-rev-collector\index.js
找到: if ( !_.isString(json[key]) || path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' ) !== path.basename(key) ) {
更新為: if ( !_.isString(json[key]) || path.basename(json[key]).split('?')[0] !== path.basename(key) ) {
上邊這里的修改也變了,我也是把之前版本的代碼直接拷過來了,如下:
找到:
if (_.isObject(json)) {
var isRev = 1;
Object.keys(json).forEach(function (key) {
if (!_.isString(json[key])) {
isRev = 0;
return;
}
var cleanReplacement = path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' );
if (!~[
path.basename(key),
_mapExtnames(path.basename(key), opts)
].indexOf(cleanReplacement)
) {
isRev = 0;
}
});
if (isRev) {
data = json;
}
}
更新為:
if (_.isObject(json)) {
var isRev = 1;
Object.keys(json).forEach(function (key) {
if ( !_.isString(json[key]) || path.basename(json[key]).split('?')[0] !== path.basename(key) ) {
isRev = 0;
}
});
if (isRev) {
data = json;
}
}
第四處、
上文中提到的“4、繼續更改gulp-rev-collector”。
打開node_modules\gulp-rev-collector\index.js
第107行 regexp: new RegExp( '([\/\\\\\'"])' + pattern, 'g' ),
更新為: regexp: new RegExp( '([\/\\\\\'"])' + pattern+'(\\?v=\\w{10})?', 'g' ),
上邊這里的修改也變了,具體修改如下:
打開node_modules\gulp-rev-collector\index.js
找到:regexp: new RegExp( prefixDelim + pattern, 'g' ),
更新為: regexp: new RegExp( prefixDelim + pattern +'(\\?v=\\w{10})?', 'g' ),
其次,也是最主要的一點是gulp4不再能夠通過數組形式傳入任務,你需要使用gulp.series()和gulp.parallel()來執行他們。例如:
gulp.task('default',gulp.parallel('taskA','taskB'));//並行執行
gulp.task('default',gulp.series('taskA','taskB'));//按順序執行
所以,之前的寫法如gulp.task('default', function (done) {runSequence(('taskA','taskB'),done}
在gulp4下已經不能使用了,要改成如下的寫法:
gulp.task('default',gulp.series(
'less',
'assetRev',
'cssMin',
'revCss',
'uglify',
'revJs',
'imageMin',
'revImage',
'htmlMin',
'revHtml',
'zip'
));
其他的插件需要修改的地方還是上文中提到的改法,這里不做贅述,如下:
打開node_modules\gulp-asset-rev\index.js
找到:var verStr = (options.verConnecter || "-") + md5;
更新為:var verStr = (options.verConnecter || "") + md5;
找到:src = src.replace(verStr, '').replace(/(\.[^\.]+)$/, verStr + "$1");
更新為:src=src+"?v="+verStr;
如此,關於gulp4及其所依賴的插件的修改配置方法已經全部介紹完畢。
本文轉自:https://segmentfault.com/a/1190000006204457
DEMO下載:
只在原來的html中引入的css或js的基礎上添加版本號