Skip to content

Commit

Permalink
Merge pull request #123 from floatdrop/chokidar
Browse files Browse the repository at this point in the history
Migrate to chokidar + anymatch
  • Loading branch information
floatdrop committed Jan 21, 2015
2 parents 2df9956 + c8734e7 commit fb972bd
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 167 deletions.
20 changes: 0 additions & 20 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,22 +1,2 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
.tags
.tags_sorted_by_file
.gemtags
.DS_Store
coverage

pids
logs
results
temp

npm-debug.log
node_modules
test/fixtures/new.js
7 changes: 0 additions & 7 deletions .npmignore

This file was deleted.

54 changes: 24 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@

This is an __reimplementation__ of bundled [`gulp.watch`](https://github.com/gulpjs/gulp/blob/master/docs/API.md#gulpwatchglob—opts-cb) with an endless-stream approach. If `gulp.watch` is working for you, stick with it; otherwise, you can try this `gulp-watch` plugin.

The main reason for `gulp-watch`'s existence is that it can easily achieve per-file rebuilding on file change:

![Awesome demonstration](https://github.com/floatdrop/gulp-watch/raw/master/img/2014-01-09.gif)

## Installation

Run `npm install gulp-watch`.
Expand Down Expand Up @@ -47,9 +43,21 @@ All incoming files that piped in will be grouped and passed to `events` stream a

* `vinyl` — is [vinyl](https://github.com/wearefractal/vinyl) object that corresponds to file that caused event. Additional `event` field is added to determine, what caused changes.

Possible events:

* `add` - file was added to watch or created
* `change` - file was changed
* `unlink` - file was deleted

#### Options

This object is passed to [`gaze` options](https://github.com/shama/gaze#properties) directly (refer to [`gaze` documentation](https://github.com/shama/gaze)). Options for [`gulp.src`](https://github.com/gulpjs/gulp#gulpsrcglobs-options) are used. If you do not want content from `watch`, then add `read: false` to the `options` object.
This object is passed to [`chokidar` options](https://github.com/paulmillr/chokidar#api) directly. Options for [`gulp.src`](https://github.com/gulpjs/gulp#gulpsrcglobs-options) are also available. If you do not want content from `watch`, then add `read: false` to the `options` object.

#### options.events
Type: `Array`
Default: `['add', 'change', 'unlink']`

List of events, that should be watched by gulp-watch. Contains [event names from chokidar](https://github.com/paulmillr/chokidar#events).

#### options.base
Type: `String`
Expand All @@ -64,42 +72,28 @@ Default: `undefined`
Name of the watcher. If it present in options, you will get more readable output.

#### options.verbose
Type: `Boolean` / `undefined`
Default: `undefined`
Type: `Boolean`
Default: `false`

This options will enable more verbose output (on `true`) or disable it completly (on `false`).
This options will enable verbose output.

### Methods

Returned `Stream` from constructor have some useful methods:

* `close()` — calling `gaze.close` and emitting `end`, after `gaze.close` is done.

Also it has `_gaze` property to access Gaze instance.
* `add(path / paths)`
* `unwatch(path / paths)`
* `close()`

### Events

* `end` — all files are stop being watched.
* `ready` — just re-emitted event from `gaze`.
* `error` — when something happened inside callback, you will get notified.

### 3.0.0 Changes

[gulp-batch](https://github.com/floatdrop/gulp-batch) was removed, so `callback` is changed. Now it passes just [vinyl](https://github.com/wearefractal/vinyl) object to you. Also `callback` now not catching errors inside and reemits them to stream.

### 2.0.0 Changes

Before `2.0.0` version there was a bug in `gulp-batch` - it does not prevent tasks to execute in same time. In `2.0.0` version of `gulp-batch` was bumped.

This can cause your watch tasks to hang, if you do not calling `done` in callback or returning `Stream`/`Promise` from it.

### Migration to 1.0.0
* `end`
* `ready`
* `error`

* __watch is not emmiting files at start__ - read «[Starting tasks on events](/docs/readme.md#starting-tasks-on-events)» and «[Incremental build](https://github.com/floatdrop/gulp-watch/tree/master/docs#incremental-build)» for workarounds.
* __watch is now pass through stream__ - which means that streaming files into watch will not add them to gaze. It is very hard to maintain, because watch is not aware about `glob`, from which this files come from and can not re-create vinyl object properly without maintaining cache of the `base` properties of incoming files (yuck).
* __array of tasks is not accepted as callback__ - this was not working anyway, but rationale behind it - requiring gulp and calling internal method start is bad. This feature will become more clear, when gulp 4.0.0 will be released with new task system. Read «[Starting tasks on events](/docs/readme.md#starting-tasks-on-events)» for right way to do it.
### [Changelog](https://github.com/floatdrop/gulp-watch/releases)

# License
## License

MIT (c) 2014 Vsevolod Strukchinsky (floatdrop@gmail.com)

Expand Down
Binary file removed img/2014-01-09.gif
Binary file not shown.
86 changes: 49 additions & 37 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
'use strict';

function nop() {}

var util = require('gulp-util'),
PluginError = require('gulp-util').PluginError,
chokidar = require('chokidar'),
Duplex = require('readable-stream').Duplex,
vinyl = require('vinyl-file'),
File = require('vinyl'),
glob2base = require('glob2base'),
path2glob = require('path2glob'),
sep = require('path').sep;

function isDirectory(path) {
return path[path.length - 1] === sep;
}
anymatch = require('anymatch'),
Glob = require('glob').Glob;

module.exports = function (globs, opts, cb) {
if (!globs) throw new PluginError('gulp-watch', 'glob argument required');

if (typeof globs === 'string') globs = [ globs ];
if (typeof globs === 'string') globs = [globs];

if (!Array.isArray(globs)) {
throw new PluginError('gulp-watch', 'glob should be String or Array, not ' + (typeof globs));
Expand All @@ -29,12 +23,13 @@ module.exports = function (globs, opts, cb) {
opts = {};
}

if (!opts) opts = {};
opts = opts || {};
cb = cb || function () {};

var baseForced = !!opts.base;
var outputStream = new Duplex({ objectMode: true, allowHalfOpen: true });
opts.events = ['add', 'change', 'unlink'];

cb = cb || nop;
var baseForced = !!opts.base;
var outputStream = new Duplex({objectMode: true, allowHalfOpen: true});

outputStream._write = function _write(file, enc, done) {
cb(file);
Expand All @@ -44,46 +39,63 @@ module.exports = function (globs, opts, cb) {

outputStream._read = function _read() { };

var Gaze = require('gaze');
var gaze = outputStream._gaze = new Gaze(globs, opts);

gaze.on('all', processEvent);
var watcher = chokidar.watch(globs, opts)
.on('all', processEvent)
.on('error', outputStream.emit.bind(outputStream, 'error'))
.on('ready', outputStream.emit.bind(outputStream, 'ready'));

function write(event, err, file) {
if (err) { return outputStream.emit('error', err); }
if (opts.verbose !== false) { log(event, file); }
file.event = event;
outputStream.push(file);
cb(file);
}
outputStream.add = watcher.add.bind(watcher);
outputStream.unwatch = watcher.unwatch.bind(watcher);
outputStream.close = function () {
watcher.close();
outputStream.emit('end');
};

function processEvent(event, filepath) {
var glob = path2glob(filepath, globs, opts);

if (!glob && opts.verbose !== false) {
log('not matched by globs', { relative: filepath });
}
var glob = globs[anymatch(globs, filepath, true)];

opts.path = filepath;

if (!baseForced) opts.base = glob ? glob2base(glob) : undefined;
if (!baseForced) {
opts.base = glob2base(new Glob(glob, opts));
}

if (event === 'deleted' || isDirectory(filepath)) {
// React only on opts.events
if (opts.events.indexOf(event) === -1) {
return;
}

// Do not stat deleted files
if (event === 'unlink') {
return write(event, null, new File(opts));
}

vinyl.read(filepath, opts, write.bind(null, event));
}

gaze.on('error', outputStream.emit.bind(outputStream, 'error'));
gaze.on('ready', outputStream.emit.bind(outputStream, 'ready'));
gaze.on('end', outputStream.emit.bind(outputStream, 'end'));
function write(event, err, file) {
if (err) {
return outputStream.emit('error', err);
}

outputStream.close = function () { gaze.close(); };
if (opts.verbose) {
log(event, file);
}

file.event = event;
outputStream.push(file);
cb(file);
}

function log(event, file) {
event = event[event.length - 1] === 'e' ? event + 'd' : event + 'ed';

var msg = [util.colors.magenta(file.relative), 'was', event];
if (opts.name) { msg.unshift(util.colors.cyan(opts.name) + ' saw'); }

if (opts.name) {
msg.unshift(util.colors.cyan(opts.name) + ' saw');
}

util.log.apply(util, msg);
}

Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
"gulpplugin"
],
"scripts": {
"test": "mocha -R spec -t 5000",
"test": "mocha -R spec",
"coverage": "istanbul cover node_modules/.bin/_mocha --report html -- -R spec",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
},
"files": [
"index.js"
],
"repository": {
"type": "git",
"url": "git://github.com/floatdrop/gulp-watch.git"
Expand All @@ -36,10 +39,11 @@
"touch": "0.0.3"
},
"dependencies": {
"gaze": "0.5.x",
"anymatch": "^1.1.0",
"chokidar": "^1.0.0-rc2",
"glob": "^4.3.5",
"glob2base": "~0.0.11",
"gulp-util": "~3.0.0",
"path2glob": "0.0.2",
"readable-stream": "^1.0.31",
"vinyl": "^0.4.3",
"vinyl-file": "~1.1.0"
Expand Down
33 changes: 30 additions & 3 deletions test/callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

var watch = require('..');
var join = require('path').join;
var fs = require('fs');
var rimraf = require('rimraf');
var touch = require('./touch.js');
require('should');

Expand All @@ -13,16 +15,41 @@ describe('callback', function () {
var w;

afterEach(function (done) {
rimraf.sync(fixtures('newDir'));
w.on('end', done);
w.close();
});

it('should be called on event', function (done) {
it('should be called on add event', function (done) {
w = watch(fixtures('*.js'), function (file) {
file.relative.should.eql('index.js');
file.event.should.eql('changed');
file.event.should.eql('add');
done();
});
w.on('ready', touch(fixtures('index.js')));
});

it('should be called on add event', function (done) {
w = watch(fixtures('*.js'), function (file) {
if (file.event === 'add') {
touch(fixtures('index.js'))();
}

if (file.event === 'change') {
file.relative.should.eql('index.js');
done();
}
});
});

it('should be called on add event in new directory', function (done) {
rimraf.sync(fixtures('newDir'));

w = watch(fixtures('**/*.ts'), function (file) {
file.relative.should.eql('newDir/index.ts');
done();
}).on('ready', function () {
fs.mkdirSync(fixtures('newDir'));
touch(fixtures('newDir/index.ts'))();
});
});
});
31 changes: 0 additions & 31 deletions test/directory.js

This file was deleted.

31 changes: 31 additions & 0 deletions test/ignore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* global describe, it, afterEach */

var watch = require('..');
var join = require('path').join;
var rimraf = require('rimraf');
var touch = require('./touch.js');
var fs = require('fs');
require('should');

function fixtures(glob) {
return join(__dirname, 'fixtures', glob);
}

describe('ignore', function () {
var w;

it('should ignore non-existent folders', function (done) {
rimraf.sync(fixtures('temp'));

w = watch([fixtures('**/*.ts'), '!' + fixtures('temp')], function () {
done('Ignored folder was watched');
});

w.on('ready', function () {
fs.mkdirSync(fixtures('temp'));
touch(fixtures('temp/index.ts'))();
rimraf.sync(fixtures('temp'));
setTimeout(done, 200);
});
});
});

0 comments on commit fb972bd

Please sign in to comment.