diff --git a/cli/run/watch-cli.ts b/cli/run/watch-cli.ts index 9629f8fd420..fb2ef95de75 100644 --- a/cli/run/watch-cli.ts +++ b/cli/run/watch-cli.ts @@ -129,7 +129,6 @@ export async function watch(command: any) { if (event.result && event.result.getTimings) { printTimings(event.result.getTimings()); } - event.result.close().catch(error => handleError(error, true)); break; case 'END': @@ -137,6 +136,10 @@ export async function watch(command: any) { stderr(`\n[${dateTime()}] waiting for changes...`); } } + + if ('result' in event && event.result) { + event.result.close().catch(error => handleError(error, true)); + } }); } diff --git a/docs/02-javascript-api.md b/docs/02-javascript-api.md index 0db750c6f66..cfa9429466d 100755 --- a/docs/02-javascript-api.md +++ b/docs/02-javascript-api.md @@ -196,11 +196,15 @@ watcher.on('event', event => { // END — finished building all bundles // ERROR — encountered an error while bundling // * event.error contains the error that was thrown + // * event.result is null for build errors and contains the + // bundle object for output generation errors. As with + // "BUNDLE_END", you should call "event.result.close()" if + // present once you are done. }); // This will make sure that bundles are properly closed after each run -watcher.on('event', ({ code, result }) => { - if (code === 'BUNDLE_END') { +watcher.on('event', ({ result }) => { + if (result) { result.close(); } }); diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index f4c2fa90215..a185e66f80a 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -857,7 +857,7 @@ export type RollupWatcherEvent = result: RollupBuild; } | { code: 'END' } - | { code: 'ERROR'; error: RollupError }; + | { code: 'ERROR'; error: RollupError; result: RollupBuild | null }; export interface RollupWatcher extends TypedEventEmitter<{ diff --git a/src/watch/watch.ts b/src/watch/watch.ts index 142c9e78e35..b6a1881fdb6 100644 --- a/src/watch/watch.ts +++ b/src/watch/watch.ts @@ -100,27 +100,18 @@ export class Watcher { private async run() { this.running = true; - this.emitter.emit('event', { code: 'START' }); - try { - for (const task of this.tasks) { - await task.run(); - } - this.running = false; - this.emitter.emit('event', { - code: 'END' - }); - } catch (error) { - this.running = false; - this.emitter.emit('event', { - code: 'ERROR', - error - }); + for (const task of this.tasks) { + await task.run(); } + this.running = false; + this.emitter.emit('event', { + code: 'END' + }); if (this.rerun) { this.rerun = false; this.invalidate(); @@ -182,7 +173,7 @@ export class Task { this.watcher.invalidate({ id, event: details.event }); } - async run() { + async run(): Promise { if (!this.invalidated) return; this.invalidated = false; @@ -198,14 +189,15 @@ export class Task { input: this.options.input, output: this.outputFiles }); + let result: RollupBuild | null = null; try { - const result = await rollupInternal(options, this.watcher.emitter); + result = await rollupInternal(options, this.watcher.emitter); if (this.closed) { return; } this.updateWatchedFiles(result); - this.skipWrite || (await Promise.all(this.outputs.map(output => result.write(output)))); + this.skipWrite || (await Promise.all(this.outputs.map(output => result!.write(output)))); this.watcher.emitter.emit('event', { code: 'BUNDLE_END', duration: Date.now() - start, @@ -214,19 +206,21 @@ export class Task { result }); } catch (error) { - if (this.closed) { - return; - } - - if (Array.isArray(error.watchFiles)) { - for (const id of error.watchFiles) { - this.watchFile(id); + if (!this.closed) { + if (Array.isArray(error.watchFiles)) { + for (const id of error.watchFiles) { + this.watchFile(id); + } + } + if (error.id) { + this.cache.modules = this.cache.modules.filter(module => module.id !== error.id); } } - if (error.id) { - this.cache.modules = this.cache.modules.filter(module => module.id !== error.id); - } - throw error; + this.watcher.emitter.emit('event', { + code: 'ERROR', + error, + result + }); } } diff --git a/test/cli/samples/watch/close-on-generate-error/_config.js b/test/cli/samples/watch/close-on-generate-error/_config.js new file mode 100644 index 00000000000..a94f8646c41 --- /dev/null +++ b/test/cli/samples/watch/close-on-generate-error/_config.js @@ -0,0 +1,19 @@ +const { assertIncludes } = require('../../../../utils.js'); + +module.exports = { + description: 'closes the bundle on generate errors', + command: 'rollup -cw', + abortOnStderr(data) { + if (data.includes('Bundle closed')) { + return true; + } + }, + stderr(stderr) { + assertIncludes( + stderr, + '[!] Error: You must specify "output.file" or "output.dir" for the build.' + ); + assertIncludes(stderr, 'Bundle closed'); + return false; + } +}; diff --git a/test/cli/samples/watch/close-on-generate-error/main.js b/test/cli/samples/watch/close-on-generate-error/main.js new file mode 100644 index 00000000000..7a4e8a723a4 --- /dev/null +++ b/test/cli/samples/watch/close-on-generate-error/main.js @@ -0,0 +1 @@ +export default 42; diff --git a/test/cli/samples/watch/close-on-generate-error/rollup.config.js b/test/cli/samples/watch/close-on-generate-error/rollup.config.js new file mode 100644 index 00000000000..2b5ed8843ea --- /dev/null +++ b/test/cli/samples/watch/close-on-generate-error/rollup.config.js @@ -0,0 +1,14 @@ +export default { + input: 'main.js', + plugins: [ + { + name: 'faulty-close', + closeBundle() { + console.error('Bundle closed') + } + } + ], + output: { + format: "es" + } +};