一個gulp用於開發與生產的示例

gulp是一款流行的前端構建工具,能夠幫咱們完成許多工做:監聽文件修改、刷新瀏覽器、編譯Less/Scss、壓縮代碼、添加md五、合併文件等。gulp的配置和使用特別簡單,學習gulp過程當中順便寫了一個小示例。javascript

能夠在GitHub上下載該示例:https://github.com/gymmer/gulp-examplecss


準備工做

  1. gulp是基於Node.js的。所以需先安裝Node。
  2. 接下來使用npm安裝gulp:sudo npm install -g gulp-g表示安裝到全局環境,能夠在任何項目中使用gulp。
  3. 進入到項目所在目錄。
  4. 若是項目沒有基於npm,則使用npm init建立package.json
  5. 在項目中安裝gulp:npm install --save-dev gulp--save-dev表示安裝的同時更新package.json中的依賴。

項目結構

示例採用了以下的項目結構:html

.
├── README.md       // git的README文件
├── dist            // 生產環境目錄。經過gulp構建自動生成
├── gulpfile.js     // gulp的配置文件
├── node_modules    // node必備
├── package.json    // node必備
└── src             // 開發環境目錄。存放項目的源代碼
    ├── css             // 樣式目錄。保存CSS文件和字體文件
    │   ├── compile         // CSS文件目錄。保存less/sass編譯後生成的CSS文件
    │   ├── fonts           // 字體文件目錄。保存Font Awesome或其餘字體文件
    │   ├── lib             // CSS文件目錄。保存引用的第三方CSS文件,如Bootstrap
    │   └── user            // CSS文件目錄。保存用戶自定義的CSS文件
    ├── img             // 圖片目錄。保存圖片文件
    ├── index.html      // 項目的HTML入口文件
    ├── js              // 腳本目錄。保存JavaScript文件
    │   ├── lib             // JS文件目錄。保存引用的第三方JS文件,如jQuery
    │   └── user            // JS文件目錄。保存用戶自定義的JS文件
    ├── less            // Less文件目錄
    └── sass            // Sass文件目錄

示例中,CSS和JS將第三方庫和用戶自定義文件分開存放,雖然爲gulp的配置帶來稍許難度,可是更有利於項目的邏輯結構。前端


插件一覽

gulp生態圈有不少成熟的插件,示例中用到的有:java

"devDependencies": {
    "gulp": "^3.9.1",
    "gulp-cache": "^0.4.6",
    "gulp-clean": "^0.3.2",
    "gulp-clean-css": "^3.3.1",
    "gulp-compass": "^2.1.0",
    "gulp-connect": "^5.0.0",
    "gulp-htmlmin": "^3.0.0",
    "gulp-imagemin": "^3.2.0",
    "gulp-jshint": "^2.0.4",
    "gulp-less": "^3.3.0",
    "gulp-load-plugins": "^1.5.0",
    "gulp-notify": "^3.0.0",
    "gulp-plumber": "^1.1.0",
    "gulp-rev": "^7.1.2",
    "gulp-rev-collector": "^1.1.1",
    "gulp-sass": "^3.1.0",
    "gulp-uglify": "^2.1.2",
    "jshint": "^2.9.4"
  }

配置一覽

gulp的配置依賴於文件gulpfile.js。這是示例配置文件一覽,接下來將介紹每一個插件:node

'use strict';

var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();

// JS代碼校驗。只校驗用戶自定義的js文件,不校驗第三方js文件,如jQuery
gulp.task('jslint', function() {
    return gulp.src('src/js/user/**/*.js')
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter());
});

// 監聽全部文件改動:自動刷新
gulp.task('reload', function() {
    return gulp.src('src/**/*')
        .pipe(plugins.connect.reload());
});

