1. 程式人生 > >在Maven+Spring專案中使用Node.js的Gulp進行前端自動化構建

在Maven+Spring專案中使用Node.js的Gulp進行前端自動化構建

剛開始打算進行前後端分離開發,後來發現在使用JSP或者Freemarker做動態頁面時,想發揮這些自動化構建工具牛逼閃閃的livereload功能並不是那麼的輕易,因為我們必須還得調教它們去呼叫Java容器。現在全球社群似乎還沒有成熟的外掛可以自動幫我們調教Java容器,百度Fis的Jello也只是做了一下velocity的自動化,自己寫感覺就是自虐,所以在這個問題上倒不如把Gulp當成一個Maven來使用,反正J2EE開發人員應該大都習慣了修改程式碼之後漫長無盡的build。相反,如果對Gulp調教好了watchify,只對發生了修改的檔案進行重新構建,那麼速度必定不是問題,也不必要我們每次修改都手動打包。

目前剛剛開始應用這一技術,用Gulp主要為了做前端工程和程式碼優化,之所以沒有選擇相較而言社群更加健壯的Grunt,可能主要還是因為Gulp的程式碼寫起來更加輕鬆,而且採用Stream對構建速度有明顯的提升。本文使用Gulp主要為了實現以下幾個功能:優化CSS,對CSS進行合併,壓縮,加MD5版本控制,生成Map對映;優化JS,對JS進行合併,壓縮,加MD5版本控制,生成Map對映;優化JSP頁面檔案,自動產生對應原始檔案壓縮後資原始檔的路徑。我們最終要達到的效果是,壓縮後的CSS檔案一個,壓縮後的JS檔案一個,JSP頁面自動產生對應MD5版本的資原始檔。之所以要加MD5進行檔案版本控制,是因為MD5在檔案未發生修改的情況下,是不會發生改變的,因此伺服器相應的資源就不需要進行替換。我們還要考慮到將所有同類資源合併到一個檔案內的弊端,那就是如果網站內容較多時,可能大部分資源並不是一個頁面真正需要的,這並不是理想的優化效果。對於這個問題,我們可以考慮結合使用browserify等模組管理工具。更省事的方法是,簡單分析一下網站對資源的需求。一般的站點通常有一個入口網站和一個後臺管理系統,入口網站頁面使用到的資源和後臺管理系統使用的資源可能會有較大的差異性,而兩者自身的頁面之間其實差異並不是非常大。所以我們也可以專門為入口網站和後臺系統設計兩套資源,省去了使用Browserify構建可能產生的大量冗餘檔案。

使用Gulp之前,我們首先需要安裝一下Node.js和NPM包管理工具。因為我們是在Maven+Spring框架下做前端開發,因此在正式寫程式碼之前,還需要搭建好一個maven的專案。一個典型的Maven專案可以如下圖所示,在這裡我們加了2個maven專案模組,靜態資源放在webapp模組中,動態頁面放在WEB-INF內,配置完成之後,maven專案就可以正常工作了。

1 類似於maven,npm和gulp也需要相應的配置檔案,分別是package.json和gulpfile.js。gulp中我們所需要用到的外掛如下:

<!-- lang: js -->
var gulp = require
('gulp'), rev = require('gulp-rev'), minifycss = require('gulp-minify-css'), uglify = require('gulp-uglify'), concat = require('gulp-concat'), sourcemaps = require('gulp-sourcemaps'), del = require('del'), revreplace = require('gulp-rev-replace');

使用npm install package-name --save-dev指令可以進行對應外掛的安裝,或者在配置好package.json檔案之後,使用npm install進行一次性安裝,所有這些庫檔案會生成到根目錄下的node_module資料夾內。

gulp的工作流程如下:

首先在專案根目錄下建立一個src資料夾,將前端需要的靜態資源放在資料夾內作為原始碼開發使用。gulp工作流首先讀取這些檔案,進行對應的合併壓縮處理,最後將產品輸出到maven模組相應的靜態資源路徑和動態頁面路徑下,這樣就完成了一整套簡單的自動化構建過程。在下面這個例子中,我們分別構建一個簡單的登陸頁面和後臺頁面流程,最後用一個gulp預設指令完成全部工作。首先定義一下檔案輸入目錄:

<!-- lang: js -->
var path = {
    css: 'maven-webapp/webapp/static/css',
    js: 'maven-webapp/webapp/static/js',
    jsp: 'maven-webapp/webapp/WEB-INF/jsp'
};

