第一個gulp程序

提及來慚愧,一直用公司內部的工具,沒有用這些紅得發紫的東西。今天東抄西拼終於搞出第一個gulp應用。gulp是作什麼的,好處在哪兒我不廢話了。直入主題吧。javascript

先在D盤下創建一個xxxx目錄,而後打開控制檯,直接將npm install gulp。 裏面多出一個node_modules目錄安裝成功。css

而後xxxx目錄下面建一個src目錄,裏面建一個index.html文件,內容以下或你本身亂寫一點東西,咱們這個例子主要測試壓縮html。html

<!DOCTYPE html>
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
    </head>
    <body>
        <h2>寫在前面</h2>
        <p>原本是想寫個如何編寫gulp插件的科普文的,忽然探究欲又發做了,因而就有了這篇東西。。。翻了下源碼看了下<code>gulp.src()</code>的實現,不由由衷感慨:腫麼這麼複雜。。。</p>
        <h2>進入正題</h2>
        <p>首先咱們看下<code>gulpfile</code>裏面的內容是長什麼樣子的,頗有express中間件的味道是否是~<br />咱們知道<code>.pipe()</code>是典型的流式操做的API。很天然的,咱們會想到<code>gulp.src()</code>這個API返回的應該是個Stream對象(也許通過層層封裝)。本着一探究竟的目的,花了點時間把gulp的源碼大體掃了下,終於找到了答案。</p>
        <p>gulpfile.js</p>
        <pre class="hljs-dark"><code class="hljs javascript"><span class="hljs-keyword">var gulp = <span class="hljs-built_in">require(<span class="hljs-string">'gulp'),
    preprocess = <span class="hljs-built_in">require(<span class="hljs-string">'gulp-preprocess');

gulp.task(<span class="hljs-string">'default', <span class="hljs-function"><span class="hljs-keyword">function<span class="hljs-params">() {

    gulp.src(<span class="hljs-string">'src/index.html')
        .pipe(preprocess({USERNAME:<span class="hljs-string">'程序猿小卡'}))
        .pipe(gulp.dest(<span class="hljs-string">'dest/'));
});
</span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
        <h2>提早劇透</h2>
        <p>此處有內容劇透,若有對劇透不適者,請自行跳過本段落。。。</p>
        <blockquote>
            <p>gulp.src() 的確返回了定製化的Stream對象。能夠在github上搜索<code>ordered-read-streams</code>這個項目。</p>
            <p>大體關係是:<br />ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</p>


        </blockquote>
        <h2>探究之路</h2>
        <p>首先,咱們看下<code>require('gulp')</code>返回了什麼。從gulp的源碼來看,返回了<code>Gulp</code>對象,該對象上有<code>src</code>、<code>pipe</code>、<code>dest</code>等方法。很好,找到了咱們想要的<code>src</code>方法。接着往下看<br />參考:<a href="https://github.com/gulpjs/gulp/blob/master/index.js#L62" target="_blank">https://github.com/gulpjs/gulp/blob/master/index.js#L62</a></p>
        <p>gulp/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-keyword">var inst = <span class="hljs-keyword">new Gulp();
<span class="hljs-built_in">module.exports = inst;
</span></span></span></code></pre>
        <p>從下面的代碼能夠看到,<code>gulp.src</code>方法,其實是<code>vfs.src</code>。繼續<br />參考:<a href="https://github.com/gulpjs/gulp/blob/master/index.js#L25" target="_blank">https://github.com/gulpjs/gulp/blob/master/index.js#L25</a></p>
        <p>gulp/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-keyword">var vfs = <span class="hljs-built_in">require(<span class="hljs-string">'vinyl-fs');
<span class="hljs-comment">// 省略不少行代碼
Gulp.prototype.src = vfs.src;
</span></span></span></span></code></pre>
        <p>接下來咱們看下<code>vfs.src</code>這個方法。從<code>vinyl-fs/index.js</code>能夠看到,<code>vfs.src</code>實際是<code>vinyl-fs/lib/src/index.js</code>。<br />參考:<a href="https://github.com/wearefractal/vinyl-fs/blob/master/index.js" target="_blank">https://github.com/wearefractal/vinyl-fs/blob/master/index.js</a></p>
        <p>vinyl-fs/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-pi">'use strict';

