Skip to content

Commit

Permalink
feat: bundle closing (#3883)
Browse files Browse the repository at this point in the history
* add close method to RollupBuild

* add closeBundle hook to PluginDriver

* run closeBundle on build failure

* make close return Promise<void>

* close bundle on cli build completion

* closeBundle also returns Promise<void>

* add some tests

* add relevant docs

* Update docs/02-javascript-api.md

Co-authored-by: Lukas Taegert-Atkinson <[email protected]>

* sort errors enum

* always run closeBundle even on watch mode

* Add test for generating/writing closed bundles

* add test for errors thrown during bundle close

* have cli watcher close the bundle

* bundle close errors are recoverable

* Test closeBundle errors are displayed on the CLI

* Update and extend documentation.

* Update dependencies

Co-authored-by: Lukas Taegert-Atkinson <[email protected]>
Co-authored-by: Lukas Taegert-Atkinson <[email protected]>
  • Loading branch information
3 people authored Dec 14, 2020
1 parent 92a2dfa commit 5f576c6
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 189 deletions.
1 change: 1 addition & 0 deletions cli/run/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default async function build(
}

await Promise.all(outputOptions.map(bundle.write));
await bundle.close();
if (!silent) {
warnings.flush();
stderr(green(`created ${bold(files.join(', '))} in ${bold(ms(Date.now() - start))}`));
Expand Down
1 change: 1 addition & 0 deletions cli/run/watch-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ 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':
Expand Down
23 changes: 22 additions & 1 deletion docs/02-javascript-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The `rollup.rollup` function receives an input options object as parameter and r

On a `bundle` object, you can call `bundle.generate` multiple times with different output options objects to generate different bundles in-memory. If you directly want to write them to disk, use `bundle.write` instead.

Once you're finished with the `bundle` object, you should call `bundle.close()`, which will let plugins clean up their external processes or services via the [`closeBundle`](guide/en/#closebundle) hook.

```javascript
const rollup = require('rollup');

Expand Down Expand Up @@ -69,6 +71,9 @@ async function build() {

// or write the bundle to disk
await bundle.write(outputOptions);

// closes the bundle
await bundle.close();
}

build();
Expand Down Expand Up @@ -160,7 +165,7 @@ const outputOptions = {

### rollup.watch

Rollup also provides a `rollup.watch` function that rebuilds your bundle when it detects that the individual modules have changed on disk. It is used internally when you run Rollup from the command line with the `--watch` flag.
Rollup also provides a `rollup.watch` function that rebuilds your bundle when it detects that the individual modules have changed on disk. It is used internally when you run Rollup from the command line with the `--watch` flag. Note that when using watch mode via the JavaScript API, it is your responsibility to call `event.result.close()` in response to the `BUNDLE_END` event to allow plugins to clean up resources in the [`closeBundle`](guide/en/#closebundle) hook, see below.

```js
const rollup = require('rollup');
Expand All @@ -172,9 +177,25 @@ watcher.on('event', event => {
// event.code can be one of:
// START — the watcher is (re)starting
// BUNDLE_START — building an individual bundle
// * event.input will be the input options object if present
// * event.outputFiles cantains an array of the "file" or
// "dir" option values of the generated outputs
// BUNDLE_END — finished building a bundle
// * event.input will be the input options object if present
// * event.outputFiles cantains an array of the "file" or
// "dir" option values of the generated outputs
// * event.duration is the build duration in milliseconds
// * event.result contains the bundle object that can be
// used to generate additional outputs by calling
// bundle.generate or bundle.write. This is especially
// important when the watch.skipWrite option is used.
// You should call "event.result.close()" once you are done
// generating outputs, or if you do not generate outputs.
// This will allow plugins to clean up resources via the
// "closeBundle" hook.
// END — finished building all bundles
// ERROR — encountered an error while bundling
// * event.error contains the error that was thrown
});

// stop watching
Expand Down
17 changes: 14 additions & 3 deletions docs/05-plugin-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ To interact with the build process, your plugin object includes 'hooks'. Hooks a
* `sequential`: If several plugins implement this hook, all of them will be run in the specified plugin order. If a hook is async, subsequent hooks of this kind will wait until the current hook is resolved.
* `parallel`: If several plugins implement this hook, all of them will be run in the specified plugin order. If a hook is async, subsequent hooks of this kind will be run in parallel and not wait for the current hook.

Build hooks are run during the build phase, which is triggered by `rollup.rollup(inputOptions)`. They are mainly concerned with locating, providing and transforming input files before they are processed by Rollup. The first hook of the build phase is [options](guide/en/#options), the last one is always [buildEnd](guide/en/#buildend).
Build hooks are run during the build phase, which is triggered by `rollup.rollup(inputOptions)`. They are mainly concerned with locating, providing and transforming input files before they are processed by Rollup. The first hook of the build phase is [`options`](guide/en/#options), the last one is always [`buildEnd`](guide/en/#buildend), unless there is a build error in which case [`closeBundle`](guide/en/#closebundle) will be called after that.

Additionally, in watch mode the [watchChange](guide/en/#watchchange) hook can be triggered at any time to notify a new run will be triggered once the current run has generated its outputs. Also, when watcher closes, the [closeWatcher](guide/en/#closewatcher) hook will be triggered.
Additionally, in watch mode the [`watchChange`](guide/en/#watchchange) hook can be triggered at any time to notify a new run will be triggered once the current run has generated its outputs. Also, when watcher closes, the [`closeWatcher`](guide/en/#closewatcher) hook will be triggered.

See [Output Generation Hooks](guide/en/#output-generation-hooks) for hooks that run during the output generation phase to modify the generated output.

Expand Down Expand Up @@ -255,7 +255,9 @@ Notifies a plugin whenever rollup has detected a change to a monitored file in `

Output generation hooks can provide information about a generated bundle and modify a build once complete. They work the same way and have the same types as [Build Hooks](guide/en/#build-hooks) but are called separately for each call to `bundle.generate(outputOptions)` or `bundle.write(outputOptions)`. Plugins that only use output generation hooks can also be passed in via the output options and therefore run only for certain outputs.

The first hook of the output generation phase is [outputOptions](guide/en/#outputoptions), the last one is either [generateBundle](guide/en/#generatebundle) if the output was successfully generated via `bundle.generate(...)`, [writeBundle](guide/en/#writebundle) if the output was successfully generated via `bundle.write(...)`, or [renderError](guide/en/#rendererror) if an error occurred at any time during the output generation.
The first hook of the output generation phase is [`outputOptions`](guide/en/#outputoptions), the last one is either [`generateBundle`](guide/en/#generatebundle) if the output was successfully generated via `bundle.generate(...)`, [`writeBundle`](guide/en/#writebundle) if the output was successfully generated via `bundle.write(...)`, or [`renderError`](guide/en/#rendererror) if an error occurred at any time during the output generation.

Additionally, [`closeBundle`](guide/en/#closebundle) can be called as the very last hook, but it is the responsibility of the User to manually call [`bundle.close()`](guide/en/#rolluprollup) to trigger this. The CLI will always make sure this is the case.

#### `augmentChunkHash`
Type: `(chunkInfo: ChunkInfo) => string`<br>
Expand Down Expand Up @@ -284,6 +286,15 @@ Next Hook: [`renderDynamicImport`](guide/en/#renderdynamicimport) for each dynam

Cf. [`output.banner/output.footer`](guide/en/#outputbanneroutputfooter).

#### `closeBundle`
Type: `closeBundle: () => Promise<void> | void`<br>
Kind: `async, parallel`<br>
Previous Hook: [`buildEnd`](guide/en/#buildend) if there was a build error, otherwise when [`bundle.close()`](guide/en/#rolluprollup) is called, in which case this would be the last hook to be triggered.

Can be used to clean up any external service that may be running. Rollup's CLI will make sure this hook is called after each run, but it is the responsibility of users of the JavaScript API to manually call `bundle.close()` once they are done generating bundles. For that reason, any plugin relying on this feature should carefully mention this in its documentation.

If a plugin wants to retain resources across builds in watch mode, they can check for [`this.meta.watchMode`](guide/en/#thismeta-rollupversion-string-watchmode-boolean) in this hook and perform the necessary cleanup for watch mode in [`closeWatcher`](guide/en/#closewatcher).

#### `footer`
Type: `string | (() => string)`<br>
Kind: `async, parallel`<br>
Expand Down
Loading

0 comments on commit 5f576c6

Please sign in to comment.