happypack
Version:
webpack speed booster, makes you happy!
177 lines (146 loc) • 4.87 kB
JavaScript
var path = require('path');
var fork = require('child_process').fork;
var assert = require('assert');
var Once = require('./fnOnce');
var WORKER_BIN = path.resolve(__dirname, 'HappyWorkerChannel.js');
var BufferedFd = require('./BufferedFd')
var UnbufferedFd = require('./UnbufferedFd')
/**
* @param {String|Number} id
* @param {Object?} config
* @param {Boolean} [config.verbose=false]
* @param {Boolean} [config.debug=false]
* @param {Boolean} [config.buffered=false]
*/
function HappyThread(id, happyRPCHandler, _config) {
var fd, bfd;
var config = _config || {};
var BufferedFdImpl = config.buffered ? BufferedFd : UnbufferedFd;
var callbacks = {};
var generateMessageId = (function() {
var counter = 0;
return function() {
var nextId = ++counter;
return 'Thread::' + id.toString() + ':' + nextId.toString();
}
}());
var logSendError = function(err) {
if (err) {
console.error('HappyThread[%s]: unable to send to worker!', id, err)
}
}
var send = function(message) {
BufferedFdImpl.send(bfd, message, logSendError)
}
return {
open: function(onReady) {
var emitReady = Once(onReady);
fd = fork(WORKER_BIN, [id, JSON.stringify({ buffered: config.buffered })], {
// Do not pass through any arguments that were passed to the main
// process (webpack or node) because they could have unwanted
// side-effects, see issue #47
execArgv: [],
});
fd.on('error', throwError);
fd.on('exit', function(exitCode) {
if (exitCode !== 0) {
emitReady('HappyPack: worker exited abnormally with code ' + exitCode);
}
});
bfd = BufferedFdImpl.of(fd)
fd.on('message', function(message) {
if (message.name === 'READY') {
if (config.debug) {
console.info('HappyThread[%s] is now open.', id);
}
emitReady();
}
else if (message.name === 'CONFIGURE_DONE') {
assert(typeof callbacks[message.id] === 'function',
"HappyThread: expected plugin to be awaiting a configuration ACK."
);
callbacks[message.id]();
delete callbacks[message.id];
}
else if (message.name === 'COMPILED') {
if (config.debug) {
console.log('HappyThread[%s]: a file has been compiled. (request=%s)', id, message.id);
}
assert(typeof callbacks[message.id] === 'function',
"HappyThread: expected loader to be pending on source file but wasn't! (this is likely an internal error!)"
);
callbacks[message.id](message);
delete callbacks[message.id];
}
else if (message.name === 'COMPILER_REQUEST') {
if (config.debug) {
console.log('HappyThread[%s]: forwarding compiler request from worker to plugin:', id, message);
}
// TODO: DRY alert, see .createForegroundWorker() in HappyPlugin.js
happyRPCHandler.execute(message.data.type, message.data.payload, function(error, result) {
send({
id: message.id, // downstream id
name: 'COMPILER_RESPONSE',
data: {
compilerId: message.data.compilerId,
payload: {
error: error || null,
result: result || null
}
}
});
});
}
});
},
configure: function(compilerId, compilerOptions, done) {
var messageId = generateMessageId();
callbacks[messageId] = done;
send({
id: messageId,
name: 'CONFIGURE',
data: {
compilerId: compilerId,
compilerOptions: compilerOptions
}
});
},
/**
* @param {Object} params
* @param {String} params.compiledPath
* @param {Object} params.loaderContext
*
* @param {Function} done
*/
compile: function(params, done) {
var messageId = generateMessageId();
assert(params.loaderContext && typeof params.loaderContext === 'object');
assert(!!fd, "You must launch a compilation thread before attempting to use it!!!");
callbacks[messageId] = done;
if (config.debug) {
console.log('HappyThread[%s]: compiling "%s"... (request=%s)', id, params.loaderContext.resourcePath, messageId);
}
send({
id: messageId,
name: 'COMPILE',
data: params,
});
},
isOpen: function() {
return !!fd;
},
close: function() {
BufferedFdImpl.discard(bfd);
bfd = null;
fd.kill('SIGINT');
fd = null;
if (config.debug) {
console.info('HappyThread[%s] is now closed.', id);
}
},
};
}
module.exports = HappyThread;
function throwError(e) {
throw e;
}