UNPKG

node-hot-loader

Version:

Hot module replacement for Node.js applications

283 lines (272 loc) 10.6 kB
"use strict"; exports.__esModule = true; exports.default = void 0; var _fs = _interopRequireDefault(require("fs")); var _path = _interopRequireDefault(require("path")); var _child_process = require("child_process"); var _requireFromString = _interopRequireDefault(require("require-from-string")); var _LogColors = _interopRequireDefault(require("./LogColors")); var _Logger = _interopRequireDefault(require("./Logger")); var _LogLevel = require("./LogLevel"); var _messageActionType = _interopRequireDefault(require("./messageActionType")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } class HmrServer { static defaultReporter({ context, stateValid, stats, compilerOptions }) { // for check log level for webpack compiler only const compilerLogLevel = (0, _LogLevel.parseLogLevel)(compilerOptions.stats); if (!stateValid) { if (compilerLogLevel >= _LogLevel.LogLevel.MINIMAL) { context.webpackLogger.info('Compiling...'); } return; } if (compilerLogLevel > _LogLevel.LogLevel.NONE) { const statsInfo = stats.toString(compilerOptions.stats); if (statsInfo) { // To avoid log empty statsInfo, e.g. when options.stats is 'errors-only'. context.webpackLogger.info(statsInfo); } } if (compilerLogLevel >= _LogLevel.LogLevel.ERRORS) { if (stats.hasErrors()) { context.webpackLogger.error('Failed to compile.'); } else if (stats.hasWarnings()) { context.webpackLogger.warn('Compiled with warnings.'); } } if (compilerLogLevel >= _LogLevel.LogLevel.MINIMAL) { context.webpackLogger.info('Compiled successfully.'); } } /** * @param {{ * compiler: import('webpack').Compiler; * fork?: boolean | string[]; * args?: string[]; * autoRestart?: boolean; * inMemory?: boolean; * logLevel?: string; * }} options */ constructor(options) { _defineProperty(this, "context", { /** When started compiled script contains process object in which script running. */ serverProcess: null, /** Valid or invalid state. */ stateValid: false, /** Last compiler stats. */ /** @type import('webpack').Stats */ webpackStats: undefined, /** Compiler watching by compiler.watch(...). */ watching: undefined, /** * Do not use memory-fs because we can't fork bundle from in-memory file. */ fs: _fs.default, reporter: HmrServer.defaultReporter, logger: new _Logger.default(_LogColors.default.cyan('[HMR]')), webpackLogger: new _Logger.default(_LogColors.default.magenta('Webpack')), /** @type {import('webpack').Compiler} */ compiler: undefined, fork: false, args: undefined, autoRestart: false, inMemory: false, logLevel: undefined }); _defineProperty(this, "sendMessage", action => { if (!this.context.serverProcess) { return; } const logLevel = this.context.logLevel != null ? (0, _LogLevel.parseLogLevel)(this.context.logLevel) : (0, _LogLevel.parseLogLevel)(this.context.compiler.options.stats); if (this.context.fork) { this.context.serverProcess.send({ action, stats: this.context.webpackStats.toJson(), logLevel }); } else { this.context.serverProcess.emit('message', { action, stats: this.context.webpackStats.toJson(), logLevel }); } }); _defineProperty(this, "getLauncherFileName", stats => { const assets = Object.values(stats.toJson().entrypoints).reduce((acc, group) => { return acc.concat(...group.assets.filter(asset => /\.[cm]?js$/i.test(asset.name || asset)).map(asset => _path.default.resolve(stats.compilation.compiler.outputPath, asset.name || asset))); }, []); if (assets.length === 1) { // Only one valid assets, so just return it path return assets[0]; } // Create temp launcher file which aggregates all assets. const launcherString = assets.map(asset => `require('${asset.replace(/\\/g, '/')}');`).join('\n'); const launcherFileName = _path.default.resolve(stats.compilation.compiler.outputPath, `launcher.${stats.hash}.js`); this.context.fs.writeFileSync(launcherFileName, launcherString); // If not launched yet (eg. if not a restart) if (!this.context.serverProcess) { // Delete created files on exit main process. const deleteLauncher = () => this.context.fs.unlinkSync(launcherFileName); process.on('exit', deleteLauncher); process.on('SIGINT', deleteLauncher); } return launcherFileName; }); _defineProperty(this, "launchAssets", stats => { return Promise.resolve().then(() => { const launcherFileName = this.getLauncherFileName(stats); // Execute built scripts if (this.context.fork) { /** @type import('child_process').ForkOptions */ const forkOptions = { cwd: process.cwd(), env: process.env, execArgv: this.context.fork === true ? undefined : this.context.fork }; if (process.getuid) { forkOptions.uid = process.getuid(); forkOptions.gid = process.getgid(); } // Launch in forked process this.context.serverProcess = (0, _child_process.fork)(launcherFileName, this.context.args || process.argv, forkOptions); // Exit main process when exit child process. const onChildExit = code => process.exit(code); this.context.serverProcess.on('exit', onChildExit); if (this.context.autoRestart) { // Listen messages from child process this.context.serverProcess.on('message', ({ action }) => { if (action !== _messageActionType.default.RestartRequired) return; this.context.logger.warn('AutoRestart is on. Restarting process...'); this.context.serverProcess.off('exit', onChildExit); this.context.serverProcess.kill('SIGINT'); this.launchAssets(stats); }); } this.context.logger.info('Launch assets in forked process.'); return; } // Require in current process to lauch script. if (this.context.inMemory) { (0, _requireFromString.default)(this.context.fs.readFileSync(launcherFileName).toString()); } else { require(`${launcherFileName}`); } this.context.serverProcess = process; }).catch(err => { this.context.logger.error(err); process.exit(); }); }); _defineProperty(this, "compilerStart", () => { try { this.sendMessage(_messageActionType.default.CompilerStart); if (this.context.watching && this.context.stateValid) { this.context.reporter({ stateValid: false, context: this.context, compilerOptions: this.context.compiler.options }); } // We are now in invalid state this.context.stateValid = false; } catch (ex) { this.context.logger.error(ex); } }); /** * @param {import('webpack').Stats} stats */ _defineProperty(this, "compilerDone", stats => new Promise(resolve => { // We are now on valid state this.context.stateValid = true; this.context.webpackStats = stats; // Do the stuff in nextTick, because bundle may be invalidated // if a change happened while compiling process.nextTick(() => { // check if still in valid state if (!this.context.stateValid) return; // print webpack output if (this.context.watching) { this.context.reporter({ stateValid: true, stats, context: this.context, compilerOptions: this.context.compiler.options }); } // Already has launched process if (this.context.serverProcess) { this.sendMessage(_messageActionType.default.CompilerDone); } // Start compiled files in child process (fork) or in current process. else { this.launchAssets(stats); } resolve(); }); }).catch(ex => { this.context.logger.error(ex); })); _defineProperty(this, "startWatch", () => { const { compiler } = this.context; // start watching this.context.watching = compiler.watch(compiler.options.watchOptions, err => { if (err) { this.context.logger.error(err.stack || err); if (err.details) this.context.logger.error(err.details); } }); this.context.logger.info('Waiting webpack...'); }); _defineProperty(this, "run", (watch = true) => { const { compiler } = this.context; if (compiler.hooks) { // webpack >= 4 compiler.hooks.invalid.tap('node-hot-loader', this.compilerStart); compiler.hooks.done.tapPromise('node-hot-loader', this.compilerDone); } else { // webpack < 4 compiler.plugin('invalid', this.compilerStart); compiler.plugin('done', this.compilerDone); } if (watch) { this.startWatch(); } return this; }); this.context = { ...this.context, ...options }; const { compiler: _compiler, inMemory } = this.context; if (inMemory) { const getName = () => 'memory-fs'; const MemoryFileSystem = require(getName()); this.context.fs = new MemoryFileSystem(); _compiler.outputFileSystem = this.context.fs; } // if (typeof compiler.outputPath === 'string' && !path.isAbsolute(compiler.outputPath)) { // throw new Error('`output.path` needs to be an absolute path or `/`.'); // } } } exports.default = HmrServer;