// 編譯less文件,並監聽less文件改動:從新編譯+自動刷新
gulp.task('less', function() {
    return gulp.src('src/less/**/*.less')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止less出錯,自動退出watch
        .pipe(plugins.less())
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

// 編譯sass文件,並監聽sass文件改動:從新編譯+自動刷新
gulp.task('sass', function() {
    return gulp.src('src/sass/**/*.{sass,scss}')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出錯,自動退出watch
        // (1) 若是隻使用 sass, 請使用sass插件:
        // .pipe(plugins.sass({
        //  outputStyle: 'expanded'  // 可選:nested  (默認)  |  expanded  |  compact  |  compressed
        // }))
        // (2) 若是使用 compass, 請使用compass插件:
        .pipe(plugins.compass({
            css: 'src/css/compile',
            sass: 'src/sass',
            image: 'src/img',
            style: 'expanded' // 可選:nested  (默認)  |  expanded  |  compact  |  compressed
        }))
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

// 監聽文件改動
gulp.task('watch', function() {
    gulp.watch('src/**/*', ['reload']);
    gulp.watch('src/less/**/*.less', ['less']);
    gulp.watch('src/less/**/*.{sass,scss}', ['sass']);
});

// 運行一個服務器
gulp.task('server', function() {
    plugins.connect.server({
        root: 'src',
        port: 8080,  // Can not be 80
        livereload: true
    });
});

// 默認任務
gulp.task('default', function() {
    gulp.run('jslint', 'reload', 'less', 'sass', 'watch', 'server');
});

// build

gulp.task('clean', function() {
    return gulp.src('dist')
        .pipe(plugins.clean());
});

gulp.task('build-js', function() {
    return gulp.src('src/**/*.js')
        .pipe(plugins.uglify())                 // JS壓縮
        .pipe(plugins.rev())                    // 添加MD5
        .pipe(gulp.dest('dist'))                // 保存JS文件
        .pipe(plugins.rev.manifest())           // 生成MD5映射
        .pipe(gulp.dest('dist/rev/js'));        // 保存映射
});

gulp.task('build-css', ['less', 'sass'], function() {   // 編譯less/sass
    return gulp.src('src/**/*.css')
        .pipe(plugins.cleanCss())               // CSS壓縮 
        .pipe(plugins.rev())                    // 添加MD5
        .pipe(gulp.dest('dist'))                // 保存CSS文件
        .pipe(plugins.rev.manifest())           // 生成MD5映射
        .pipe(gulp.dest('dist/rev/css'));       // 保存映射
});

gulp.task('build-html', ['build-js', 'build-css'], function() {     // 依賴:需先生成映射
    return gulp.src(['dist/rev/**/*.json', 'src/**/*.html'])
        .pipe(plugins.revCollector())                       // 根據映射,替換文件名
        .pipe(plugins.htmlmin({collapseWhitespace: true}))  // HTML壓縮
        .pipe(gulp.dest('dist'));                           // 保存HTML文件
});

gulp.task('build-fonts', function() {
    return gulp.src('src/**/*.{eot,ttf,woff,woff2,otf}')
        .pipe(gulp.dest('dist'));
});

gulp.task('build-img', function() {
    return gulp.src('src/**/*.{png,jpg,gif,jpeg,svg}')
        .pipe(plugins.cache(plugins.imagemin({  // 圖片壓縮
            interlaced: true
        })))
        .pipe(gulp.dest('dist'));
});

gulp.task('build', ['clean'], function() {
    return gulp.run('build-html', 'build-fonts', 'build-img', function() {
        gulp.src('dist/rev').pipe(plugins.clean());
    });
});

配置詳解

一般開發環境和生產環境對項目構建的需求是不同的。開發環境側重於調試代碼,如監聽文件修改、CSS預編譯等。而生產環境側重於項目的上線與部署,如壓縮代碼、壓縮圖片、添加md5等。爲此,示例定義了兩項gulp任務,以知足開發與生產的不一樣需求。git

另外,每個gulp插件都有豐富的功能和配置,本示例的目的不在於講解每一個插件的詳細用法,而在於演示如何組合使用經常使用插件的基本功能,完成平常開發任務。github

插件詳解可移步npm官網Githubweb

自動加載插件 (gulp-load-plugins)

官網:https://www.npmjs.com/package/gulp-load-pluginsnpm

安裝:npm install --save-dev gulp-load-plugins

使用gulp插件的通常過程是:

  1. 使用npm安裝在局部環境,並更新package.jsonnpm install --save-dev gulp-foo-plugin
  2. gulpfile.js中加載插件:var foo = require('gulp-foo-plugin')
  3. 在合適的地方使用插件:gulp.src('src/*.js').pipe(foo())

若是加載太多gulp插件,gulpfile.js文件開頭會有大量require代碼,致使文件冗長,而且容易遺漏:

var gulp = require('gulp');
var foo = require('gulp-foo-plugin');
var boo = require('gulp-boo-plugin');
var bar = require('gulp-bar-plugin');

gulp.task('foo', function() {
    return gulp.src('src/*.js')
        .pipe(foo())
        .pipe(gulp.dest('dist/*.js'));
});

gulp.task('boo', function() {
    return gulp.src('src/*.css')
        .pipe(boo())
        .pipe(gulp.dest('dist/*.css'));
});

gulp.task('bar', function() {
    return gulp.src('src/*.less')
        .pipe(bar())
        .pipe(gulp.dest('dist/*.less'));
});

gulp-load-plugins能夠避免這樣的問題。

  1. 安裝gulp-load-pluginsnpm install --save-dev gulp-load-plugins
  2. 安裝其餘插件:npm install --save-dev gulp-foo-plugin, gulp-boo-plugin, gulp-bar-plugin
  3. gulpfile.js中只需加載gulp-load-pluginsvar plugins = require('gulp-load-plugins')()
  4. 使用其餘插件時,將其做爲plugins對象的方法進行調用。以下:
var gulp = require('gulp');
var plugins = require('gulp-load-plugins')();

gulp.task('foo', function() {
    return gulp.src('src/*.js')
        .pipe(plugins.fooPlugin())
        .pipe(gulp.dest('dist/*.js'));
});

gulp.task('boo', function() {
    return gulp.src('src/*.css')
        .pipe(plugins.booPlugin())
        .pipe(gulp.dest('dist/*.css'));
});

gulp.task('bar', function() {
    return gulp.src('src/*.less')
        .pipe(plugins.barPlugin())
        .pipe(gulp.dest('dist/*.less'));
});

這樣,就沒必要寫冗長的require()了!

gulp-load-plugins會自動尋找package.json中以gulp-爲前綴的依賴項。因此必定記得更新package.json

本示例將大量使用gulp-load-plugins


開發環境

本地服務器 (gulp-connect)

官網:https://www.npmjs.com/package/gulp-connect

安裝:npm install --save-dev gulp-connect

本地調試時一般須要搭建一個web服務器。我選用的是gulp-connect,配合gulp.watch()還能夠實現自動刷新瀏覽器。

// 運行一個服務器
gulp.task('server', function() {
    plugins.connect.server({
        root: 'src',        // 根目錄
        port: 8080,         // 端口號。經測試,若是設置爲80會報錯。
        livereload: true    // 啓用livereload,刷新瀏覽器
    });
});

gulp-connect還能夠啓動多個server,併爲每一個server指定名稱。具體看應用場景。

監聽文件修改 + 自動刷新 (gulp.watch)

利用gulp-connect的livereload功能,和gulp.watch()函數,能夠實現監聽文件的修改,並自動刷新瀏覽器。

// 監聽全部文件改動:自動刷新
gulp.task('reload', function() {
    return gulp.src('src/**/*')
        .pipe(plugins.connect.reload());
});

// 監聽文件改動
gulp.task('watch', function() {
    gulp.watch('src/**/*', ['reload']);
    gulp.watch('src/less/**/*.less', ['less']);
    gulp.watch('src/less/**/*.{sass,scss}', ['sass']);
});

這裏比較暴力,直接監聽了全部文件的修改。能夠根據文件類型定義具體的規則。好比,但願修改less/sass文件的同時,從新編譯並刷新瀏覽器。那麼須要在less/sass任務中,觸發connect.reload()less/sass任務在後文有詳細介紹。

JS代碼校驗 (gulp-jshint)

官網:https://www.npmjs.com/package/gulp-jshint

安裝:npm install --save-dev gulp-jshint jshint

代碼校驗可使程序更健壯、更嚴謹,哪怕漏掉一個可有可無的分號,都逃不過jshint的法眼。

使用比較簡單。須要注意的是,在本示例的項目結構中,第三方JS文件和自定義JS文件分開存儲,所以只校驗了自定義的JS文件。

// JS代碼校驗。只校驗用戶自定義的js文件,不校驗第三方js文件,如jQuery
gulp.task('jslint', function() {
    return gulp.src('src/js/user/**/*.js')
        .pipe(plugins.jshint())
        .pipe(plugins.jshint.reporter());
});

使用Less (gulp-less)

官網:https://www.npmjs.com/package/gulp-less

安裝:npm install --save-dev gulp-less gulp-plumber gulp-notify

編譯less至CSS文件。基本用法爲:

gulp.task('less', function() {
    return gulp.src('src/less/**/*.less')
        .pipe(plugins.less())
        .pipe(gulp.dest('src/css/compile'));
});

雖然編譯生成了CSS,可是並不能知足需求。

首先,前面介紹過,若是但願修改less文件的同時,編譯less並刷新瀏覽器,則須要在less任務中,觸發connect.reload()

其次,當編譯less時出現語法錯誤,會致使watch終止,而錯誤信息須要進入命令行中查看。因而你會發現,修改less後瀏覽器不會自動刷新了,查看CSS發現less沒有被編譯,而整個過程居然沒有主動提醒我!解決方法是gulp-plumber+gulp-notify

完整的解決方案以下:

// 編譯less文件,並監聽less文件改動:從新編譯+自動刷新
gulp.task('less', function() {
    return gulp.src('src/less/**/*.less')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止less出錯,自動退出watch
        .pipe(plugins.less())
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

使用Sass (gulp-sass)

官網:https://www.npmjs.com/package/gulp-sass

安裝:npm install --save-dev gulp-sass gulp-plumber gulp-notify

與Less相比,幾點不一樣是:

  • Sass有兩種文件後綴名,.sass.scss。最保險的方法是都編譯一下。
  • Sass能夠指定編譯生成CSS的格式。有四種格式可選:nested(默認)、expanded、compact、compressed
// 編譯sass文件,並監聽sass文件改動:從新編譯+自動刷新
gulp.task('sass', function() {
    return gulp.src('src/sass/**/*.{sass,scss}')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')}))       // 防止sass出錯,自動退出watch
        .pipe(plugins.sass({
            outputStyle: 'expanded'     // CSS格式
        }))
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

使用Compass (gulp-compass)

官網:https://www.npmjs.com/package/gulp-compass

準備:需安裝compass:gem install compass

安裝:npm install --save-dev gulp-compass gulp-plumber gulp-notify

Compass是Sass的神器。使用Compass插件的配置要稍稍複雜:

gulp.task('compass', function() {
    return gulp.src('src/sass/**/*.{sass,scss}')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出錯,自動退出watch
        .pipe(plugins.compass({
            css: 'src/css/compile',
            sass: 'src/sass',
            image: 'src/img',
            style: 'expanded'     // CSS格式
        }))
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

gulp-sassgulp-compass都是編譯Sass,何須分家,搞得那麼累?因而,我合併了這兩個任務,應用時根據具體場景,註釋掉相應代碼便可。

// 編譯sass文件,並監聽sass文件改動:從新編譯+自動刷新
gulp.task('sass', function() {
    return gulp.src('src/sass/**/*.{sass,scss}')
        .pipe(plugins.plumber({errorHandler: plugins.notify.onError('Error: <%= error.message %>')})) // 防止sass出錯,自動退出watch
        // (1) 若是隻使用 sass, 請使用sass插件:
        // .pipe(plugins.sass({
        //  outputStyle: 'expanded'
        // }))
        // (2) 若是使用 compass, 請使用compass插件:
        .pipe(plugins.compass({
            css: 'src/css/compile',
            sass: 'src/sass',
            image: 'src/img',
            style: 'expanded' 
        }))
        .pipe(gulp.dest('src/css/compile'))
        .pipe(plugins.connect.reload());
});

任務組合

這麼多小任務,是時候組合一下了。考慮到最常使用的是開發環境,而生產環境只是在上線迭代時才接觸。所以,示例將defalut任務分配給開發環境。

// 默認任務
gulp.task('default', function() {
    gulp.run('jslint', 'reload', 'less', 'sass', 'watch', 'server');
});

生產環境

清空構建目錄 (gulp-clean)

官網:https://www.npmjs.com/package/gulp-clean

安裝:npm install --save-dev gulp-clean

每次構建目錄,若是但願清理上次構建的文件,可使用gulp-clean插件,免去了手動刪除的操做。

gulp.task('clean', function() {
    return gulp.src('dist')
        .pipe(plugins.clean());
});

壓縮JS (gulp-uglify)

官網:https://www.npmjs.com/package/gulp-uglify

安裝:npm install --save-dev gulp-uglify

方法:

gulp.task('minify-js', function() {
    return gulp.src('src/**/*.js')
        .pipe(plugins.uglify())                 // JS壓縮
        .pipe(gulp.dest('dist'));               // 保存JS文件
});

壓縮CSS (gulp-clean-css)

官網:https://www.npmjs.com/package/gulp-clean-css

安裝:npm install --save-dev gulp-clean-css

方法:

gulp.task('minify-css', ['less', 'sass'], function() {    // 編譯less/sass
    return gulp.src('src/**/*.css')
        .pipe(plugins.cleanCss())               // CSS壓縮 
        .pipe(gulp.dest('dist'));               // 保存CSS文件
});

須要注意的是,本示例在壓縮CSS以前,進行了一次Less/Sass編譯,防止CSS文件丟失。

有的教程會提到使用gulp-minify-css,不過npm官網已不推薦gulp-minify-css

This package has been deprecated. Please use gulp-clean-css instead.

壓縮HTML (gulp-htmlmin)

官網:https://www.npmjs.com/package/gulp-htmlmin

安裝:npm install --save-dev gulp-htmlmin

方法:

gulp.task('minify-html', function() {
    return gulp.src('src/**/*.html')
        .pipe(htmlmin({collapseWhitespace: true}))  // HTML壓縮
        .pipe(gulp.dest('dist'));                   // 保存HTML文件
});

有的教程會提到使用gulp-minify-html,不過npm官網已不推薦gulp-minify-html

This package has been deprecated in favor of gulp-htmlmin, which should be faster and more comprehensive.

壓縮圖片 (gulp-imagemin)

官網:https://www.npmjs.com/package/gulp-imagemin

安裝:npm install --save-dev gulp-imagemin gulp-cache

方法:

gulp.task('build-img', function() {
    return gulp.src('src/**/*.{png,jpg,gif,jpeg,svg}')
        .pipe(plugins.cache(plugins.imagemin({  // 圖片壓縮
            interlaced: true
        })))
        .pipe(gulp.dest('dist'));
});

添加MD5 (gulp-rev)

官網:https://www.npmjs.com/package/gulp-rev

安裝:npm install --save-dev gulp-rev

項目上線時 ,一般會爲靜態資源文件添加一個「後綴」,用來解決緩存更新問題。經常使用的後綴有時間戳、版本號、文件MD5值。本示例採用MD5值。

gulp-rev插件會根據原文件生成一個新文件,其文件名會自動追加MD5。原文件與新文件的映射關係也須要保留下來,路徑替換時會用到映射關係。

方法:

gulp.task('MD5', function() { 
    return gulp.src('src/*.css')
        .pipe(plugins.rev())            // 添加MD5
        .pipe(gulp.dest('dist'))
        .pipe(plugins.rev.manifest())   // 生成MD5映射
        .pipe(gulp.dest('./rev'));      // 保存映射
});

將壓縮JS、壓縮CSS和添加MD5這三項任務組合起來,便有了:

gulp.task('build-js', function() {
    return gulp.src('src/**/*.js')
        .pipe(plugins.uglify())                 // JS壓縮
        .pipe(plugins.rev())                    // 添加MD5
        .pipe(gulp.dest('dist'))                // 保存JS文件
        .pipe(plugins.rev.manifest())           // 生成MD5映射
        .pipe(gulp.dest('dist/rev/js'));        // 保存映射
});

gulp.task('build-css', ['less', 'sass'], function() {    // 編譯less/sass
    return gulp.src('src/**/*.css')
        .pipe(plugins.cleanCss())              // CSS壓縮 
        .pipe(plugins.rev())                    // 添加MD5
        .pipe(gulp.dest('dist'))                // 保存CSS文件
        .pipe(plugins.rev.manifest())           // 生成MD5映射
        .pipe(gulp.dest('dist/rev/css'));       // 保存映射
});

路徑替換 (gulp-rev-collector)

官網:https://www.npmjs.com/package/gulp-rev-collector

安裝:npm install --save-dev gulp-rev-collector

添加MD5會致使靜態資源文件名的更改。所以HTML文件中須要對路徑作替換,才能保證正確的引用關係。

須要注意的是,路徑替換的依據是gulp-rev插件生成的映射關係文件,所以它依賴build-jsbuild-css任務。

gulp.task('build-html', ['build-js', 'build-css'], function() {
    return gulp.src(['dist/rev/**/*.json', 'src/**/*.html'])
        .pipe(plugins.revCollector())               // 根據映射,替換文件名
        .pipe(htmlmin({collapseWhitespace: true}))  // HTML壓縮
        .pipe(gulp.dest('dist'));                   // 保存HTML文件
});

拷貝其餘文件

項目中有一些文件在整個週期中不會被修改,上線發佈時也無需任何處理,如字體文件。那麼,直接將這類文件複製到構建目錄便可。

gulp.task('build-fonts', function() {
    return gulp.src('src/**/*.{eot,ttf,woff,woff2,otf}')
        .pipe(gulp.dest('dist'));
});

任務組合

生產環境的配置已大體完成。將上述任務組合:

gulp.task('build', ['clean'], function() {
    return gulp.run('build-html', 'build-fonts', 'build-img');
});

清空rev

添加MD5任務中生成的映射文件,在完成路徑替換後就結束了本身的使命。能夠將其刪除,使構建目錄更加清爽。方法是,在build任務中增長回調參數,使用gulp-clean插件清空映射文件目錄。

gulp.task('build', ['clean'], function() {
    return gulp.run('build-html', 'build-fonts', 'build-img', function() {
        gulp.src('dist/rev').pipe(plugins.clean());
    });
});

其餘

.gitignore

若是使用git作版本控制,有一些目錄是不須要提交的,能夠用.gitignore文件指定。例如:

/node_modules
/dist
/.sass-cache
.DS_Store

其餘功能推薦

  • sourcemap:gulp-sourcemaps
  • 合併文件:gulp-concat
  • 重命名文件:gulp-rename

參考

前端構建工具gulp入門教程

前端構建工具gulpjs的使用介紹及技巧

gulp教程之gulp-less

Gulp學習指南之CSS合併、壓縮與MD5命名及路徑替換

JavaScript Source Map 詳解

相關文章
相關標籤/搜索