Gulp介紹與入門實踐

Gulp,一個基於流的構建工具。css

這是本身寫的一個構建的demo,只是一個純演示的示例,並無完成什麼項目工做。下面根據這個demo介紹一下Gulp。html

上代碼:node

gulpfile.jsjquery

'use strict';

/**
 *  環境
 */

const env = process.argv.slice(6)[0] || 'development';
process.env.NODE_ENV = env;
const isDev = env == 'development';

/**
 *  依賴
 */
const browserSync = require('browser-sync');
const reload = browserSync.reload;

const watchify = require('watchify');
const browserify = require('browserify');
const babelify = require('babelify');
const envify = require('envify/custom');

const del = require('del');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const gulp = require('gulp');
const $ = require('gulp-load-plugins')();

/**
 * path
 */
const pathConf =  require('./path.js');
const path = pathConf.path;
const filePath = pathConf.filePath;


/**
 * browserify & watchfy
 */
// browserify conf
const customOpts = {
    entries: ['./src/main.js'],
    debug: true
};

// watchfy conf
const opts = Object.assign({}, watchify.args, customOpts);
// init watchfy
const browserify_watch = watchify(browserify(opts));
browserify_watch.transform(babelify, {
    presets: ["es2015", "stage-2"],
    plugins: ["transform-runtime"]
});
browserify_watch.transform(envify({
    NODE_ENV: env
}));

function jsBundler (ids){
    return browserify_watch
        .bundle()
        .on('error', $.util.log)
        .pipe(source("build.js"))
        .pipe(buffer())
        .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
        .pipe($.uglify())
        .pipe($.if(isDev, $.sourcemaps.write(".")))
        .pipe(gulp.dest(path.dist))
}

browserify_watch.on('update', jsBundler);
browserify_watch.on('log', $.util.log);

/**
 * gulp task
 */
gulp.task('cleanAll', () => {
    del([
        path.temp + '**/.{html.js.css}', 
        path.dist + '**/.{html.js.css}', 
        '!' + path.dist + 'video/**'
    ]);
});

gulp.task('serve', () => {
    browserSync({
        server: {
            // proxy: 'xx.xx.xx.xx',
            baseDir: __dirname
        }
    });
});

gulp.task('jsBundle', jsBundler);

gulp.task('less', () => {
    return gulp.src(filePath.src_less)
        .pipe($.less())
        .on("error", function (error){
            $.util.log(error);
            this.emit("end");
        })
        .pipe(gulp.dest(path.temp + 'css/'));
});

gulp.task('cssBundle', ['less'], () => {
    return gulp.src([filePath.temp_css])
        .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
        .pipe($.concat('build.css'))
        .pipe($.minifyCss())
        .pipe($.if(isDev, $.sourcemaps.write('.')))
        .pipe(gulp.dest(path.dist));
});

gulp.task('watchLess', () => {
    gulp.watch([filePath.src_less], ['cssBundle']);
});

gulp.task('watchDist', () => {
    gulp.watch(['index.html', path.dist + '*.*'], reload);
});

/**
 * cli
 * '$ gulp build' for development
 * '$ npm run dev' for development
 * '$ npm run pro' for production
 */
gulp.task('build', $.sequence(
      'cleanAll'
    , ['jsBundle', 'cssBundle']
    , ['watchLess', 'watchDist']
    , 'serve'
));

 

package.jsonwebpack

{
  "name": "gulp",
  "description": "gulp",
  "author": "james",
  "scripts": {
    "dev": "gulp build --gulpfile gulpfile.js --env development",
    "pro": "gulp build --gulpfile gulpfile.js --env production"
  },
  "dependencies": {
    "jquery": "^3.1.1"
  },
  "devDependencies": {
    "babel-core": "^6.17.0",
    "babel-plugin-transform-runtime": "^6.15.0",
    "babel-preset-es2015": "^6.16.0",
    "babel-preset-stage-2": "^6.17.0",
    "babel-preset-stage-3": "^6.17.0",
    "babel-runtime": "^6.11.6",
    "babelify": "^7.3.0",
    "browser-sync": "^2.17.3",
    "browserify": "^13.1.0",
    "del": "^2.2.2",
    "envify": "^3.4.1",
    "gulp": "^3.9.1",
    "gulp-changed": "^1.3.2",
    "gulp-concat": "^2.6.0",
    "gulp-debug": "^2.1.2",
    "gulp-filter": "^4.0.0",
    "gulp-if": "^2.0.1",
    "gulp-inject": "^4.1.0",
    "gulp-jshint": "^2.0.1",
    "gulp-less": "^3.1.0",
    "gulp-load-plugins": "^1.3.0",
    "gulp-minify-css": "^1.2.4",
    "gulp-minify-html": "^1.0.6",
    "gulp-notify": "^2.2.0",
    "gulp-rename": "^1.2.2",
    "gulp-rev-all": "^0.9.7",
    "gulp-sequence": "^0.4.6",
    "gulp-sourcemaps": "^2.0.1",
    "gulp-uglify": "^2.0.0",
    "gulp-util": "^3.0.7",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "watchify": "^3.7.0"
  }
}

 

文件結構以下:es6

 結構說明:web

main.js 爲入口文件npm

path.js 定義了一些路徑變量json

src 文件夾存放原始代碼gulp

dis 文件夾存放打包後的代碼

temp 文件夾存放零食文件

 

經過browserify轉譯commjs和es6語法,經過gulp-less轉譯less語法,把轉譯後的文件輸出到dist文件夾。

 

下面經過一些主要代碼介紹一下Gulp的工做方式:

 

