From 8edb784f7a35b5db84d715ff98882d174293aab6 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 14 Jan 2022 10:18:32 +0100 Subject: [PATCH] Immediately restart Rollup upon config file changes and change test to look for a message from the second config --- cli/logging.ts | 2 +- cli/run/watch-cli.ts | 37 ++++------ .../watch-config-early-update/_config.js | 74 +++++++++---------- 3 files changed, 50 insertions(+), 63 deletions(-) diff --git a/cli/logging.ts b/cli/logging.ts index 959eae94071..59b4ec94630 100644 --- a/cli/logging.ts +++ b/cli/logging.ts @@ -3,7 +3,7 @@ import { bold, cyan, dim, red } from '../src/utils/colors'; import relativeId from '../src/utils/relativeId'; // log to stderr to keep `rollup main.js > bundle.js` from breaking -export const stderr = console.error.bind(console); +export const stderr = (...args: unknown[]) => process.stderr.write(`${args.join('')}\n`); export function handleError(err: RollupError, recover = false): void { let description = err.message || err; diff --git a/cli/run/watch-cli.ts b/cli/run/watch-cli.ts index 9c83fc8265e..ad3e1242a5f 100644 --- a/cli/run/watch-cli.ts +++ b/cli/run/watch-cli.ts @@ -23,6 +23,7 @@ export async function watch(command: Record): Promise { let warnings: BatchWarnings; let watcher: RollupWatcher; let configWatcher: FSWatcher; + let resetScreen: (heading: string) => void; const configFile = command.config ? getConfigPath(command.config) : null; onExit(close); @@ -33,11 +34,10 @@ export async function watch(command: Record): Promise { } async function loadConfigFromFileAndTrack(configFile: string): Promise { - let reloadingConfig = false; - let aborted = false; let configFileData: string | null = null; + let configFileRevision = 0; - configWatcher = chokidar.watch(configFile).on('change', () => reloadConfigFile()); + configWatcher = chokidar.watch(configFile).on('change', reloadConfigFile); await reloadConfigFile(); async function reloadConfigFile() { @@ -46,29 +46,23 @@ export async function watch(command: Record): Promise { if (newConfigFileData === configFileData) { return; } - if (reloadingConfig) { - aborted = true; - return; - } + configFileRevision++; + const currentConfigFileRevision = configFileRevision; if (configFileData) { stderr(`\nReloading updated config...`); } configFileData = newConfigFileData; - reloadingConfig = true; - ({ options: configs, warnings } = await loadAndParseConfigFile(configFile, command)); - reloadingConfig = false; - if (aborted) { - aborted = false; - reloadConfigFile(); - } else { - if (watcher) { - watcher.close(); - } - start(configs); + const parsedConfig = await loadAndParseConfigFile(configFile, command); + if (currentConfigFileRevision !== configFileRevision) { + return; } + if (watcher) { + watcher.close(); + } + ({ options: configs, warnings } = parsedConfig); + start(configs); } catch (err: any) { configs = []; - reloadingConfig = false; handleError(err, true); } } @@ -81,8 +75,6 @@ export async function watch(command: Record): Promise { start(configs); } - const resetScreen = getResetScreen(configs!, isTTY); - function start(configs: MergedRollupOptions[]): void { try { watcher = rollup.watch(configs as any); @@ -99,6 +91,9 @@ export async function watch(command: Record): Promise { case 'START': if (!silent) { + if (!resetScreen) { + resetScreen = getResetScreen(configs!, isTTY); + } resetScreen(underline(`rollup v${rollup.VERSION}`)); } break; diff --git a/test/cli/samples/watch/watch-config-early-update/_config.js b/test/cli/samples/watch/watch-config-early-update/_config.js index c29cc0977bc..a2d163ef062 100644 --- a/test/cli/samples/watch/watch-config-early-update/_config.js +++ b/test/cli/samples/watch/watch-config-early-update/_config.js @@ -5,57 +5,50 @@ const { writeAndSync } = require('../../../../utils'); let configFile; module.exports = { - solo: true, repeat: 100, + solo: true, description: 'immediately reloads the config file if a change happens while it is parsed', command: 'rollup -cw', before() { - // This test writes a config file that prints a message to stderr but delays resolving to a - // config. The stderr message is observed by the parent process and triggers overwriting the - // config file. That way, we simulate a complicated config file being changed while it is parsed. - configFile = path.resolve(__dirname, 'rollup.config.js'); - console.time('testTime'); + // This test writes a config file that prints a message to stderr which signals to the test that + // the config files has been parsed, at which point the test replaces the config file. The + // initial file returns a Promise that only resolves once the second config file has been + // parsed. To do that, the first config hooks into process.stderr and looks for a log from the + // second config. + // That way, we simulate a complicated config file being changed while it is parsed. + configFile = path.join(__dirname, 'rollup.config.js'); + fs.mkdirSync(path.join(__dirname, '_actual')); writeAndSync( configFile, ` - import { watch } from 'fs'; - let watcher; - - // Sometimes, fs.watch does not seem to trigger on MacOS. Thus, we wait at most 5 seconds. - export default Promise.race([ - new Promise(resolve => { - watcher = watch(${JSON.stringify(configFile)}, () => { - console.error('config update detected'); - watcher.close(); - watcher = null; - // wait a moment to make sure we do not trigger before Rollup's watcher - setTimeout(resolve, 600); - }) - }), - new Promise(resolve => setTimeout(() => { - if (watcher) { - watcher.close(); - }; - resolve(); - }, 5000)) - ]).then(() => ({ - input: 'main.js', - output: { - file: '_actual/output1.js', - format: 'es' - } - })); - console.error('initial'); - ` + import { Writable } from 'stream'; + process.stderr.write('initial\\n'); + const processStderr = process.stderr; + export default new Promise(resolve => { + delete process.stderr; + process.stderr = new Writable({ + write(chunk, encoding, next) { + processStderr.write(chunk, encoding, next); + if (chunk.toString() === 'updated\\n') { + process.stderr.end(); + process.stderr = processStderr; + resolve({ + input: 'main.js', + output: { + file: '_actual/output1.js', + format: 'es' + } + }) + } + }, + }); + });` ); - return new Promise(resolve => setTimeout(resolve, 600)); }, after() { - console.timeEnd('testTime'); fs.unlinkSync(configFile); }, abortOnStderr(data) { - console.log('data:', data); if (data === 'initial\n') { writeAndSync( configFile, @@ -67,13 +60,12 @@ module.exports = { file: '_actual/output2.js', format: "es" } - }; - ` + };` ); return false; } if (data.includes(`created _actual${path.sep}output2.js`)) { - return new Promise(resolve => setTimeout(() => resolve(true), 600)); + return new Promise(resolve => setTimeout(() => resolve(true), 500)); } } };