gulp

gulp安装

1. 全局安装 gulp:

$ npm install --global gulp

2. 作为项目的开发依赖(devDependencies)安装:

$ npm install --save-dev gulp

3. 在项目根目录下创建一个名为 gulpfile.js 的文件:

var gulp = require('gulp');

gulp.task('default', function() {
  // 将你的默认的任务代码放在这
});

4. 运行 gulp:

$ gulp

默认的名为 default 的任务(task)将会被运行,在这里,这个任务并未做任何事情。

想要单独执行特定的任务(task),请输入 gulp <task> <othertask>

下一步做什么呢?

你已经安装了所有必要的东西,并且拥有了一个空的 gulpfile。

 

 

我们可以通过一些例子来看gulp的使用

1.整合 streams 来处理错误

默认情况下,在 stream 中发生一个错误的话,它会被直接抛出,除非已经有一个时间监听器监听着 error 时间。 这在处理一个比较长的管道操作的时候会显得比较棘手。

通过使用 stream-combiner2,你可以将一系列的 stream 合并成一个,这意味着,你只需要在你的代码中一个地方添加监听器监听 error 时间就可以了。

这里是一个在 gulpfile 中使用它的例子

var combiner = require('stream-combiner2');
var uglify = require('gulp-uglify');
var gulp = require('gulp');

gulp.task('test', function() {
  var combined = combiner.obj([
    gulp.src('bootstrap/js/*.js'),
    uglify(),
    gulp.dest('public/bootstrap')
  ]);

  // 任何在上面的 stream 中发生的错误,都不会抛出,
  // 而是会被监听器捕获
  combined.on('error', console.error.bind(console));

  return combined;
});

  

删除文件和文件夹

你也许会想要在编译文件之前删除一些文件。由于删除文件和文件内容并没有太大关系,所以,我们没必要去用一个 gulp 插件。最好的一个选择就是使用一个原生的 node 模块。

因为 del 模块支持多个文件以及 globbing,因此,在这个例子中,我们将使用它来删除文件:

$ npm install --save-dev gulp del

  

假想有如下的文件结构:

.
├── dist
│   ├── report.csv
│   ├── desktop
│   └── mobile
│       ├── app.js
│       ├── deploy.json
│       └── index.html
└── src

  

在 gulpfile 中,我们希望在运行我们的编译任务之前,将 mobile 文件的内容先清理掉:

var gulp = require('gulp');
var del = require('del');

gulp.task('clean:mobile', function (cb) {
  del([
    'dist/report.csv',
    // 这里我们使用一个通配模式来匹配 `mobile` 文件夹中的所有东西
    'dist/mobile/**/*',
    // 我们不希望删掉这个文件,所以我们取反这个匹配模式
    '!dist/mobile/deploy.json'
  ], cb);
});

gulp.task('default', ['clean:mobile']);

  

在管道中删除文件

你可能需要在管道中将一些处理过的文件删除掉。

我们使用 vinyl-paths 模块来简单地获取 stream 中每个文件的路径,然后传给 del 方法。

$ npm install --save-dev gulp del vinyl-paths

 

假想有如下的文件结构:

.
├── tmp
│   ├── rainbow.js
│   └── unicorn.js
└── dist

  

var gulp = require('gulp');
var stripDebug = require('gulp-strip-debug'); // 仅用于本例做演示
var del = require('del');
var vinylPaths = require('vinyl-paths');

gulp.task('clean:tmp', function () {
  return gulp.src('tmp/*')
    .pipe(stripDebug())
    .pipe(gulp.dest('dist'))
    .pipe(vinylPaths(del));
});

gulp.task('default', ['clean:tmp']);

只有在已经使用了其他的插件之后才需要这样做,否则,请直接使用 gulp.src 来代替。

 

 

使用 watchify 加速 browserify 编译

当一个 browserify 项目开始变大的时候,编译打包的时间也会慢慢变得长起来。虽然开始的时候可能只需花 1 秒,然后当你的项目需要建立在一些流行的大型项目的基础上时,它很有可能就变成 30 秒了。

这就是为什么 substack 写了 watchify 的原因,一个持续监视文件的改动,并且 只重新打包必要的文件 的 browserify 打包工具。用这种方法,第一次打包的时候可能会还是会花 30 秒,但是后续的编译打包工作将一直保持在 100 毫秒以下 —— 这是一个极大的提升。

