kotatsu
Version:
Straightforward command line tool to setup a development environment for modern JavaScript.
211 lines (167 loc) • 4.63 kB
JavaScript
/**
* Kotatsu Start Command
* ======================
*
* Function starting a long-running node.js script, watching for its
* changes and applying HMR updates to it.
*/
var defaults = require('./defaults.js'),
createCompiler = require('./createCompiler.js'),
createLogger = require('./createLogger.js'),
solveOutput = require('./solveOutput.js'),
pretty = require('pretty-ms'),
path = require('path'),
fork = require('child_process').fork,
rmrf = require('rimraf'),
_ = require('lodash');
/**
* Helpers.
*/
function message(data) {
return _.extend({__hmrUpdate: true}, data);
}
/**
* Start function.
*/
module.exports = function start(command, opts) {
opts = _.merge({}, defaults, opts);
opts.side = 'back';
opts.hot = command === 'start';
opts.solvedOutput = solveOutput(opts);
var logger = createLogger(opts.quiet);
logger.announce();
// State
var running = false,
child;
// Creating the webpack compiler
var compiler = createCompiler(opts);
// Creating a cleanup function
var dirty = true;
var cleanup = function() {
if (dirty) {
rmrf.sync(opts.solvedOutput.path);
dirty = false;
}
};
// Hooking into the compiler
compiler.hooks.compile.tap('kotatsu', function() {
if (running) {
logger.info('Bundle rebuilding...');
if (command === 'monitor') {
child.kill();
child = null;
running = false;
}
}
});
compiler.hooks.watchRun.tap('kotatsu', function(compilation) {
if (
!running ||
!compilation.modifiedFiles ||
!compilation.modifiedFiles.size
)
return;
var modifiedFiles = Array.from(compilation.modifiedFiles)
.filter(function(f) {
return !!path.extname(f);
});
if (!modifiedFiles.length)
return;
console.log('');
logger.info('Modified files:');
modifiedFiles.forEach(function(f) {
f = path.relative(process.cwd(), f);
logger.info(' ./' + f);
});
});
if (!opts.progress)
logger.info('Compiling...');
// Forging the action
var run = function(callback) {
if (command === 'run')
return compiler.run(callback);
return compiler.watch({aggregateTimeout: 200}, callback);
};
// Starting to watch
var handle = run(function(err, stats) {
if (err) throw err;
// Compiling stats to JSON
stats = stats.toJson();
// Errors & warnings?
// var errors = stats.errors || [],
// warnings = stats.warnings || [];
// if (errors.length) {
// errors.forEach(function(error) {
// logger.error(error);
// });
// return;
// }
// if (warnings.length) {
// warnings.forEach(function(warning) {
// logger.warn(warning);
// });
// }
// Running the script
if (!running) {
// Announcing we are done!
logger.success('Done!');
if (command === 'start')
logger.info('Starting your script...');
else
logger.info('Running your script...');
var scriptPath = path.join(opts.solvedOutput.path, opts.solvedOutput.filename),
childOptions = {};
if (process.getuid) {
childOptions.uid = process.getuid();
childOptions.gid = process.getgid();
}
child = fork(scriptPath, opts.args || [], childOptions);
// Listening to child's log
child.on('message', function(log) {
logger.log(log.level, log.message);
});
// Listening to child's exit
child.on('exit', function(code) {
// If we just ran the script, we stop right here
if (command === 'run')
process.exit(0);
// Waiting for changes to reload
if (code)
logger.error('The script crashed. Waiting for changes to reload...');
else
logger.info('The script ended. Waiting for changes to reload...');
running = false;
child = null;
});
running = true;
}
else {
logger.info('Built in ' + pretty(stats.time) + '.');
// Building module map
var map = {};
stats.modules.forEach(function(m) {
map[m.id] = m.name;
});
// Notify the child
if (opts.hot)
child.send(message({
hash: stats.hash,
type: 'update',
modules: map
}));
}
});
// Cleaning up on exit
process.on('SIGINT', function() {
if (child)
child.kill();
cleanup();
process.exit();
});
process.on('exit', function() {
if (child)
child.kill();
cleanup();
});
return handle;
};