Pascal Precht 71e92358a8
feat(modules/watcher): introduce watcher plugin module
As part of a bigger refactoring to make Embark's build pipeline pluggable,
this commit moves the watcher into its own plugin module so it can be
consumed via Embark's event bus.

It also introduces new command handlers for all watcher related APIs respectively:

- watcher:start
- watcher:stop
- watcher:restart
2018-10-22 19:35:58 +02:00

208 lines
6.3 KiB
JavaScript

let chokidar = require('chokidar');
let path = require('path');
let fs = require('../../core/fs.js');
const DAPP_PIPELINE_CONFIG_FILE = 'pipeline.js';
const DAPP_WEBPACK_CONFIG_FILE = 'webpack.config.js';
const DAPP_BABEL_LOADER_OVERRIDES_CONFIG_FILE = 'babel-loader-overrides.js';
// TODO: this should be receiving the config object not re-reading the
// embark.json file
class Watcher {
constructor(embark) {
this.logger = embark.logger;
this.events = embark.events;
this.fileWatchers = [];
this.events.setCommandHandler('watcher:start', () => this.start());
this.events.setCommandHandler('watcher:stop', () => this.stop());
this.events.setCommandHandler('watcher:restart', () => this.restart());
}
start() {
let self = this;
// TODO: should come from the config object instead of reading the file
// directly
let embarkConfig = fs.readJSONSync("embark.json");
this.watchAssets(embarkConfig, function () {
self.logger.trace('ready to watch asset changes');
});
this.watchContracts(embarkConfig, function () {
self.logger.trace('ready to watch contract changes');
});
this.watchContractConfig(embarkConfig, function () {
self.logger.trace('ready to watch contract config changes');
});
this.watchPipelineConfig(embarkConfig, function () {
self.logger.trace('ready to watch pipeline config changes');
});
this.watchWebserverConfig(embarkConfig, function () {
self.logger.trace('ready to watch webserver config changes');
});
this.logger.info(__("ready to watch file changes"));
}
restart() {
this.stop();
this.start();
}
stop() {
this.fileWatchers.forEach(fileWatcher => {
if (fileWatcher.shouldClose) return;
if (fileWatcher.isReady) fileWatcher.close();
fileWatcher.shouldClose = true;
});
}
watchAssets(embarkConfig, callback) {
let self = this;
let appConfig = embarkConfig.app;
let filesToWatch = [];
for (let targetFile in appConfig) {
let files = appConfig[targetFile];
let fileGlob = files;
// workaround for imports issue
// so embark reacts to changes made in imported js files
// chokidar glob patterns only work with front-slashes
if (!Array.isArray(files)) {
fileGlob = path.join(path.dirname(files), '**', '*.*').replace(/\\/g, '/');
} else if (files.length === 1) {
fileGlob = path.join(path.dirname(files[0]), '**', '*.*').replace(/\\/g, '/');
}
filesToWatch.push(fileGlob);
}
filesToWatch = Array.from(new Set(filesToWatch));
this.watchFiles(
filesToWatch,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'asset', path);
self.events.emit('file-event', 'asset', path);
},
function () {
callback();
}
);
}
watchContracts(embarkConfig, callback) {
let self = this;
this.watchFiles(
[embarkConfig.contracts],
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'contract', path);
self.events.emit('file-event', 'contract', path);
},
function () {
callback();
}
);
}
watchWebserverConfig(embarkConfig, callback) {
let self = this;
let webserverConfig;
if (typeof embarkConfig.config === 'object') {
if (!embarkConfig.config.webserver) {
return;
}
webserverConfig = embarkConfig.config.webserver;
} else {
let contractsFolder = embarkConfig.config.replace(/\\/g, '/');
if (contractsFolder.charAt(contractsFolder.length - 1) !== '/') {
contractsFolder += '/';
}
webserverConfig = [`${contractsFolder}**/webserver.json`, `${contractsFolder}**/webserver.js`];
}
this.watchFiles(webserverConfig,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('webserver:config:change', 'config', path);
},
function () {
callback();
}
);
}
watchContractConfig(embarkConfig, callback) {
let self = this;
let contractConfig;
if (typeof embarkConfig.config === 'object' || embarkConfig.config.contracts) {
contractConfig = embarkConfig.config.contracts;
} else {
let contractsFolder = embarkConfig.config.replace(/\\/g, '/');
if (contractsFolder.charAt(contractsFolder.length - 1) !== '/') {
contractsFolder += '/';
}
contractConfig = [`${contractsFolder}**/contracts.json`, `${contractsFolder}**/contracts.js`];
}
this.watchFiles(contractConfig,
function (eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.events.emit('file-' + eventName, 'config', path);
self.events.emit('file-event', 'config', path);
},
function () {
callback();
}
);
}
watchPipelineConfig(embarkConfig, callback) {
let filesToWatch = [
fs.dappPath('', DAPP_WEBPACK_CONFIG_FILE),
fs.dappPath('', DAPP_BABEL_LOADER_OVERRIDES_CONFIG_FILE)
];
if (typeof embarkConfig.config === 'object' && embarkConfig.config.pipeline) {
filesToWatch.push(embarkConfig.config.pipeline);
} else if (typeof embarkConfig.config === 'string') {
filesToWatch.push(fs.dappPath(embarkConfig.config, DAPP_PIPELINE_CONFIG_FILE));
}
this.watchFiles(filesToWatch, (eventName, path) => {
this.logger.info(`${eventName}: ${path}`);
this.events.emit('file-' + eventName, 'config', path);
this.events.emit('file-event', 'config', path);
}, callback);
}
watchFiles(files, changeCallback, doneCallback) {
this.logger.trace('watchFiles');
this.logger.trace(files);
let configWatcher = chokidar.watch(files, {
ignored: /[\/\\]\.|tmp_/, persistent: true, ignoreInitial: true, followSymlinks: true
});
this.fileWatchers.push(configWatcher);
configWatcher
.on('add', path => changeCallback('add', path))
.on('change', path => changeCallback('change', path))
.on('unlink', path => changeCallback('remove', path))
.once('ready', () => {
configWatcher.isReady = true;
if (configWatcher.shouldClose) configWatcher.close();
doneCallback();
});
}
}
module.exports = Watcher;