watchify 并没有一个相应的 gulp 插件,并且也不需要有:你可以使用 vinyl-source-stream 来把你的用于打包的 stream 连接到 gulp 管道中。

'use strict';

var watchify = require('watchify');
var browserify = require('browserify');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var buffer = require('vinyl-buffer');
var gutil = require('gulp-util');
var sourcemaps = require('gulp-sourcemaps');
var assign = require('lodash.assign');

// 在这里添加自定义 browserify 选项
var customOpts = {
  entries: ['./src/index.js'],
  debug: true
};
var opts = assign({}, watchify.args, customOpts);
var b = watchify(browserify(opts));

// 在这里加入变换操作
// 比如: b.transform(coffeeify);

gulp.task('js', bundle); // 这样你就可以运行 `gulp js` 来编译文件了
b.on('update', bundle); // 当任何依赖发生改变的时候,运行打包工具
b.on('log', gutil.log); // 输出编译日志到终端

function bundle() {
  return b.bundle()
    // 如果有错误发生,记录这些错误
    .on('error', gutil.log.bind(gutil, 'Browserify Error'))
    .pipe(source('bundle.js'))
    // 可选项,如果你不需要缓存文件内容,就删除
    .pipe(buffer())
    // 可选项,如果你不需要 sourcemaps,就删除
    .pipe(sourcemaps.init({loadMaps: true})) // 从 browserify 文件载入 map
       // 在这里将变换操作加入管道
    .pipe(sourcemaps.write('./')) // 写入 .map 文件
    .pipe(gulp.dest('./dist'));
}

  

增量编译打包,包括处理整所涉及的所有文件

在做增量编译打包的时候,有一个比较麻烦的事情,那就是你常常希望操作的是 所有 处理过的文件,而不仅仅是单个的文件。举个例子,你想要只对更改的文件做代码 lint 操作,以及一些模块封装的操作,然后将他们与其他已经 lint 过的,以及已经进行过模块封装的文件合并到一起。如果不用到临时文件的话,这将会非常困难。

使用 gulp-cached 以及 gulp-remember 来解决这个问题。

var gulp = require('gulp');
var header = require('gulp-header');
var footer = require('gulp-footer');
var concat = require('gulp-concat');
var jshint = require('gulp-jshint');
var cached = require('gulp-cached');
var remember = require('gulp-remember');

var scriptsGlob = 'src/**/*.js';

gulp.task('scripts', function() {
  return gulp.src(scriptsGlob)
      .pipe(cached('scripts'))        // 只传递更改过的文件
      .pipe(jshint())                 // 对这些更改过的文件做一些特殊的处理...
      .pipe(header('(function () {')) // 比如 jshinting ^^^
      .pipe(footer('})();'))          // 增加一些类似模块封装的东西
      .pipe(remember('scripts'))      // 把所有的文件放回 stream
      .pipe(concat('app.js'))         // 做一些需要所有文件的操作
      .pipe(gulp.dest('public/'));
});

gulp.task('watch', function () {
  var watcher = gulp.watch(scriptsGlob, ['scripts']); // 监视与 scripts 任务中同样的文件
  watcher.on('change', function (event) {
    if (event.type === 'deleted') {                   // 如果一个文件被删除了,则将其忘记
      delete cached.caches.scripts[event.path];       // gulp-cached 的删除 api
      remember.forget('scripts', event.path);         // gulp-remember 的删除 api
    }
  });
});

  

将 buffer 变为 stream (内存中的内容)

有时候,你会需要这样一个 stream,它们的内容保存在一个变量中,而不是在一个实际的文件中。换言之,怎么不使用 gulp.src() 而创建一个 'gulp' stream。

我们来举一个例子,我们拥有一个包含 js 库文件的目录,以及一个包含一些模块的不同版本文件的目录。编译的目标是为每个版本创建一个 js 文件,其中包含所有库文件以及相应版本的模块文件拼接后的结果。

逻辑上我们将把这个拆分为如下步骤:

  • 载入库文件
  • 拼接库文件的内容
  • 载入不同版本的文件
  • 对于每个版本的文件,将其和库文件的内容拼接
  • 对于每个版本的文件,将结果输出到一个文件

想象如下的文件结构:

├── libs
│   ├── lib1.js
│   └── lib2.js
└── versions
    ├── version.1.js
    └── version.2.js

  