然後寫一個login介面的css壓縮合並優化生產線。如下文所示,在這段程式碼中,我們首先用"del"指令刪除上一次構建產生的舊檔案。其中rev-manifest.json是MD5版本工具輸出的一組資料,它的作用是記錄合併後的檔案添加了MD5版本數之後的檔名。將4個檔案進行合併,合併之前初始化sourcemap,這樣在處理完成之後可以生成對映檔案,有了這個對映檔案,我們就可以在谷歌瀏覽器的除錯工具下面檢視到壓縮前原始碼的形式。concat執行檔案合併指令,path定義了合併結果的檔名,合併之後用minifycss壓縮整理,緊接著給檔案加上MD5版本號,到這裡修改完成,寫出對映檔案,並將結果匯出到maven專案的目錄裡面。

<!-- lang: js -->
gulp.task('login-css-min', function() {
del(['rev-manifest.json',
    path.css + '/login.*.*',
    path.js + '/login-bundle.*.*',
    path.jsp + '/login.jsp'], function(err, deletedFiles) {
    console.log('Files deleted:\n', deletedFiles.join('\n'));
});
return gulp.src(['src/css/bootstrap.css',
    'src/css/bootstrap-reset.css',
    'src/css/style-responsive.css',
    'src/css/login.css'])
    .pipe(sourcemaps.init())
    .pipe(concat({path:'login.min.css', cwd: ''}))
    .pipe(minifycss())
    .pipe(rev())
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest(path.css))
    .pipe(rev.manifest())
    .pipe(gulp.dest(''));
});

js的合併和壓縮是類似的。但是需要注意,當我們合併js的時候,還是儘量使用閉包的匿名函式,避免外掛汙染全域性變數。在下面的程式碼中,我在最頭上加了一個namespace.js的檔案,將它暴露在全域性環境中,目的就是讓它做名稱空間的管理。

<!-- lang: js -->
gulp.task('login-js-min', ['login-css-min'], function() {
return gulp.src(['src/js/Namespace.js', 'src/js/lib/jquery.js', 'src/js/main.js'])
    .pipe(sourcemaps.init())
    .pipe(concat({path:'login-bundle.min.js', cwd: ''}))
    .pipe(uglify())
    .pipe(rev())
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest(path.js))
    .pipe(rev.manifest({base:'', merge: true}))
    .pipe(gulp.dest(''));
});

第三步,根據rev-manifest.json的檔名對映,把jsp內對應的資源路徑修改成加了MD5版本數字後的路徑名。

<!-- lang: js -->
gulp.task("login-build", ['login-js-min'], function() {
var manifest = gulp.src("rev-manifest.json");
return gulp.src( "src/jsp/login.jsp")
    .pipe(revreplace({replaceInExtensions: ['.jsp'], manifest: manifest}))
    .pipe(gulp.dest(path.jsp));
});

同樣的方法做一下後臺頁面的資源合併壓縮:

<!-- lang: js -->
gulp.task('index-css-min', function() {
del([
    'rev-manifest.json',
    path.css + '/index.*.*',
    path.js + '/index-bundle.*.*',
    path.jsp + '/index/*.*'], function(err, deletedFiles) {
    console.log('Files deleted:\n', deletedFiles.join('\n'));
});
return gulp.src([
    'src/css/index/jquery.fullPage.css',
    'src/css/index/tipso.min.css',
    'src/css/index/show.css',
    'src/css/index/style.css'
    ])
    .pipe(sourcemaps.init())
    .pipe(concat({path:'index.min.css', cwd: ''}))
    .pipe(minifycss())
    .pipe(rev())
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest(path.css))
    .pipe(rev.manifest())
    .pipe(gulp.dest(''));
});

gulp.task('index-js-min', ['index-css-min'], function() {
return gulp.src([
    'src/js/Namespace.js',
    'src/js/lib/jquery.js',
    'src/js/lib/jquery.*.min.js',
    'src/js/lib/jquery-ui-1.10.3.min.js',
    'src/js/lib/smooth-scroll.min.js',
    'src/js/lib/tipso.min.js',
    'src/js/lib/transit.js',
    'src/js/index/*.js'])
    .pipe(sourcemaps.init())
    .pipe(concat({path:'index-bundle.min.js', cwd: ''}))
    .pipe(uglify())
    .pipe(rev())
    .pipe(sourcemaps.write('.'))
    .pipe(gulp.dest(path.js))
    .pipe(rev.manifest({base:'', merge: true}))
    .pipe(gulp.dest(''));
});

gulp.task("index-build", ['index-css-min','index-js-min'], function() {
var manifest = gulp.src("rev-manifest.json");
return gulp.src( "src/jsp/index/*.jsp")
    .pipe(revreplace({replaceInExtensions: ['.jsp'], manifest: manifest}))
    .pipe(gulp.dest(path.jsp + '/index/'));
});

最後將兩個步驟合併到default中去,就完成了自動化。

<!-- lang: js -->
gulp.task('default', ['login-build', 'index-build'], function() {
// place code for your default task here
});

結果如下圖所示:

2

3

如果需要讓多個jsp頁面共用一塊資源,可以將這些頁面放在一個資料夾中統一進行處理。