<span class="hljs-built_in">module.exports = {
  src: <span class="hljs-built_in">require(<span class="hljs-string">'./lib/src'),
  dest: <span class="hljs-built_in">require(<span class="hljs-string">'./lib/dest'),
  watch: <span class="hljs-built_in">require(<span class="hljs-string">'glob-watcher')
};
</span></span></span></span></span></span></span></span></code></pre>
        <p>那麼,咱們看下<code>vinyl-fs/lib/src/index.js</code>。能夠看到,<code>gulp.src()</code>返回的,實際是<code>outputStream</code>這貨,而<code>outputStream</code>是<code>gs.create(glob, options).pipe()</code>得到的,差很少接近真相了,還有幾步而已。<br />參考:<a href="https://github.com/wearefractal/vinyl-fs/blob/master/lib/src/index.js#L37" target="_blank">https://github.com/wearefractal/vinyl-fs/blob/master/lib/src/index.js#L37</a></p>
        <p>vinyl-fs/lib/src/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-keyword">var defaults = <span class="hljs-built_in">require(<span class="hljs-string">'lodash.defaults');
<span class="hljs-keyword">var through = <span class="hljs-built_in">require(<span class="hljs-string">'through2');
<span class="hljs-keyword">var gs = <span class="hljs-built_in">require(<span class="hljs-string">'glob-stream');
<span class="hljs-keyword">var File = <span class="hljs-built_in">require(<span class="hljs-string">'vinyl');

<span class="hljs-comment">// 省略非重要代碼若干行

<span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">src<span class="hljs-params">(glob, opt) {
  <span class="hljs-comment">// 繼續省略代碼

  <span class="hljs-keyword">var globStream = gs.create(glob, options);

  <span class="hljs-comment">// when people write to use just pass it through
  <span class="hljs-keyword">var outputStream = globStream
    .pipe(through.obj(createFile))
    .pipe(getStats(options));

  <span class="hljs-keyword">if (options.read !== <span class="hljs-literal">false) {
    outputStream = outputStream
      .pipe(getContents(options));
  }
  <span class="hljs-comment">// 就是這裏了
  <span class="hljs-keyword">return outputStream
    .pipe(through.obj());
}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
        <p>咱們再看看<code>glob-stream/index.js</code>裏的<code>create</code>方法,最後的<code>return aggregate.pipe(uniqueStream);</code>。好的,下一步就是真相了,咱們去<code>ordered-read-streams</code>這個項目一探究竟。<br />參考:<a href="https://github.com/wearefractal/glob-stream/blob/master/index.js#L89" target="_blank">https://github.com/wearefractal/glob-stream/blob/master/index.js#L89</a></p>
        <p>glob-stream/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-keyword">var through2 = <span class="hljs-built_in">require(<span class="hljs-string">'through2');
<span class="hljs-keyword">var Combine = <span class="hljs-built_in">require(<span class="hljs-string">'ordered-read-streams');
<span class="hljs-keyword">var unique = <span class="hljs-built_in">require(<span class="hljs-string">'unique-stream');

<span class="hljs-keyword">var glob = <span class="hljs-built_in">require(<span class="hljs-string">'glob');
<span class="hljs-keyword">var minimatch = <span class="hljs-built_in">require(<span class="hljs-string">'minimatch');
<span class="hljs-keyword">var glob2base = <span class="hljs-built_in">require(<span class="hljs-string">'glob2base');
<span class="hljs-keyword">var path = <span class="hljs-built_in">require(<span class="hljs-string">'path');

<span class="hljs-comment">// 必須省略不少代碼