你应该要得到这样的结果:

└── output
    ├── version.1.complete.js # lib1.js + lib2.js + version.1.js
    └── version.2.complete.js # lib1.js + lib2.js + version.2.js

  

一个简单的模块化处理方式将会像下面这样:

var gulp = require('gulp');
var runSequence = require('run-sequence');
var source = require('vinyl-source-stream');
var vinylBuffer = require('vinyl-buffer');
var tap = require('gulp-tap');
var concat = require('gulp-concat');
var size = require('gulp-size');
var path = require('path');
var es = require('event-stream');

var memory = {}; // 我们会将 assets 保存到内存中

// 载入内存中文件内容的任务
gulp.task('load-lib-files', function() {
  // 从磁盘中读取库文件
  return gulp.src('src/libs/*.js')
  // 将所有库文件拼接到一起
  .pipe(concat('libs.concat.js'))
  // 接入 stream 来获取每个文件的数据
  .pipe(tap(function(file) {
    // 保存文件的内容到内存
    memory[path.basename(file.path)] = file.contents.toString();
  }));
});

gulp.task('load-versions', function() {
  memory.versions = {};
  // 从磁盘中读取文件
  return gulp.src('src/versions/version.*.js')
  // 接入 stream 来获取每个文件的数据
  .pipe( tap(function(file) {
    // 在 assets 中保存文件的内容
    memory.versions[path.basename(file.path)] = file.contents.toString();
  }));
});

gulp.task('write-versions', function() {
  // 我们将不容版本的文件的名字保存到一个数组中
  var availableVersions = Object.keys(memory.versions);
  // 我们创建一个数组来保存所有的 stream 的 promise
  var streams = [];

  availableVersions.forEach(function(v) {
    // 以一个假文件名创建一个新的 stream
    var stream = source('final.' + v);
    // 从拼接后的文件中读取数据
    var fileContents = memory['libs.concat.js'] +
      // 增加版本文件的数据
      '\n' + memory.versions[v];

    streams.push(stream);

    // 将文件的内容写入 stream
    stream.write(fileContents);

    process.nextTick(function() {
      // 在下一次处理循环中结束 stream
      stream.end();
    });

    stream
    // 转换原始数据到 stream 中去,到一个 vinyl 对象/文件
    .pipe(vinylBuffer())
    //.pipe(tap(function(file) { /* 这里可以做一些对文件内容的处理操作 */ }))
    .pipe(gulp.dest('output'));
  });

  return es.merge.apply(this, streams);
});

//============================================ 我们的主任务
gulp.task('default', function(taskDone) {
  runSequence(
    ['load-lib-files', 'load-versions'],  // 并行载入文件
    'write-versions',  // 一旦所有资源进入内存便可以做写入操作了
    taskDone           // 完成
  );
});

//============================================ 我们的监控任务
// 只在运行完 'default' 任务后运行,
// 这样所有的资源都已经在内存中了
gulp.task('watch', ['default'], function() {
  gulp.watch('./src/libs/*.js', function() {
    runSequence(
      'load-lib-files',  // 我们只需要载入更改过的文件
      'write-versions'
    );
  });

  gulp.watch('./src/versions/*.js', function() {
    runSequence(
      'load-versions',  // 我们只需要载入更改过的文件
      'write-versions'
    );
  });
});

在 gulp 中运行 Mocha 测试

运行所有的测试用例

// npm install gulp gulp-mocha

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

gulp.task('default', function() {
  return gulp.src(['test/test-*.js'], { read: false })
    .pipe(mocha({
      reporter: 'spec',
      globals: {
        should: require('should')
      }
    }));
});

  

在文件改动时候运行 mocha 测试用例

// npm install gulp gulp-mocha gulp-util

var gulp = require('gulp');
var mocha = require('gulp-mocha');
var gutil = require('gulp-util');

gulp.task('mocha', function() {
    return gulp.src(['test/*.js'], { read: false })
        .pipe(mocha({ reporter: 'list' }))
        .on('error', gutil.log);
});

gulp.task('watch-mocha', function() {
    gulp.watch(['lib/**', 'test/**'], ['mocha']);
});

  

  

  


 

posted @ 2016-11-24 16:12  ァ颏餶こ铭訫の  阅读(177)  评论(0编辑  收藏  举报