Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement .observe() #44

Merged
merged 3 commits into from
Apr 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 0.8.0


## 0.7.14

* Cached transforms can be reused regardless of sourcemaps ([#46](https://github.com/gobblejs/gobble/issues/46))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "gobble",
"description": "The last build tool you'll ever need",
"version": "0.7.14",
"version": "0.8.0-edge",
"author": "Rich Harris",
"license": "MIT",
"repository": "https://github.com/gobblejs/gobble",
Expand Down
55 changes: 53 additions & 2 deletions src/nodes/Node.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { EventEmitter2 } from 'eventemitter2';
import { rimraf } from 'sander';
import * as crc32 from 'buffer-crc32';
import { lsrSync, readFileSync, rimraf } from 'sander';
import { join, resolve } from 'path';
import * as requireRelative from 'require-relative';
import { grab, include, map as mapTransform, move } from '../builtins';
import { Transformer } from './index';
import { Observer, Transformer } from './index';
import config from '../config';
import GobbleError from '../utils/GobbleError';
import assign from '../utils/assign';
import warnOnce from '../utils/warnOnce';
import compareBuffers from '../utils/compareBuffers';
import serve from './serve';
import build from './build';
import watch from './watch';
Expand Down Expand Up @@ -108,6 +110,39 @@ export default class Node extends EventEmitter2 {
return new Transformer( this, include, { patterns, exclude: true });
}

getChanges ( inputdir ) {
const files = lsrSync( inputdir );

if ( !this._files ) {
this._files = files;
this._checksums = {};

files.forEach( file => {
this._checksums[ file ] = crc32( readFileSync( inputdir, file ) );
});

return files.map( file => ({ file, added: true }) );
}

const added = files.filter( file => !~this._files.indexOf( file ) ).map( file => ({ file, added: true }) );
const removed = this._files.filter( file => !~files.indexOf( file ) ).map( file => ({ file, removed: true }) );

const maybeChanged = files.filter( file => ~this._files.indexOf( file ) );

let changed = [];

maybeChanged.forEach( file => {
let checksum = crc32( readFileSync( inputdir, file ) );

if ( !compareBuffers( checksum, this._checksums[ file ] ) ) {
changed.push({ file, changed: true });
this._checksums[ file ] = checksum;
}
});

return added.concat( removed ).concat( changed );
}

grab () {
const src = join.apply( null, arguments );
return new Transformer( this, grab, { src });
Expand Down Expand Up @@ -140,6 +175,18 @@ export default class Node extends EventEmitter2 {
return new Transformer( this, move, { dest });
}

observe ( fn, userOptions ) {
if ( typeof fn === 'string' ) {
fn = tryToLoad( fn );
}

return new Observer( this, fn, userOptions );
}

observeIf ( condition, fn, userOptions ) {
return condition ? this.observe( fn, userOptions ) : this;
}

serve ( options ) {
return serve( this, options );
}
Expand Down Expand Up @@ -168,6 +215,10 @@ export default class Node extends EventEmitter2 {
return new Transformer( this, fn, userOptions );
}

transformIf ( condition, fn, userOptions ) {
return condition ? this.transform( fn, userOptions ) : this;
}

watch ( options ) {
return watch( this, options );
}
Expand Down
138 changes: 138 additions & 0 deletions src/nodes/Observer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import * as sander from 'sander';
import Node from './Node';
import queue from '../queue';
import GobbleError from '../utils/GobbleError';
import assign from '../utils/assign';
import uid from '../utils/uid';
import makeLog from '../utils/makeLog';
import config from '../config';
import extractLocationInfo from '../utils/extractLocationInfo';
import { ABORTED } from '../utils/signals';

export default class Observer extends Node {
constructor ( input, fn, options, id ) {
super();

this.input = input;

this.fn = fn;
this.options = assign( {}, options );

this.name = id || fn.id || fn.name || 'unknown';
this.id = uid( this.name );
}

ready () {
let observation;

if ( !this._ready ) {
observation = {
node: this,
log: makeLog( this ),
env: config.env,
sander: sander
};

this._abort = () => {
this._ready = null;
observation.aborted = true;
};

this._ready = this.input.ready().then( inputdir => {
return queue.add( ( fulfil, reject ) => {
this.emit( 'info', {
code: 'TRANSFORM_START', // TODO
progressIndicator: true,
id: this.id
});

const start = Date.now();
let called = false;

const callback = err => {
if ( called ) return;
called = true;

if ( observation.aborted ) {
reject( ABORTED );
}

else if ( err ) {
let stack = err.stack || new Error().stack;
let { file, line, column } = extractLocationInfo( err );

let gobbleError = new GobbleError({
inputdir,
stack, file, line, column,
message: 'observation failed',
id: this.id,
code: 'TRANSFORMATION_FAILED', // TODO
original: err
});

reject( gobbleError );
}

else {
this.emit( 'info', {
code: 'TRANSFORM_COMPLETE', // TODO
id: this.id,
duration: Date.now() - start
});

fulfil( inputdir );
}
};

try {
observation.changes = this.input.changes || this.getChanges( inputdir );

const promise = this.fn.call( observation, inputdir, assign({}, this.options ), callback );
const promiseIsPromise = promise && typeof promise.then === 'function';

if ( !promiseIsPromise && this.fn.length < 3 ) {
throw new Error( `Observer ${this.id} did not return a promise and did not accept callback` );
}

if ( promiseIsPromise ) {
promise.then( () => callback(), callback );
}
} catch ( err ) {
callback( err );
}
});
});
}

return this._ready;
}

start () {
if ( this._active ) {
return;
}

this._active = true;

// Propagate invalidation events and information
this._oninvalidate = changes => {
this._abort();
this.emit( 'invalidate', changes );
};

this._oninfo = details => this.emit( 'info', details );

this.input.on( 'invalidate', this._oninvalidate );
this.input.on( 'info', this._oninfo );

return this.input.start();
}

stop () {
this.input.off( 'invalidate', this._oninvalidate );
this.input.off( 'info', this._oninfo );

this.input.stop();
this._active = false;
}
}
34 changes: 0 additions & 34 deletions src/nodes/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import makeLog from '../utils/makeLog';
import config from '../config';
import warnOnce from '../utils/warnOnce';
import extractLocationInfo from '../utils/extractLocationInfo';
import compareBuffers from '../utils/compareBuffers';
import { ABORTED } from '../utils/signals';

export default class Transformer extends Node {
Expand Down Expand Up @@ -153,39 +152,6 @@ export default class Transformer extends Node {
this._active = false;
}

getChanges ( inputdir ) {
const files = lsrSync( inputdir );

if ( !this._files ) {
this._files = files;
this._checksums = {};

files.forEach( file => {
this._checksums[ file ] = crc32( readFileSync( inputdir, file ) );
});

return files.map( file => ({ file, added: true }) );
}

const added = files.filter( file => !~this._files.indexOf( file ) ).map( file => ({ file, added: true }) );
const removed = this._files.filter( file => !~files.indexOf( file ) ).map( file => ({ file, removed: true }) );

const maybeChanged = files.filter( file => ~this._files.indexOf( file ) );

let changed = [];

maybeChanged.forEach( file => {
let checksum = crc32( readFileSync( inputdir, file ) );

if ( !compareBuffers( checksum, this._checksums[ file ] ) ) {
changed.push({ file, changed: true });
this._checksums[ file ] = checksum;
}
});

return added.concat( removed ).concat( changed );
}

_cleanup ( latest ) {
const dir = join( session.config.gobbledir, this.id );

Expand Down
76 changes: 42 additions & 34 deletions src/nodes/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,30 @@ export default function ( node, options ) {
let promise;
let previousDetails;

// that does double duty as a promise
task.then = function () {
return promise.then.apply( promise, arguments );
};
function build () {
task.emit( 'info', {
code: 'BUILD_START'
});

task.catch = function () {
return promise.catch.apply( promise, arguments );
};
node.on( 'info', details => {
if ( details === previousDetails ) return;
previousDetails = details;
task.emit( 'info', details );
});

node.start(); // TODO this starts a file watcher! need to start without watching

return node.ready().then(
inputdir => {
node.stop();
return copydir( inputdir ).to( dest );
},
err => {
node.stop();
throw err;
}
);
}

promise = cleanup( gobbledir )
.then( () => {
Expand All @@ -43,34 +59,26 @@ export default function ( node, options ) {
return cleanup( dest ).then( build );
}, build );
})
.then( () => {
task.emit( 'complete' );
session.destroy();
})
.catch( err => {
task.emit( 'error', err );
session.destroy();
throw err;
});
.then(
() => {
task.emit( 'complete' );
session.destroy();
},
err => {
session.destroy();
task.emit( 'error', err );
throw err;
}
);

return task;

function build () {
task.emit( 'info', {
code: 'BUILD_START'
});

node.on( 'info', details => {
if ( details === previousDetails ) return;
previousDetails = details;
task.emit( 'info', details );
});
// that does double duty as a promise
task.then = function () {
return promise.then.apply( promise, arguments );
};

node.start(); // TODO this starts a file watcher! need to start without watching
task.catch = function () {
return promise.catch.apply( promise, arguments );
};

return node.ready().then( inputdir => {
node.stop();
return copydir( inputdir ).to( dest );
});
}
return task;
}
3 changes: 2 additions & 1 deletion src/nodes/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Source from './Source';
import Merger from './Merger';
import Observer from './Observer';
import Transformer from './Transformer';

export { Source, Merger, Transformer };
export { Source, Merger, Observer, Transformer };
Loading