Skip to content

Commit

Permalink
feat: support async plugins (#4671)
Browse files Browse the repository at this point in the history
* fix: nested plugin in options stage

* chore: remove unused

* feat: support async plugins

* fix: watch

* fix: watch

* fix: dts

* fix: await

* fix: watch cli

* test: add

* chore: improve types

* refactor: remove iife

* docs: add
  • Loading branch information
sxzz committed Oct 15, 2022
1 parent 53de5b0 commit 585de6d
Show file tree
Hide file tree
Showing 16 changed files with 92 additions and 72 deletions.
2 changes: 1 addition & 1 deletion cli/run/commandPlugins.ts
Expand Up @@ -23,7 +23,7 @@ export async function addPluginsFromCommandOption(
inputOptions: InputOptionsWithPlugins
): Promise<void> {
if (commandPlugin) {
const plugins: any[] = normalizePluginOption(commandPlugin as any);
const plugins = await normalizePluginOption(commandPlugin);
for (const plugin of plugins) {
if (/[={}]/.test(plugin)) {
// -p plugin=value
Expand Down
2 changes: 1 addition & 1 deletion cli/run/loadConfigFile.ts
Expand Up @@ -31,7 +31,7 @@ export async function loadConfigFile(
try {
const normalizedConfigs: MergedRollupOptions[] = [];
for (const config of configs) {
const options = mergeOptions(config, commandOptions, warnings.add);
const options = await mergeOptions(config, commandOptions, warnings.add);
await addCommandPluginsToInputOptions(options, commandOptions);
normalizedConfigs.push(options);
}
Expand Down
2 changes: 1 addition & 1 deletion cli/run/loadConfigFromCommand.ts
Expand Up @@ -13,7 +13,7 @@ export default async function loadConfigFromCommand(command: Record<string, unkn
if (!command.input && (command.stdin || !process.stdin.isTTY)) {
command.input = stdinName;
}
const options = mergeOptions({ input: [] }, command, warnings.add);
const options = await mergeOptions({ input: [] }, command, warnings.add);
await addCommandPluginsToInputOptions(options, command);
return { options: [options], warnings };
}
10 changes: 3 additions & 7 deletions cli/run/watch-cli.ts
Expand Up @@ -71,15 +71,11 @@ export async function watch(command: Record<string, any>): Promise<void> {
await loadConfigFromFileAndTrack(configFile);
} else {
const { options, warnings } = await loadConfigFromCommand(command);
start(options, warnings);
await start(options, warnings);
}

function start(configs: MergedRollupOptions[], warnings: BatchWarnings): void {
try {
watcher = rollup.watch(configs as any);
} catch (error: any) {
return handleError(error);
}
async function start(configs: MergedRollupOptions[], warnings: BatchWarnings): Promise<void> {
watcher = rollup.watch(configs as any);

watcher.on('event', event => {
switch (event.code) {
Expand Down
8 changes: 4 additions & 4 deletions docs/999-big-list-of-options.md
Expand Up @@ -258,9 +258,9 @@ this.a.b.c = ...
#### output.plugins
Type: `OutputPlugin | (OutputPlugin | OutputPlugin[] | void)[]`
Type: `MaybeArray<MaybePromise<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. Nested plugins will be flatten.
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. Nested plugins will be flatten. Async plugin will be awaited and resolved.
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 [output generation hooks](guide/en/#output-generation-hooks) to find out which hooks can be used.
Expand Down Expand Up @@ -288,9 +288,9 @@ export default {
#### plugins
Type: `Plugin | (Plugin | Plugin[] | void)[]`
Type: `MaybeArray<MaybePromise<Plugin | void>>`
See [Using plugins](guide/en/#using-plugins) for more information on how to use plugins and [Plugins](guide/en/#plugin-development) on how to write your own (try it out, it's not as difficult as it may sound and very much extends what you can do with Rollup). 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. Nested plugins will be flatten.
See [Using plugins](guide/en/#using-plugins) for more information on how to use plugins and [Plugins](guide/en/#plugin-development) on how to write your own (try it out, it's not as difficult as it may sound and very much extends what you can do with Rollup). 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. Nested plugins will be flatten. Async plugins will be awaited and resolved.
```js
// rollup.config.js
Expand Down
25 changes: 15 additions & 10 deletions src/rollup/rollup.ts
Expand Up @@ -111,9 +111,9 @@ async function getInputOptions(
}
const rawPlugins = getSortedValidatedPlugins(
'options',
normalizePluginOption(rawInputOptions.plugins)
await normalizePluginOption(rawInputOptions.plugins)
);
const { options, unsetOptions } = normalizeInputOptions(
const { options, unsetOptions } = await normalizeInputOptions(
await rawPlugins.reduce(applyOptionHook(watchMode), Promise.resolve(rawInputOptions))
);
normalizePlugins(options.plugins, ANONYMOUS_PLUGIN_PREFIX);
Expand All @@ -138,7 +138,7 @@ function normalizePlugins(plugins: readonly Plugin[], anonymousPrefix: string):
}
}

function handleGenerateWrite(
async function handleGenerateWrite(
isWrite: boolean,
inputOptions: NormalizedInputOptions,
unsetInputOptions: ReadonlySet<string>,
Expand All @@ -149,7 +149,7 @@ function handleGenerateWrite(
options: outputOptions,
outputPluginDriver,
unsetOptions
} = getOutputOptionsAndPluginDriver(
} = await getOutputOptionsAndPluginDriver(
rawOutputOptions,
graph.pluginDriver,
inputOptions,
Expand All @@ -175,25 +175,30 @@ function handleGenerateWrite(
});
}

function getOutputOptionsAndPluginDriver(
async function getOutputOptionsAndPluginDriver(
rawOutputOptions: OutputOptions,
inputPluginDriver: PluginDriver,
inputOptions: NormalizedInputOptions,
unsetInputOptions: ReadonlySet<string>
): {
): Promise<{
options: NormalizedOutputOptions;
outputPluginDriver: PluginDriver;
unsetOptions: Set<string>;
} {
}> {
if (!rawOutputOptions) {
throw new Error('You must supply an options object');
}
const rawPlugins = normalizePluginOption(rawOutputOptions.plugins);
const rawPlugins = await normalizePluginOption(rawOutputOptions.plugins);
normalizePlugins(rawPlugins, ANONYMOUS_OUTPUT_PLUGIN_PREFIX);
const outputPluginDriver = inputPluginDriver.createOutputPluginDriver(rawPlugins);

return {
...getOutputOptions(inputOptions, unsetInputOptions, rawOutputOptions, outputPluginDriver),
...(await getOutputOptions(
inputOptions,
unsetInputOptions,
rawOutputOptions,
outputPluginDriver
)),
outputPluginDriver
};
}
Expand All @@ -203,7 +208,7 @@ function getOutputOptions(
unsetInputOptions: ReadonlySet<string>,
rawOutputOptions: OutputOptions,
outputPluginDriver: PluginDriver
): { options: NormalizedOutputOptions; unsetOptions: Set<string> } {
): Promise<{ options: NormalizedOutputOptions; unsetOptions: Set<string> }> {
return normalizeOutputOptions(
outputPluginDriver.hookReduceArg0Sync(
'outputOptions',
Expand Down
8 changes: 6 additions & 2 deletions src/rollup/types.d.ts
@@ -1,5 +1,9 @@
export const VERSION: string;

type FalsyValue = false | null | undefined;
type MaybeArray<T> = T | T[];
type MaybePromise<T> = T | Promise<T>;

export interface RollupError extends RollupLog {
name?: string;
stack?: string;
Expand Down Expand Up @@ -495,7 +499,7 @@ export type SourcemapPathTransformOption = (
sourcemapPath: string
) => string;

export type InputPluginOption = Plugin | null | false | undefined | InputPluginOption[];
export type InputPluginOption = MaybePromise<Plugin | FalsyValue | InputPluginOption[]>;

export interface InputOptions {
acorn?: Record<string, unknown>;
Expand Down Expand Up @@ -619,7 +623,7 @@ export type NormalizedAmdOptions = (

type AddonFunction = (chunk: RenderedChunk) => string | Promise<string>;

type OutputPluginOption = OutputPlugin | null | false | undefined | OutputPluginOption[];
type OutputPluginOption = MaybePromise<OutputPlugin | FalsyValue | OutputPluginOption[]>;

export interface OutputOptions {
amd?: AmdOptions;
Expand Down
6 changes: 6 additions & 0 deletions src/utils/asyncFlatten.ts
@@ -0,0 +1,6 @@
export async function asyncFlatten<T>(array: T[]): Promise<T[]> {
do {
array = (await Promise.all(array)).flat(Infinity) as any;
} while (array.some((v: any) => v?.then));
return array;
}
26 changes: 13 additions & 13 deletions src/utils/options/mergeOptions.ts
@@ -1,10 +1,8 @@
import type {
ExternalOption,
InputOptions,
InputPluginOption,
MergedRollupOptions,
OutputOptions,
OutputPluginOption,
RollupCache,
RollupOptions,
WarningHandler,
Expand Down Expand Up @@ -41,21 +39,23 @@ export const commandAliases: { [key: string]: string } = {

const EMPTY_COMMAND_OPTIONS = { external: [], globals: undefined };

export function mergeOptions(
export async function mergeOptions(
config: RollupOptions,
rawCommandOptions: GenericConfigObject = EMPTY_COMMAND_OPTIONS,
defaultOnWarnHandler: WarningHandler = defaultOnWarn
): MergedRollupOptions {
): Promise<MergedRollupOptions> {
const command = getCommandOptions(rawCommandOptions);
const inputOptions = mergeInputOptions(config, command, defaultOnWarnHandler);
const inputOptions = await mergeInputOptions(config, command, defaultOnWarnHandler);
const warn = inputOptions.onwarn as WarningHandler;
if (command.output) {
Object.assign(command, command.output);
}
const outputOptionsArray = ensureArray(config.output);
if (outputOptionsArray.length === 0) outputOptionsArray.push({});
const outputOptions = outputOptionsArray.map(singleOutputOptions =>
mergeOutputOptions(singleOutputOptions, command, warn)
const outputOptions = await Promise.all(
outputOptionsArray.map(singleOutputOptions =>
mergeOutputOptions(singleOutputOptions, command, warn)
)
);

warnUnknownOptions(
Expand Down Expand Up @@ -108,11 +108,11 @@ type CompleteInputOptions<U extends keyof InputOptions> = {
[K in U]: InputOptions[K];
};

function mergeInputOptions(
async function mergeInputOptions(
config: InputOptions,
overrides: CommandConfigObject,
defaultOnWarnHandler: WarningHandler
): InputOptions {
): Promise<InputOptions> {
const getOption = (name: keyof InputOptions): any => overrides[name] ?? config[name];
const inputOptions: CompleteInputOptions<keyof InputOptions> = {
acorn: getOption('acorn'),
Expand All @@ -133,7 +133,7 @@ function mergeInputOptions(
moduleContext: getOption('moduleContext'),
onwarn: getOnWarn(config, defaultOnWarnHandler),
perf: getOption('perf'),
plugins: normalizePluginOption(config.plugins as InputPluginOption),
plugins: await normalizePluginOption(config.plugins),
preserveEntrySignatures: getOption('preserveEntrySignatures'),
preserveModules: getOption('preserveModules'),
preserveSymlinks: getOption('preserveSymlinks'),
Expand Down Expand Up @@ -218,11 +218,11 @@ type CompleteOutputOptions<U extends keyof OutputOptions> = {
[K in U]: OutputOptions[K];
};

function mergeOutputOptions(
async function mergeOutputOptions(
config: OutputOptions,
overrides: OutputOptions,
warn: WarningHandler
): OutputOptions {
): Promise<OutputOptions> {
const getOption = (name: keyof OutputOptions): any => overrides[name] ?? config[name];
const outputOptions: CompleteOutputOptions<keyof OutputOptions> = {
amd: getObjectOption(config, overrides, 'amd'),
Expand Down Expand Up @@ -262,7 +262,7 @@ function mergeOutputOptions(
noConflict: getOption('noConflict'),
outro: getOption('outro'),
paths: getOption('paths'),
plugins: normalizePluginOption(config.plugins as OutputPluginOption),
plugins: await normalizePluginOption(config.plugins),
preferConst: getOption('preferConst'),
preserveModules: getOption('preserveModules'),
preserveModulesRoot: getOption('preserveModulesRoot'),
Expand Down
6 changes: 3 additions & 3 deletions src/utils/options/normalizeInputOptions.ts
Expand Up @@ -26,10 +26,10 @@ export interface CommandConfigObject {
globals: { [id: string]: string } | undefined;
}

export function normalizeInputOptions(config: InputOptions): {
export async function normalizeInputOptions(config: InputOptions): Promise<{
options: NormalizedInputOptions;
unsetOptions: Set<string>;
} {
}> {
// These are options that may trigger special warnings or behaviour later
// if the user did not select an explicit value
const unsetOptions = new Set<string>();
Expand All @@ -54,7 +54,7 @@ export function normalizeInputOptions(config: InputOptions): {
moduleContext: getModuleContext(config, context),
onwarn,
perf: config.perf || false,
plugins: normalizePluginOption(config.plugins),
plugins: await normalizePluginOption(config.plugins),
preserveEntrySignatures: config.preserveEntrySignatures ?? 'exports-only',
preserveModules: getPreserveModules(config, onwarn, strictDeprecations),
preserveSymlinks: config.preserveSymlinks || false,
Expand Down
6 changes: 3 additions & 3 deletions src/utils/options/normalizeOutputOptions.ts
Expand Up @@ -22,11 +22,11 @@ import {
warnUnknownOptions
} from './options';

export function normalizeOutputOptions(
export async function normalizeOutputOptions(
config: OutputOptions,
inputOptions: NormalizedInputOptions,
unsetInputOptions: ReadonlySet<string>
): { options: NormalizedOutputOptions; unsetOptions: Set<string> } {
): Promise<{ options: NormalizedOutputOptions; unsetOptions: Set<string> }> {
// These are options that may trigger special warnings or behaviour later
// if the user did not select an explicit value
const unsetOptions = new Set(unsetInputOptions);
Expand Down Expand Up @@ -72,7 +72,7 @@ export function normalizeOutputOptions(
noConflict: config.noConflict || false,
outro: getAddon(config, 'outro'),
paths: config.paths || {},
plugins: normalizePluginOption(config.plugins),
plugins: await normalizePluginOption(config.plugins),
preferConst,
preserveModules,
preserveModulesRoot: getPreserveModulesRoot(config),
Expand Down
8 changes: 5 additions & 3 deletions src/utils/options/options.ts
Expand Up @@ -10,6 +10,7 @@ import type {
Plugin,
WarningHandler
} from '../../rollup/types';
import { asyncFlatten } from '../asyncFlatten';
import { error, errorInvalidOption, errorUnknownOption } from '../error';
import { printQuotedStringList } from '../printStringList';

Expand Down Expand Up @@ -151,6 +152,7 @@ const getHashFromObjectOption = (optionName: string): string =>
optionName.split('.').join('').toLowerCase();

export const normalizePluginOption: {
(plugins: InputPluginOption): Plugin[];
(plugins: OutputPluginOption): OutputPlugin[];
} = (plugins: any) => [plugins].flat(Infinity).filter(Boolean);
(plugins: InputPluginOption): Promise<Plugin[]>;
(plugins: OutputPluginOption): Promise<OutputPlugin[]>;
(plugins: unknown): Promise<any[]>;
} = async (plugins: any) => (await asyncFlatten([plugins])).filter(Boolean);
29 changes: 19 additions & 10 deletions src/watch/watch-proxy.ts
@@ -1,15 +1,25 @@
import type { RollupWatcher } from '../rollup/types';
import { handleError } from '../../cli/logging';
import type { MaybeArray, RollupOptions, RollupWatcher } from '../rollup/types';
import { ensureArray } from '../utils/ensureArray';
import { error, errorInvalidOption } from '../utils/error';
import type { GenericConfigObject } from '../utils/options/options';
import { mergeOptions } from '../utils/options/mergeOptions';
import { WatchEmitter } from './WatchEmitter';
import { loadFsEvents } from './fsevents-importer';

export default function watch(configs: GenericConfigObject[] | GenericConfigObject): RollupWatcher {
export default function watch(configs: RollupOptions[] | RollupOptions): RollupWatcher {
const emitter = new WatchEmitter() as RollupWatcher;
const configArray = ensureArray(configs);
const watchConfigs = configArray.filter(config => config.watch !== false);
if (watchConfigs.length === 0) {

watchInternal(configs, emitter).catch(error => {
handleError(error);
});

return emitter;
}

async function watchInternal(configs: MaybeArray<RollupOptions>, emitter: RollupWatcher) {
const optionsList = await Promise.all(ensureArray(configs).map(config => mergeOptions(config)));
const watchOptionsList = optionsList.filter(config => config.watch !== false);
if (watchOptionsList.length === 0) {
return error(
errorInvalidOption(
'watch',
Expand All @@ -18,8 +28,7 @@ export default function watch(configs: GenericConfigObject[] | GenericConfigObje
)
);
}
loadFsEvents()
.then(() => import('./watch'))
.then(({ Watcher }) => new Watcher(watchConfigs, emitter));
return emitter;
await loadFsEvents();
const { Watcher } = await import('./watch');
new Watcher(watchOptionsList, emitter);
}

0 comments on commit 585de6d

Please sign in to comment.