<span class="hljs-comment">// create 方法
create: <span class="hljs-function"><span class="hljs-keyword">function<span class="hljs-params">(globs, opt) {
    <span class="hljs-comment">// 繼續省略代碼
<span class="hljs-comment">// create all individual streams
    <span class="hljs-keyword">var streams = positives.map(<span class="hljs-function"><span class="hljs-keyword">function<span class="hljs-params">(glob){
      <span class="hljs-keyword">return gs.createStream(glob, negatives, opt);
    });

    <span class="hljs-comment">// then just pipe them to a single unique stream and return it
    <span class="hljs-keyword">var aggregate = <span class="hljs-keyword">new Combine(streams);
    <span class="hljs-keyword">var uniqueStream = unique(<span class="hljs-string">'path');

    <span class="hljs-comment">// TODO: set up streaming queue so items come in order

    <span class="hljs-keyword">return aggregate.pipe(uniqueStream);
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
        <p>真相來了,咱們看下<code>ordered-read-streams</code>的代碼,可能剛開始看不是很懂,不要緊,知道它實現了本身的<code>Stream</code>就能夠了(nodejs是有暴露相應的API讓開發者對Stream進行定製的),具體可參考:<a href="http://www.nodejs.org/api/stream.html#stream_api_for_stream_implementors" target="_blank">http://www.nodejs.org/api/stream.html#stream_api_for_stream_implementors</a></p>
        <p>代碼來自:<a href="https://github.com/armed/ordered-read-streams/blob/master/index.js" target="_blank">https://github.com/armed/ordered-read-streams/blob/master/index.js</a></p>
        <p>ordered-read-streams/index.js</p>
        <pre class="hljs-dark"><code class="hljs js"><span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-title">OrderedStreams<span class="hljs-params">(streams, options) {
  <span class="hljs-keyword">if (!(<span class="hljs-keyword">this <span class="hljs-keyword">instanceof(OrderedStreams))) {
    <span class="hljs-keyword">return <span class="hljs-keyword">new OrderedStreams(streams, options);
  }

  streams = streams || [];
  options = options || {};

  <span class="hljs-keyword">if (!<span class="hljs-built_in">Array.isArray(streams)) {
    streams = [streams];
  }

  options.objectMode = <span class="hljs-literal">true;

  Readable.call(<span class="hljs-keyword">this, options);

  <span class="hljs-comment">// stream data buffer
  <span class="hljs-keyword">this._buffs = [];

  <span class="hljs-keyword">if (streams.length === <span class="hljs-number">0) {
    <span class="hljs-keyword">this.push(<span class="hljs-literal">null); <span class="hljs-comment">// no streams, close
    <span class="hljs-keyword">return;
  }  

  streams.forEach(<span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-params">(s, i) {
    <span class="hljs-keyword">if (!s.readable) {
      <span class="hljs-keyword">throw <span class="hljs-keyword">new <span class="hljs-built_in">Error(<span class="hljs-string">'All input streams must be readable');
    }
    s.on(<span class="hljs-string">'error', <span class="hljs-function"><span class="hljs-keyword">function <span class="hljs-params">(e) {
      <span class="hljs-keyword">this.emit(<span class="hljs-string">'error', e);
    }.bind(<span class="hljs-keyword">this));

    <span class="hljs-keyword">var buff = [];
    <span class="hljs-keyword">this._buffs.push(buff);

    s.on(<span class="hljs-string">'data', buff.unshift.bind(buff));
    s.on(<span class="hljs-string">'end', flushStreamAtIndex.bind(<span class="hljs-keyword">this, i));
  }, <span class="hljs-keyword">this);
}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
        <p>參考:<a href="https://github.com/armed/ordered-read-streams/blob/master/index.js" target="_blank">https://github.com/armed/ordered-read-streams/blob/master/index.js</a></p>
        <h2>寫在後面</h2>
        <p>兜兜轉轉一大圈,終於找到了<code>gulp.src()</code>的源頭,大體流程以下,算是蠻深的層級。代碼細節神馬的,有興趣的同窗能夠深究一下。</p>
        <blockquote>
            <p>ordered-read-streams --> glob-stream --> vinyl-fs --> gulp.src()</p>
        </blockquote>
    </body>
</html>

好了,繼續安裝另外一個插件gulp-htmlmin。照着readme安裝就是,不過一下安裝這麼多依賴,黑壓壓一坨,着實嚇人!java

而後在xxxx目錄下,創建一個gulpfile.js文件,內容直接抄gulp-htmlmin的readme:node

var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');

gulp.task('minify', function() {
  gulp.src('src/*.html')
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(gulp.dest('dist'))
});

而後控制檯運行gulp命令,報錯,說什麼「 Task 'default' is not in your gulpfile」。只好求助谷歌,發現這個東西git

var gulp   = require('gulp');
var coffee = require('gulp-coffee');
 
gulp.task('scripts', function () {
  gulp.src('src/*.coffee')
    .pipe(coffee())
    .pipe(gulp.dest('./'));
});
 
gulp.task('watch', function () {
  gulp.watch('src/*.coffee', ['scripts']);
});
 
gulp.task('default', ['scripts', 'watch']);

因而將原來的代碼改裝一下: github

var gulp = require('gulp');
var htmlmin = require('gulp-htmlmin');

gulp.task('minify', function() {
  gulp.src('src/*.html')
    .pipe(htmlmin({collapseWhitespace: true}))
    .pipe(gulp.dest('dist'))
});
gulp.task('watch', function () {
  console.log('繼續壓死你!')
  gulp.watch('src/*.html', ['minify']);
});
gulp.task('default', ['minify', 'watch']);

運行gulp命令,生成dest目錄,裏面的index.html已經成功被壓縮。而且有了watch任務,之後咱們每次修改html,都會同步到dest中去。express

