ima-gulp-tasks
Version:
Default gulp tasks for IMA.js applications.
203 lines (172 loc) • 6.12 kB
JavaScript
const gulp = require('gulp');
const cache = require('gulp-cached');
const flo = require('fb-flo');
const color = require('ansi-colors');
const log = require('fancy-log');
const remember = require('gulp-remember');
const watch = require('gulp-watch');
const path = require('path');
const fs = require('fs');
const net = require('net');
const { promisify } = require('util');
const exec = promisify(require('child_process').exec);
const sharedState = require('../gulpState.js');
const dgram = require('dgram');
const notifyServer = dgram.createSocket('udp4');
let notifyServerMessageTimeout = null;
let notifyServerJobQueue = [];
exports.__requiresConfig = true;
exports.default = gulpConfig => {
const { files, occupiedPorts, notifyServer: notifyServerConfig } = gulpConfig;
function watchTask() {
let hotReloadedCacheKeys = [];
runOnChange(files.app.watch, 'app:build');
runOnChange(files.vendor.watch, 'vendor:build');
runOnChange(files.less.watch, 'less:build');
runOnChange(files.server.watch, 'server:build');
runOnChange(files.locale.watch, 'locale:build');
runOnChange('./app/assets/static/**/*', 'copy:appStatic');
if (notifyServerConfig.enable) {
notifyServer.bind({
address: notifyServerConfig.server,
port: notifyServerConfig.port,
exclusive: true
});
notifyServer.on('listening', () => {
log(
`Notification server listening on ${notifyServerConfig.server}:${
notifyServerConfig.port
} for messages [ ${Object.keys(notifyServerConfig.messageJobs)} ]`
);
});
notifyServer.on('message', message => {
const changedSubject = message.toString();
Object.keys(notifyServerConfig.messageJobs).map(testRegexp => {
const test = new RegExp(testRegexp, 'i');
if (test.test(changedSubject)) {
clearTimeout(notifyServerMessageTimeout);
log(
`Notify message [ '${color.cyan(
changedSubject
)}' ] queueing jobs:`,
notifyServerConfig.messageJobs[testRegexp]
);
notifyServerJobQueue = notifyServerJobQueue.concat(
notifyServerConfig.messageJobs[testRegexp].filter(job => {
return !notifyServerJobQueue.includes(job);
})
);
notifyServerMessageTimeout = setTimeout(() => {
log(
`Starting queued jobs: ${color.cyan(
notifyServerJobQueue.join(',')
)}`
);
gulp.parallel(notifyServerJobQueue)();
notifyServerJobQueue = [];
}, notifyServerConfig.jobRunTimeout);
}
});
});
}
gulp
.watch([
'./ima/**/*.js',
'./app/**/*.{js,jsx}',
'./build/static/js/locale/*.js'
])
.on('all', (event, filePath) => {
sharedState.watchEvent = { path: filePath };
let absoluteFilePath = path.resolve('.', filePath);
let cacheKey = absoluteFilePath.toLowerCase().replace('.jsx', '.js');
hotReloadedCacheKeys.push(cacheKey);
if (event === 'unlink') {
if (cache.caches['Es6ToEs5:server:app'][absoluteFilePath]) {
delete cache.caches['Es6ToEs5:server:app'][absoluteFilePath];
remember.forget(
'Es6ToEs5:server:app',
absoluteFilePath.replace('.jsx', '.js')
);
}
}
});
flo(
'./build/static/',
{
port: occupiedPorts['fb-flo'],
host: 'localhost',
glob: ['**/*.css', '**/*.js']
},
(filepath, callback) => {
log(`Reloading 'public/${color.cyan(filepath)}' with ` + 'flo...');
let hotReloadedContents = '';
if (path.parse(filepath).ext === '.css') {
hotReloadedContents = fs.readFileSync('./build/static/' + filepath);
} else {
hotReloadedContents = hotReloadedCacheKeys.map(cacheKey => {
let file = remember.cacheFor('Es6ToEs5:server:app')[cacheKey];
if (!file) {
return '';
}
return file.contents
.toString()
.replace(/System.import/g, '$IMA.Loader.import')
.replace(/System.register/g, '$IMA.Loader.replaceModule');
});
hotReloadedCacheKeys = [];
}
callback({
resourceURL: 'static/' + filepath,
contents: hotReloadedContents
});
}
);
function runOnChange(files, task) {
watch(files, () => gulp.series(task)());
}
}
function checkAndReleasePorts() {
const occupants = Object.keys(occupiedPorts);
log(`Releasing ports occupied by ${occupants.join(', ')}`);
return Promise.all(
occupants.map(occupant => {
const port = occupiedPorts[occupant];
return isPortOccupied(port)
.then(occupied => {
if (!occupied) {
return;
}
log(`Releasing port occupied by ${occupant}.`);
const command =
process.platform === 'win32'
? `Stop-Process -Id (Get-NetTCPConnection -LocalPort ${port}).OwningProcess -Force`
: `lsof -i:${port} | grep LISTEN | awk '{print $2}' | xargs kill -9`;
return exec(command).catch(() => null);
})
.catch(error => {
log(error);
throw Error(`Unable to determine if port ${port} is occupied.`);
});
})
);
}
function isPortOccupied(port) {
return new Promise((resolve, reject) => {
const tester = net.createServer();
tester.once('error', error => {
if (error.code !== 'EADDRINUSE') {
return reject(error);
}
resolve(true);
});
tester.once('listening', () => {
tester.once('close', () => resolve(false)).close();
});
tester.listen(port);
});
}
return {
watch: watchTask,
'watch:releasePorts': checkAndReleasePorts
};
};