diff --git a/cli/run/commandPlugins.ts b/cli/run/commandPlugins.ts index 4fcc70f4f26..27912931670 100644 --- a/cli/run/commandPlugins.ts +++ b/cli/run/commandPlugins.ts @@ -1,28 +1,29 @@ import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; -import type { InputOptions } from '../../src/rollup/types'; +import type { InputOptionsWithPlugins } from '../../src/rollup/types'; +import { normalizePluginOption } from '../../src/utils/options/options'; import { stdinPlugin } from './stdin'; import { waitForInputPlugin } from './waitForInput'; export async function addCommandPluginsToInputOptions( - inputOptions: InputOptions, + inputOptions: InputOptionsWithPlugins, command: Record ): Promise { if (command.stdin !== false) { - inputOptions.plugins!.push(stdinPlugin(command.stdin)); + inputOptions.plugins.push(stdinPlugin(command.stdin)); } if (command.waitForBundleInput === true) { - inputOptions.plugins!.push(waitForInputPlugin()); + inputOptions.plugins.push(waitForInputPlugin()); } await addPluginsFromCommandOption(command.plugin, inputOptions); } export async function addPluginsFromCommandOption( commandPlugin: unknown, - inputOptions: InputOptions + inputOptions: InputOptionsWithPlugins ): Promise { if (commandPlugin) { - const plugins = Array.isArray(commandPlugin) ? commandPlugin : [commandPlugin]; + const plugins: any[] = normalizePluginOption(commandPlugin as any); for (const plugin of plugins) { if (/[={}]/.test(plugin)) { // -p plugin=value @@ -40,7 +41,7 @@ export async function addPluginsFromCommandOption( } async function loadAndRegisterPlugin( - inputOptions: InputOptions, + inputOptions: InputOptionsWithPlugins, pluginText: string ): Promise { let plugin: any = null; @@ -96,9 +97,7 @@ async function loadAndRegisterPlugin( )}" for Rollup to recognize it.` ); } - inputOptions.plugins!.push( - typeof plugin === 'function' ? plugin.call(plugin, pluginArg) : plugin - ); + inputOptions.plugins.push(typeof plugin === 'function' ? plugin.call(plugin, pluginArg) : plugin); } function getCamelizedPluginBaseName(pluginText: string): string { diff --git a/docs/999-big-list-of-options.md b/docs/999-big-list-of-options.md index f3535b2afb8..a3479ee4e8f 100755 --- a/docs/999-big-list-of-options.md +++ b/docs/999-big-list-of-options.md @@ -258,9 +258,9 @@ this.a.b.c = ... #### output.plugins -Type: `OutputPlugin | (OutputPlugin | void)[]` +Type: `OutputPlugin | (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. +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. 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. @@ -288,9 +288,9 @@ export default { #### plugins -Type: `Plugin | (Plugin | void)[]` +Type: `Plugin | (Plugin | 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. +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. ```js // rollup.config.js diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index 18dcf4311a4..ed7b60cb45c 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -492,6 +492,8 @@ export type SourcemapPathTransformOption = ( sourcemapPath: string ) => string; +export type InputPluginOption = Plugin | null | false | undefined | InputPluginOption[]; + export interface InputOptions { acorn?: Record; acornInjectPlugins?: (() => unknown)[] | (() => unknown); @@ -511,7 +513,7 @@ export interface InputOptions { moduleContext?: ((id: string) => string | null | void) | { [id: string]: string }; onwarn?: WarningHandlerWithDefault; perf?: boolean; - plugins?: (Plugin | null | false | undefined)[]; + plugins?: InputPluginOption; preserveEntrySignatures?: PreserveEntrySignaturesOption; /** @deprecated Use the "preserveModules" output option instead. */ preserveModules?: boolean; @@ -522,6 +524,10 @@ export interface InputOptions { watch?: WatcherOptions | false; } +export interface InputOptionsWithPlugins extends InputOptions { + plugins: Plugin[]; +} + export interface NormalizedInputOptions { acorn: Record; acornInjectPlugins: (() => unknown)[]; @@ -610,6 +616,8 @@ export type NormalizedAmdOptions = ( type AddonFunction = (chunk: RenderedChunk) => string | Promise; +type OutputPluginOption = OutputPlugin | null | false | undefined | OutputPluginOption[]; + export interface OutputOptions { amd?: AmdOptions; assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string); @@ -647,7 +655,7 @@ export interface OutputOptions { noConflict?: boolean; outro?: string | AddonFunction; paths?: OptionsPaths; - plugins?: (OutputPlugin | null | false | undefined)[]; + plugins?: OutputPluginOption; /** @deprecated Use "generatedCode.constBindings" instead. */ preferConst?: boolean; preserveModules?: boolean; @@ -800,7 +808,7 @@ export interface RollupOptions extends InputOptions { output?: OutputOptions | OutputOptions[]; } -export interface MergedRollupOptions extends InputOptions { +export interface MergedRollupOptions extends InputOptionsWithPlugins { output: OutputOptions[]; } diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index bf3ba84824b..9380a9a148a 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -1,8 +1,10 @@ import type { ExternalOption, InputOptions, + InputPluginOption, MergedRollupOptions, OutputOptions, + OutputPluginOption, RollupCache, WarningHandler, WarningHandlerWithDefault @@ -13,6 +15,7 @@ import { defaultOnWarn, generatedCodePresets, type GenericConfigObject, + normalizePluginOption, objectifyOption, objectifyOptionWithPresets, treeshakePresets, @@ -126,7 +129,7 @@ function mergeInputOptions( moduleContext: getOption('moduleContext'), onwarn: getOnWarn(config, defaultOnWarnHandler), perf: getOption('perf'), - plugins: ensureArray(config.plugins) as Plugin[], + plugins: normalizePluginOption(config.plugins as InputPluginOption), preserveEntrySignatures: getOption('preserveEntrySignatures'), preserveModules: getOption('preserveModules'), preserveSymlinks: getOption('preserveSymlinks'), @@ -261,7 +264,7 @@ function mergeOutputOptions( noConflict: getOption('noConflict'), outro: getOption('outro'), paths: getOption('paths'), - plugins: ensureArray(config.plugins) as Plugin[], + plugins: normalizePluginOption(config.plugins as OutputPluginOption), preferConst: getOption('preferConst'), preserveModules: getOption('preserveModules'), preserveModulesRoot: getOption('preserveModulesRoot'), diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index 7bef62ab0c3..9d77a018ae4 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -16,6 +16,7 @@ import { defaultOnWarn, type GenericConfigObject, getOptionWithPreset, + normalizePluginOption, treeshakePresets, warnUnknownOptions } from './options'; @@ -54,7 +55,7 @@ export function normalizeInputOptions(config: InputOptions): { moduleContext: getModuleContext(config, context), onwarn, perf: config.perf || false, - plugins: ensureArray(config.plugins), + plugins: normalizePluginOption(config.plugins), preserveEntrySignatures: config.preserveEntrySignatures ?? 'exports-only', preserveModules: getPreserveModules(config, onwarn, strictDeprecations), preserveSymlinks: config.preserveSymlinks || false, diff --git a/src/utils/options/normalizeOutputOptions.ts b/src/utils/options/normalizeOutputOptions.ts index 6451375503f..dd562c2ba29 100644 --- a/src/utils/options/normalizeOutputOptions.ts +++ b/src/utils/options/normalizeOutputOptions.ts @@ -6,7 +6,6 @@ import type { OutputOptions, SourcemapPathTransformOption } from '../../rollup/types'; -import { ensureArray } from '../ensureArray'; import { errInvalidExportOptionValue, errInvalidOption, error, warnDeprecation } from '../error'; import { resolve } from '../path'; import { sanitizeFileName as defaultSanitizeFileName } from '../sanitizeFileName'; @@ -15,6 +14,7 @@ import { generatedCodePresets, type GenericConfigObject, getOptionWithPreset, + normalizePluginOption, warnUnknownOptions } from './options'; @@ -68,7 +68,7 @@ export function normalizeOutputOptions( noConflict: config.noConflict || false, outro: getAddon(config, 'outro'), paths: config.paths || {}, - plugins: ensureArray(config.plugins), + plugins: normalizePluginOption(config.plugins), preferConst, preserveModules, preserveModulesRoot: getPreserveModulesRoot(config), diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index 1349b5f549c..2b19273bea5 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -1,9 +1,13 @@ import type { InputOptions, + InputPluginOption, NormalizedGeneratedCodeOptions, NormalizedOutputOptions, NormalizedTreeshakingOptions, OutputOptions, + OutputPlugin, + OutputPluginOption, + Plugin, WarningHandler } from '../../rollup/types'; import { errInvalidOption, error, errUnknownOption } from '../error'; @@ -145,3 +149,8 @@ export const getOptionWithPreset = ( 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); diff --git a/src/utils/timers.ts b/src/utils/timers.ts index c5ec310fc1f..c951d1162c0 100644 --- a/src/utils/timers.ts +++ b/src/utils/timers.ts @@ -1,4 +1,9 @@ -import type { InputOptions, Plugin, PluginHooks, SerializedTimings } from '../rollup/types'; +import type { + NormalizedInputOptions, + Plugin, + PluginHooks, + SerializedTimings +} from '../rollup/types'; import performance from './performance'; import process from './process'; @@ -117,7 +122,7 @@ function getPluginWithTimers(plugin: any, index: number): Plugin { return plugin; } -export function initialiseTimers(inputOptions: InputOptions): void { +export function initialiseTimers(inputOptions: NormalizedInputOptions): void { if (inputOptions.perf) { timers = new Map(); timeStart = timeStartImpl; diff --git a/test/function/samples/nested-plugin/_config.js b/test/function/samples/nested-plugin/_config.js new file mode 100644 index 00000000000..6e62a4d6fda --- /dev/null +++ b/test/function/samples/nested-plugin/_config.js @@ -0,0 +1,22 @@ +const plugin = [ + { + name: 'nested-plugin-1', + transform(code) { + return code.replace('foo = 1', 'foo = 2'); + } + }, + { + name: 'nested-plugin-2', + transform(code) { + return code.replace('answer = 41', 'answer = 42'); + } + } +]; + +module.exports = { + description: 'works when nested plugin', + options: { + // eslint-disable-next-line no-sparse-arrays + plugins: [plugin, [undefined, [null]], ,] + } +}; diff --git a/test/function/samples/nested-plugin/main.js b/test/function/samples/nested-plugin/main.js new file mode 100644 index 00000000000..1eb0e2232b3 --- /dev/null +++ b/test/function/samples/nested-plugin/main.js @@ -0,0 +1,5 @@ +const foo = 1; +const answer = 41; + +assert.equal(foo, 2); +assert.equal(answer, 42);