import * as chokidar from 'chokidar'; import { resolve as pathResolve } from 'path'; import { logger } from '@spine/logger'; import { getProjectPath, getSourcePath, getConfigPath, } from '@erect/core/paths'; import { config } from '@erect/core/config'; import { HotDevelopment } from './hotDevelopment'; import { cliDevelopHook, deathHook } from '../hooks'; import { removeNil } from '@erect/core/shared/utils'; const log = logger('develop'); let changeTimer: any; let devServer: HotDevelopment; let watcher: chokidar.FSWatcher; const resolveSourcePath = (sourcePath: string) => pathResolve(getSourcePath(), sourcePath); const resolveSourcePathFile = (sourcePath: string) => { if (sourcePath.match(/^\./)) { try { return require.resolve(resolveSourcePath(sourcePath)); } catch (error) { return null; } } return sourcePath; }; const getNormalizedWatchBuildPaths = () => removeNil(config.devWatchBuildPaths.map(resolveSourcePathFile)); const getWatchBuildPathsString = (watchBuildPaths = getNormalizedWatchBuildPaths()) => JSON.stringify(watchBuildPaths.sort()); function createDevelopmentEnv() { // Re-require the development devServer so that all new configs are used. const { HotDevelopment } = require('./hotDevelopment'); devServer = new HotDevelopment(); const watchBuildPaths = getNormalizedWatchBuildPaths(); const cacheBuildPaths = getWatchBuildPathsString(watchBuildPaths); const watchPaths: string[] = [ ...watchBuildPaths, ]; const projectRoot = pathResolve(__dirname, '../..'); if (getProjectPath() === projectRoot) { watchPaths.push(...[ pathResolve(getProjectPath(), 'client'), pathResolve(getProjectPath(), 'shared'), pathResolve(getProjectPath(), 'internal'), pathResolve(getProjectPath(), 'config'), pathResolve(getProjectPath(), 'loadConfig'), pathResolve(getProjectPath(), 'loadEnv'), pathResolve(getProjectPath(), 'loadPluggable'), ]); } // Any changes to our webpack bundleConfigs should restart the development devServer. watcher = chokidar.watch(watchPaths); let changedFilesMap: { [name: string]: any } = {}; const changeListener = async () => { log.warn('Project build configuration has changed. Restarting the development server...'); for (const changedFile in changedFilesMap) { log.verbose(`File "${changedFile}" has changed`); } devServer.dispose().then(async () => { // Make sure our new webpack bundleConfigs aren't in the module caches. delete require.cache[getConfigPath()]; delete require.cache[pathResolve(__dirname, '../../loadConfig')]; for (const changedFile in changedFilesMap) { delete require.cache[changedFile]; } const projectPath = pathResolve(__dirname, '../../..'); for (const projectFile of Object.keys(require.cache)) { if (projectFile === __filename) { continue; } if (((projectFile.substr(0, projectPath.length) === projectPath && !projectFile.match(/\/node_modules\//)) || (projectFile.substr(0, getProjectPath().length) === getProjectPath() && !projectFile.match(/\/node_modules\//)) || projectFile.match(/node_modules\/@spine\/config/) )) { delete require.cache[projectFile]; } } /* Object.keys(require.cache).forEach((modulePath) => { // TODO: better check substr === if (modulePath.indexOf(pathResolve(__dirname, '../..')) !== -1) { delete require.cache[modulePath]; } }); */ require('../../loadConfig'); changedFilesMap = {}; if (cacheBuildPaths !== getWatchBuildPathsString()) { (async () => { console.log('did change'); watcher.close(); createDevelopmentEnv(); })().catch(error => { console.error(error); process.exit(); }); return; } // Re-require the development devServer so that all new configs are used. const { HotDevelopment } = require('./hotDevelopment'); // Create a new development devServer. devServer = new HotDevelopment(true); }) .catch(error => { console.error(error); process.exit(0); }); }; watcher.on('ready', () => { watcher.on('change', (changedFile) => { changedFilesMap[changedFile] = true; clearTimeout(changeTimer); changeTimer = setTimeout(changeListener, 100); }); }); } cliDevelopHook.addAction('default', () => { createDevelopmentEnv(); deathHook.addAction('develop', async () => { if (watcher != null) { watcher.close(); } if (devServer != null) { await devServer.dispose(); } }); }, { priority: 50 });