先看這段代碼:

function jsBundler (ids){
    return browserify_watch
        .bundle()
        .on('error', $.util.log)
        .pipe(source("build.js"))
        .pipe(buffer())
        .pipe($.if(isDev, $.sourcemaps.init({loadMaps: true})))
        .pipe($.uglify())
        .pipe($.if(isDev, $.sourcemaps.write(".")))
        .pipe(gulp.dest(path.dist))
}

它主要是在watchify監聽到依賴的js文件有變更以後,會自動調用的一個回調函數。函數內部實現步驟爲:

1.經過browserify以main.js爲入口文件,打包依賴的全部js,並經過browserify的es2015預設插件來轉譯es6語法。最後將生成的流經過管道傳輸給vinyl-source-stream這個插件

2.普通流經過vinyl-source-stream處理之後再傳輸給vinyl-buffer

3.vinyl-buffer處理之後的流再傳輸給gulp-sourcemaps插件(若是環境條件判斷經過的話),生成原始文件和打包後的文件映射關係(便於調試)

4.再傳輸給gulp-uglify插件進行壓縮和混淆

5.輸出soucemap

6.輸出打包後的js文件到dist文件夾

 

看到這個流程難免有些疑問:爲啥browserify傳輸出流不能直接給gulp插件使用呢,要先經過vinyl插件的系列處理才能夠?

 

緣由是:browserify處理後輸出的流只是普通的node.js流,gulp的插件須要的是通過處理的vinyl流。vinyl能夠把普通的node.js的流轉換爲Vinyl File Object Stream。這樣,至關於把普通的流接入到了Gulp的處理體系內。若是須要完整流,能夠經過vinyl-buffer插件接收完整個流轉成二進制數據之後再進行處理,一般gulp-soucemaps、gulp-uglify這些須要完整文件進行映射或者替換的插件都須要buffer一次。不然在任務運行的時候會報流不支持的錯誤:

 

 

vinyl流的特色是它是Object風格的,除了流的內容content,還有path等屬性。記錄了文件的內容和文件名、文件路徑的信息。

一個很好的例子能夠解釋爲何須要這樣:

gulp.task("css", () => {
    gulp.src("./src/**/*.css")
        .pipe(gulp.dest("./dist/css/"));
});

這個Gulp任務很簡單,就是把src文件夾下面全部的css(不管子文件夾層級)都'搬運"到./dist/css/這個路徑下。任務執行結果固然代表這是一次成功的搬運。

單仔細看看不難發現一個蹊蹺的事情:./src/下的文件夾是不定的,能夠是不少層,也能夠只有一層,每一個css文件能夠是任何名字,那麼在dest到./dist/css/還能對應上文件夾層級和文件名,這是怎麼作到的呢?

其實就是經過vinyl流(對象)的path那些屬性來知道文件夾層級和文件名的。普通的node.js流只傳輸String或Buffer類型,也就是隻關注content。

Gulp不僅用到了文件的內容,並且還用到了這個文件的相關信息。所以,vinyl流就會有contents、path這樣的多個屬性了。

 

可是Gulp自己並不能直接生成vinyl流對象,而是依賴了一個叫作vinyl-fs的node模塊,這是一個相似於文件適配器的模塊。它提供三個方法:.src()、.dest()和.watch(),其中.src()將生成vinyl流對象,而.dest()將使用vinyl流,進行寫入操做。

在Gulp的包中能夠看到源碼:

var vfs = require('vinyl-fs');

// ...

Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;

// ...

能夠看到gulp的src和dest方法其實就是vinyl-fs的src和dest方法。

深刻探究以後能夠發現這麼一條依賴關係:gulp -> vinyl-fs ->vinyl -> glob-stream -> node-glob

 

從gulp.src開始到gulp.dest結束這一系列的工做流程大概是這樣的:

  首先經過node-glob來匹配路徑和文件,而後glob-stream把匹配到的文件流式讀取, 再經過vinyl輸出Gulp體系須要用到的vinyl流對象,src完成工做後 流被pipe下去,直到gulp.dest時候,經過先前已知的文件路徑和文件名進行寫入操做。

若是是從webpack/browserify的普通流開始,會有它們本身的一套文件流失讀取方式和輸出,只要經過vinyl改造後輸出vinyl流對象就能接入進Gulp體系。

 

值得注意的是:寫入是一個異步的操做。任務結束之後,不必定表示文件已經所有寫入!

Gulp比起Grunt更具的node.js風格, 並且其依賴的orchestrator的特性就是最大併發的執行任務。在提供了大併發和node.js異步操做特性的同時也帶來了很差的一點:鏈式任務很差控制。

這也是爲何build這個task是經過gulp-sequence來嚴格按照既定順序執行的(經過返回流和其餘異步處理的方式也能夠達到效果):

gulp.task('build', $.sequence(
      'cleanAll'
    , ['jsBundle', 'cssBundle']
    , ['watchLess', 'watchDist']
    , 'serve'
));

 

咱們必定不會但願這類事情發生:一邊寫入文件一邊又在刪除;下一個任務須要上個任務提供必要文件,但在執行的時候文件尚未寫入完;等等....

 

Gulp常被用於和老前輩Grunt相比較,通常都說Gulp的速度快,這其實只是Gulp基於管道流的處理速度相對於Grunt不停地生成臨時文件要快。讀取和寫入(I/O)操做還要看機器自身狀況。

 

Gulp意爲"狼吐虎咽",即擁有基於流的快速與支持高併發的特性。

相關文章
相關標籤/搜索