diff --git a/docs/config/shared-options.md b/docs/config/shared-options.md index 991f347c02be13..d8097aa6011f9a 100644 --- a/docs/config/shared-options.md +++ b/docs/config/shared-options.md @@ -77,9 +77,9 @@ const obj = { ## plugins -- **Type:** `(Plugin | Plugin[])[]` +- **Type:** `(Plugin | Plugin[] | Promise)[]` -Array of plugins to use. Falsy plugins are ignored and arrays of plugins are flattened. See [Plugin API](/guide/api-plugin) for more details on Vite plugins. +Array of plugins to use. Falsy plugins are ignored and arrays of plugins are flattened. If a promise is returned, it would be resolved before running. See [Plugin API](/guide/api-plugin) for more details on Vite plugins. ## publicDir diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 5a6ea4d55de30c..889cbf8386538b 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'vitest' import { + asyncFlatten, getHash, getPotentialTsSrcPaths, injectQuery, @@ -127,3 +128,45 @@ describe('getHash', () => { expect(hash).toMatch(/^[\da-f]{8}$/) }) }) + +describe('asyncFlatten', () => { + test('plain array', async () => { + const arr = await asyncFlatten([1, 2, 3]) + expect(arr).toEqual([1, 2, 3]) + }) + + test('nested array', async () => { + const arr = await asyncFlatten([1, 2, 3, [4, 5, 6]]) + expect(arr).toEqual([1, 2, 3, 4, 5, 6]) + }) + + test('nested falsy array', async () => { + const arr = await asyncFlatten([1, 2, false, [4, null, undefined]]) + expect(arr).toEqual([1, 2, false, 4, null, undefined]) + }) + + test('plain promise array', async () => { + const arr = await asyncFlatten([1, 2, Promise.resolve(3)]) + expect(arr).toEqual([1, 2, 3]) + }) + + test('nested promise array', async () => { + const arr = await asyncFlatten([ + 1, + 2, + Promise.resolve(3), + Promise.resolve([4, 5, 6]) + ]) + expect(arr).toEqual([1, 2, 3, 4, 5, 6]) + }) + + test('2x nested promise array', async () => { + const arr = await asyncFlatten([ + 1, + 2, + Promise.resolve(3), + Promise.resolve([4, 5, Promise.resolve(6), Promise.resolve([7, 8, 9])]) + ]) + expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]) + }) +}) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index b2eb774861aa7a..d8f04aef0610fe 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -17,6 +17,7 @@ import type { PreviewOptions, ResolvedPreviewOptions } from './preview' import { resolvePreviewOptions } from './preview' import type { CSSOptions } from './plugins/css' import { + asyncFlatten, createDebugger, createFilter, dynamicImport, @@ -75,7 +76,13 @@ export function defineConfig(config: UserConfigExport): UserConfigExport { return config } -export type PluginOption = Plugin | false | null | undefined | PluginOption[] +export type PluginOption = + | Plugin + | false + | null + | undefined + | PluginOption[] + | Promise export interface UserConfig { /** @@ -370,7 +377,9 @@ export async function resolveConfig( configEnv.mode = mode // resolve plugins - const rawUserPlugins = (config.plugins || []).flat(Infinity).filter((p) => { + const rawUserPlugins = ( + (await asyncFlatten(config.plugins || [])) as Plugin[] + ).filter((p) => { if (!p) { return false } else if (!p.apply) { @@ -380,7 +389,7 @@ export async function resolveConfig( } else { return p.apply === command } - }) as Plugin[] + }) const [prePlugins, normalPlugins, postPlugins] = sortUserPlugins(rawUserPlugins) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index c3c58696921dc9..02611dcfffae01 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1015,6 +1015,13 @@ export function transformResult( } } +export async function asyncFlatten(arr: T[]): Promise { + do { + arr = (await Promise.all(arr)).flat(Infinity) as any + } while (arr.some((v: any) => v?.then)) + return arr +} + // strip UTF-8 BOM export function stripBomTag(content: string): string { if (content.charCodeAt(0) === 0xfeff) {