Skip to content

Commit

Permalink
Per output plugins (#3218)
Browse files Browse the repository at this point in the history
* Extract plugin cache to separate file

* Extract plugin context to separate file

* Extract more code from plugin driver

* Introduce a virtual "outputPluginDriver" that we can replace to have per-output plugins
TODO: Add phases (build/generate) to hooks in documentation
TODO: Think about creating an Output interface containing options, driver, bundle?

* Improve some names

* Make PluginDriver a class

* Use separate file emitters for different outputs

* Warn when reemitting files of the same name

* Generate outputs in parallel again

* Enable per-output plugins

* Warn if build-time hooks are used in an output plugin

* Pass output and intput options to the renderStart hook

* Make sure the CLI supports output plugins

* Add documentation

* Improve coverage
  • Loading branch information
lukastaegert committed Nov 12, 2019
1 parent 8318bb4 commit 17c14cd
Show file tree
Hide file tree
Showing 87 changed files with 1,722 additions and 1,004 deletions.
7 changes: 3 additions & 4 deletions cli/run/build.ts
Expand Up @@ -66,10 +66,9 @@ export default function build(
});
}

return outputOptions.reduce(
(prev, output) => prev.then(() => bundle.write(output) as Promise<any>),
Promise.resolve()
).then(() => bundle)
return Promise.all(outputOptions.map(output => bundle.write(output) as Promise<any>)).then(
() => bundle
);
})
.then((bundle: RollupBuild | null) => {
if (!silent) {
Expand Down
1 change: 1 addition & 0 deletions docs/01-command-line-reference.md
Expand Up @@ -66,6 +66,7 @@ export default { // can be an array (for multiple inputs)
format, // required
globals,
name,
plugins,

// advanced output options
assetFileNames,
Expand Down
1 change: 1 addition & 0 deletions docs/02-javascript-api.md
Expand Up @@ -116,6 +116,7 @@ const outputOptions = {
format, // required
globals,
name,
plugins,

// advanced output options
assetFileNames,
Expand Down
59 changes: 56 additions & 3 deletions docs/04-tutorial.md
Expand Up @@ -227,17 +227,70 @@ Run Rollup with `npm run build`. The result should look like this:
```js
'use strict';

const version = "1.0.0";
var version = "1.0.0";

const main = function () {
function main () {
console.log('version ' + version);
};
}

module.exports = main;
```

_Note: Only the data we actually need gets imported – `name` and `devDependencies` and other parts of `package.json` are ignored. That's **tree-shaking** in action._

### Using output plugins

Some plugins can also be applied specifically to some outputs. See [plugin hooks](guide/en/#hooks) for the technical details of what output-specific plugins can do. In a nut-shell, those plugins can only modify code after the main analysis of Rollup has completed. Rollup will warn if an incompatible plugin is used as an output-specific plugin. One possible use-case is minification of bundles to be consumed in a browser.

Let us extend the previous example to provide a minified build together with the non-minified one. To that end, we install `rollup-plugin-terser`:

```console
npm install --save-dev rollup-plugin-terser
```

Edit your `rollup.config.js` file to add a second minified output. As format, we choose `iife`. This format wraps the code so that it can be consumed via a `script` tag in the browser while avoiding unwanted interactions with other code. As we have an export, we need to provide the name of a global variable that will be created by our bundle so that other code can access our export via this variable.

```js
// rollup.config.js
import json from 'rollup-plugin-json';
import {terser} from 'rollup-plugin-terser';

export default {
input: 'src/main.js',
output: [
{
file: 'bundle.js',
format: 'cjs'
},
{
file: 'bundle.min.js',
format: 'iife',
name: 'version',
plugins: [terser()]
}
],
plugins: [ json() ]
};
```

Besides `bundle.js`, Rollup will now create a second file `bundle.min.js`:

```js
var version = (function () {
'use strict';

var version = "1.0.0";

function main () {
console.log('version ' + version);
}

return main;

}());
```


### Code Splitting

To use the code splitting feature, we got back to the original example and modify `src/main.js` to load `src/foo.js` dynamically instead of statically:
Expand Down
77 changes: 51 additions & 26 deletions docs/05-plugin-development.md
Expand Up @@ -72,9 +72,12 @@ In addition to properties defining the identity of your plugin, you may also spe
* `sequential`: If this hook returns a promise, then other hooks of this kind will only be executed once this hook has resolved
* `parallel`: If this hook returns a promise, then other hooks of this kind will not wait for this hook to be resolved

Furthermore, hooks can be run either during the `build` phase of the Rollup build, which is triggered by `rollup.rollup()`, or during the `generate` phase, which is triggered by `bundle.generate()` or `bundle.write()`. Plugins that only use `generate` phase hooks can also be passed in via the output options to `bundle.generate()` or `bundle.write()` and therefore run only for certain outputs.

#### `augmentChunkHash`
Type: `(preRenderedChunk: PreRenderedChunk) => string`<br>
Kind: `sync, sequential`
Kind: `sync, sequential`<br>
Phase: `generate`

Can be used to augment the hash of individual chunks. Called for each Rollup output chunk. Returning a falsy value will not modify the hash.

Expand All @@ -91,31 +94,36 @@ augmentChunkHash(chunkInfo) {

#### `banner`
Type: `string | (() => string)`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

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

#### `buildEnd`
Type: `(error?: Error) => void`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `build`

Called when rollup has finished bundling, but before `generate` or `write` is called; you can also return a Promise. If an error occurred during the build, it is passed on to this hook.

#### `buildStart`
Type: `(options: InputOptions) => void`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `build`

Called on each `rollup.rollup` build.
Called on each `rollup.rollup` build. This is the recommended hook to use when you need access to the options passed to `rollup.rollup()` as it will take the transformations by all [`options`](guide/en/#options) hooks into account.

#### `footer`
Type: `string | (() => string)`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

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

#### `generateBundle`
Type: `(options: OutputOptions, bundle: { [fileName: string]: AssetInfo | ChunkInfo }, isWrite: boolean) => void`<br>
Kind: `async, sequential`
Kind: `async, sequential`<br>
Phase: `generate`

Called at the end of `bundle.generate()` or immediately before the files are written in `bundle.write()`. To modify the files after they have been written, use the [`writeBundle`](guide/en/#writebundle) hook. `bundle` provides the full list of files being written or generated along with their details:

Expand Down Expand Up @@ -155,13 +163,15 @@ You can prevent files from being emitted by deleting them from the bundle object

#### `intro`
Type: `string | (() => string)`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

Cf. [`output.intro/output.outro`](guide/en/#outputintrooutputoutro).

#### `load`
Type: `(id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null }`<br>
Kind: `async, first`
Kind: `async, first`<br>
Phase: `build`

Defines a custom loader. Returning `null` defers to other `load` functions (and eventually the default behavior of loading from the file system). To prevent additional parsing overhead in case e.g. this hook already used `this.parse` to generate an AST for some reason, this hook can optionally return a `{ code, ast }` object. The `ast` must be a standard ESTree AST with `start` and `end` properties for each node.

Expand All @@ -171,43 +181,52 @@ You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--m

#### `options`
Type: `(options: InputOptions) => InputOptions | null`<br>
Kind: `sync, sequential`
Kind: `sync, sequential`<br>
Phase: `build`

Replaces or manipulates the options object passed to `rollup.rollup`. Returning `null` does not replace anything. If you just need to read the options, it is recommended to use the [`buildStart`](guide/en/#buildstart) hook as that hook has access to the options after the transformations from all `options` hooks have been taken into account.

Reads and replaces or manipulates the options object passed to `rollup.rollup`. Returning `null` does not replace anything. This is the only hook that does not have access to most [plugin context](guide/en/#plugin-context) utility functions as it is run before rollup is fully configured.
This is the only hook that does not have access to most [plugin context](guide/en/#plugin-context) utility functions as it is run before rollup is fully configured.

#### `outputOptions`
Type: `(outputOptions: OutputOptions) => OutputOptions | null`<br>
Kind: `sync, sequential`
Kind: `sync, sequential`<br>
Phase: `generate`

Reads and replaces or manipulates the output options object passed to `bundle.generate`. Returning `null` does not replace anything.
Replaces or manipulates the output options object passed to `bundle.generate()` or `bundle.write()`. Returning `null` does not replace anything. If you just need to read the output options, it is recommended to use the [`renderStart`](guide/en/#renderstart) hook as this hook has access to the output options after the transformations from all `outputOptions` hooks have been taken into account.

#### `outro`
Type: `string | (() => string)`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

Cf. [`output.intro/output.outro`](guide/en/#outputintrooutputoutro).

#### `renderChunk`
Type: `(code: string, chunk: ChunkInfo, options: OutputOptions) => string | { code: string, map: SourceMap } | null`<br>
Kind: `async, sequential`
Kind: `async, sequential`<br>
Phase: `generate`

Can be used to transform individual chunks. Called for each Rollup output chunk file. Returning `null` will apply no transformations.

#### `renderError`
Type: `(error: Error) => void`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

Called when rollup encounters an error during `bundle.generate()` or `bundle.write()`. The error is passed to this hook. To get notified when generation completes successfully, use the `generateBundle` hook.

#### `renderStart`
Type: `() => void`<br>
Kind: `async, parallel`
Type: `(outputOptions: OutputOptions, inputOptions: InputOptions) => void`<br>
Kind: `async, parallel`<br>
Phase: `generate`

Called initially each time `bundle.generate()` or `bundle.write()` is called. To get notified when generation has completed, use the `generateBundle` and `renderError` hooks.
Called initially each time `bundle.generate()` or `bundle.write()` is called. To get notified when generation has completed, use the `generateBundle` and `renderError` hooks. This is the recommended hook to use when you need access to the output options passed to `bundle.generate()` or `bundle.write()` as it will take the transformations by all [`outputOptions`](guide/en/#outputoptions) hooks into account. It also receives the input options passed to `rollup.rollup()` so that plugins that can be used as output plugins, i.e. plugins that only use `generate` phase hooks, can get access to them.

#### `resolveDynamicImport`
Type: `(specifier: string | ESTree.Node, importer: string) => string | false | null | {id: string, external?: boolean}`<br>
Kind: `async, first`
Kind: `async, first`<br>
Phase: `generate`

Defines a custom resolver for dynamic imports. Returning `false` signals that the import should be kept as it is and not be passed to other resolvers thus making it external. Similar to the [`resolveId`](guide/en/#resolveid) hook, you can also return an object to resolve the import to a different id while marking it as external at the same time.

Expand All @@ -222,7 +241,8 @@ Note that the return value of this hook will not be passed to `resolveId` afterw

#### `resolveFileUrl`
Type: `({chunkId: string, fileName: string, format: string, moduleId: string, referenceId: string, relativePath: string}) => string | null`<br>
Kind: `sync, first`
Kind: `sync, first`<br>
Phase: `generate`

Allows to customize how Rollup resolves URLs of files that were emitted by plugins via `this.emitAsset` or `this.emitChunk`. By default, Rollup will generate code for `import.meta.ROLLUP_ASSET_URL_assetReferenceId` and `import.meta.ROLLUP_CHUNK_URL_chunkReferenceId` that should correctly generate absolute URLs of emitted files independent of the output format and the host system where the code is deployed.

Expand All @@ -249,7 +269,8 @@ resolveFileUrl({fileName}) {

#### `resolveId`
Type: `(source: string, importer: string) => string | false | null | {id: string, external?: boolean, moduleSideEffects?: boolean | null}`<br>
Kind: `async, first`
Kind: `async, first`<br>
Phase: `build`

Defines a custom resolver. A resolver can be useful for e.g. locating third-party dependencies. Returning `null` defers to other `resolveId` functions and eventually the default resolution behavior; returning `false` signals that `source` should be treated as an external module and not included in the bundle. If this happens for a relative import, the id will be renormalized the same way as when the `external` option is used.

Expand All @@ -270,7 +291,8 @@ If `false` is returned for `moduleSideEffects` in the first hook that resolves a

#### `resolveImportMeta`
Type: `(property: string | null, {chunkId: string, moduleId: string, format: string}) => string | null`<br>
Kind: `sync, first`
Kind: `sync, first`<br>
Phase: `generate`

Allows to customize how Rollup handles `import.meta` and `import.meta.someProperty`, in particular `import.meta.url`. In ES modules, `import.meta` is an object and `import.meta.url` contains the URL of the current module, e.g. `http://server.net/bundle.js` for browsers or `file:///path/to/bundle.js` in Node.

Expand All @@ -292,7 +314,8 @@ Note that since this hook has access to the filename of the current chunk, its r

#### `transform`
Type: `(code: string, id: string) => string | null | { code: string, map?: string | SourceMap, ast? : ESTree.Program, moduleSideEffects?: boolean | null }`<br>
Kind: `async, sequential`
Kind: `async, sequential`<br>
Phase: `build`

Can be used to transform individual modules. To prevent additional parsing overhead in case e.g. this hook already used `this.parse` to generate an AST for some reason, this hook can optionally return a `{ code, ast }` object. The `ast` must be a standard ESTree AST with `start` and `end` properties for each node.

Expand All @@ -304,13 +327,15 @@ You can use [`this.getModuleInfo`](guide/en/#thisgetmoduleinfomoduleid-string--m

#### `watchChange`
Type: `(id: string) => void`<br>
Kind: `sync, sequential`
Kind: `sync, sequential`<br>
Phase: `build` (can also be triggered during `generate` but cannot be used by output plugins)

Notifies a plugin whenever rollup has detected a change to a monitored file in `--watch` mode.

#### `writeBundle`
Type: `( bundle: { [fileName: string]: AssetInfo | ChunkInfo }) => void`<br>
Kind: `async, parallel`
Kind: `async, parallel`<br>
Phase: `generate`

Called only at the end of `bundle.write()` once all files have been written. Similar to the [`generateBundle`](guide/en/#generatebundle) hook, `bundle` provides the full list of files being written along with their details.

Expand Down
37 changes: 35 additions & 2 deletions docs/999-big-list-of-options.md
Expand Up @@ -226,6 +226,35 @@ this.a.b.c = ...
*/
```

#### output.plugins
Type: `OutputPlugin | (OutputPlugin | void)[]`

Adds a plugin just to this output. See [Using output plugins](guide/en/#using-output-plugins) for more information on how to use output-specific plugins and [Plugins](guide/en/#plugin-development) on how to write your own. For plugins imported from packages, remember to call the imported plugin function (i.e. `commonjs()`, not just `commonjs`). Falsy plugins will be ignored, which can be used to easily activate or deactivate plugins.

Not every plugin can be used here. `output.plugins` is limited to plugins that only use hooks that run during `bundle.generate()` or `bundle.write()`, i.e. after Rollup's main analysis is complete. If you are a plugin author, see [Plugin hooks](guide/en/#hooks) to find out which hooks can be used.

The following will add minifaction to one of the outputs:

```js
// rollup.config.js
import {terser} from 'rollup-plugin-terser';

export default {
input: 'main.js',
output: [
{
file: 'bundle.js',
format: 'esm'
},
{
file: 'bundle.min.js',
format: 'esm',
plugins: [terser()]
}
]
};
```

#### plugins
Type: `Plugin | (Plugin | void)[]`

Expand All @@ -239,12 +268,16 @@ import commonjs from 'rollup-plugin-commonjs';
const isProduction = process.env.NODE_ENV === 'production';

export default (async () => ({
entry: 'main.js',
input: 'main.js',
plugins: [
resolve(),
commonjs(),
isProduction && (await import('rollup-plugin-terser')).terser()
]
],
output: {
file: 'bundle.js',
format: 'cjs'
}
}))();
```

Expand Down

0 comments on commit 17c14cd

Please sign in to comment.