估計default任務應該是相似C語言的main方法那樣的東西,沒有它是沒法帶動其餘任務的。npm

接着咱們好好學一下其基礎吧。gulp

gulp有5個基本方法:src、dest、task、run、watch

gulp.src()

gulp模塊的src方法,用於產生數據流。它的參數表示所要處理的文件,通常有如下幾種形式:

  • js/app.js:指定確切的文件名
  • js/*.js:某個目錄全部後綴名爲js的文件
  • js/**/*.js:某個目錄及其全部子目錄中的全部後綴名爲js的文件
  • !js/app.js:除了js/app.js之外的全部文件
  • *.+(js|css):匹配項目根目錄下,全部後綴名爲js或css的文件

src方法的參數還能夠是一個數組,用來指定多個成員:

gulp.src(['js/**/*.js', '!js/**/*.min.js']);  

gulp.dest()

gulp模塊的dest方法,能夠用來傳送文件,同時寫入文件到指定目錄。能夠重複的發送傳遞給它的數據,所以能夠將文件傳送到多個目錄中。簡單的例子:

gulp.src('./client/templates/*.jade')  
    .pipe(jade())
    .pipe(gulp.dest('./build/templates'))
    .pipe(minify())
    .pipe(gulp.dest('./build/minified_templates'));

gulp.task()

gulp模塊的task方法,用於定義具體的任務。它的第一個參數是任務名,第二個參數是任務函數。下面是一個很是簡單的任務函數:

gulp.task('greet', function () {  
   console.log('Hello world!');
});

task方法還能夠指定按順序運行的一組任務:

gulp.task('build', ['css', 'js', 'imgs']);  

上面代碼先指定build任務,它按次序由css、js、imgs三個任務所組成。注意:因爲每一個任務都是異步調用,因此沒有辦法保證js任務的開始運行的時間,正好是css任務運行結束時間。

若是但願各個任務嚴格按次序運行,能夠把前一個任務寫成後一個任務的依賴模塊:

gulp.task('css', ['greet'], function () {  
   // Deal with CSS here
});

上面代碼代表,css任務依賴greet任務,因此css必定會在greet運行完成後再運行。

若是一個任務的名字爲default,就代表它是「默認任務」,在命令行直接輸入gulp命令,就會運行該任務:

gulp.task('default', function () {  
   // Your default task
});

gulp.run()

gulp模塊的run方法,表示要執行的任務。可能會使用單個參數的形式傳遞多個任務。注意:任務是儘量多的並行執行的,而且可能不會按照指定的順序運行:

gulp.run('scripts','copyfiles','builddocs');

gulp.run('scripts','copyfiles','builddocs', function(err) {  
    // 全部任務完成,或者觸發錯誤而終止
});

可使用gulp.run在其餘任務中運行任務。也能夠在默認任務中使用gulp.run 組織多個更小的任務爲一個大任務。

gulp.watch()

gulp模塊的watch方法,用於指定須要監視的文件。一旦這些文件發生變更,就運行指定任務:

gulp.task('watch', function () {  
   gulp.watch('templates/*.tmpl.html', ['build']);
});

上面代碼指定,一旦templates目錄中的模板文件發生變化,就運行build任務。

watch方法也能夠用回調函數,代替指定的任務:

gulp.watch('templates/*.tmpl.html', function (event) {  
   console.log('Event type: ' + event.type); 
   console.log('Event path: ' + event.path); 
});

另外一種寫法是watch方法所監控的文件發生變化時(修改、增長、刪除文件),會觸發change事件,能夠對change事件指定回調函數:

var watcher = gulp.watch('templates/*.tmpl.html', ['build']);

watcher.on('change', function (event) {  
   console.log('Event type: ' + event.type);
   console.log('Event path: ' + event.path);
});

除了change事件,watch方法還可能觸發如下事件:

  • end:回調函數運行完畢時觸發。
  • error:發生錯誤時觸發。
  • ready:當開始監聽文件時觸發。
  • nomatch:沒有匹配的監聽文件時觸發。

watcher對象還包含其餘一些方法:

  • watcher.end():中止watcher對象,不會再調用任務或回調函數。
  • watcher.files():返回watcher對象監視的文件。
  • watcher.add(glob):增長所要監視的文件,它還能夠附件第二個參數,表示回調函數。
  • watcher.remove(filepath):從watcher對象中移走一個監視的文件。

學完這些就能夠到其官網上找插件了,畢竟插件纔是王道。

相關文章
相關標籤/搜索