UNPKG

node-hot-loader

Version:

Hot module replacement for Node.js applications

314 lines (303 loc) 12.2 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 memfs = _interopRequireWildcard(require("memfs")); var _LogColors = _interopRequireDefault(require("./LogColors")); var _Logger = _interopRequireDefault(require("./Logger")); var _LogLevel = require("./LogLevel"); var _messageActionType = _interopRequireDefault(require("./messageActionType")); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } 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, "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) { this.context.fs = memfs.createFsFromVolume(new memfs.Volume()); _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 `/`.'); // } } 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 }); } } 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; } requireFromMemory(filename) { const Module = module.constructor.length > 1 ? module.constructor : require('module'); const { _findPath } = Module; const { readFileSync, statSync } = _fs.default; const basedir = _path.default.dirname(filename); Module._findPath = (request, ...rest) => { if (request === filename || request.includes('.hot-update.js')) { return _path.default.isAbsolute(request) ? request : _path.default.resolve(basedir, request); } return _findPath.call(Module, request, ...rest); }; _fs.default.readFileSync = (fname, ...rest) => { if (fname === filename || fname.includes('.hot-update.js')) { return this.context.fs.readFileSync(fname, ...rest); } return readFileSync.call(_fs.default, fname, ...rest); }; _fs.default.statSync = (fname, ...rest) => { if (fname === filename || fname.includes('.hot-update.js')) { return this.context.fs.statSync(fname, ...rest); } return statSync.call(_fs.default, fname, ...rest); }; delete require.cache[filename]; return require(filename); } 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 launch script. if (this.context.inMemory) { this.requireFromMemory(launcherFileName); } else { require(`${launcherFileName}`); } this.context.serverProcess = process; }).catch(err => { this.context.logger.error(err); process.exit(); }); } } exports.default = HmrServer;