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']); });