Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support nested plugins #3348

Merged
merged 9 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/core/src/createRsbuild.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isPromise } from 'node:util/types';
import { createContext } from './createContext';
import { getNodeEnv, pick, setNodeEnv } from './helpers';
import { initPluginAPI } from './initPlugins';
Expand All @@ -9,11 +10,14 @@ import type {
Build,
CreateDevServer,
CreateRsbuildOptions,
Falsy,
InternalContext,
PluginManager,
PreviewServerOptions,
ResolvedCreateRsbuildOptions,
RsbuildInstance,
RsbuildPlugin,
RsbuildPlugins,
RsbuildProvider,
StartDevServer,
} from './types';
Expand Down Expand Up @@ -199,8 +203,18 @@ export async function createRsbuild(
]),
};

const getFlattenedPlugins = async (pluginOptions: RsbuildPlugins) => {
let plugins = pluginOptions;
do {
// @ts-expect-error Depth is determined by user configuration
9aoy marked this conversation as resolved.
Show resolved Hide resolved
plugins = (await Promise.all(plugins)).flat(Number.POSITIVE_INFINITY);
} while (plugins.some((v: any) => isPromise(v)));

return plugins as Array<RsbuildPlugin | Falsy>;
};

if (rsbuildConfig.plugins) {
const plugins = await Promise.all(rsbuildConfig.plugins);
const plugins = await getFlattenedPlugins(rsbuildConfig.plugins);
rsbuild.addPlugins(plugins);
}

Expand All @@ -212,7 +226,7 @@ export async function createRsbuild(
!rsbuildOptions.environment ||
rsbuildOptions.environment.includes(name);
if (config.plugins && isEnvironmentEnabled) {
const plugins = await Promise.all(config.plugins);
const plugins = await getFlattenedPlugins(config.plugins);
rsbuild.addPlugins(plugins, {
environment: name,
});
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ type LooseRsbuildPlugin = Omit<RsbuildPlugin, 'setup'> & {
export type RsbuildPlugins = (
| LooseRsbuildPlugin
| Falsy
| Promise<LooseRsbuildPlugin | Falsy>
| Promise<LooseRsbuildPlugin | Falsy | RsbuildPlugins>
| RsbuildPlugins
)[];

export type GetRsbuildConfig = {
Expand Down
41 changes: 41 additions & 0 deletions packages/core/tests/builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,44 @@ describe('should use rspack as default bundler', () => {
process.env.NODE_ENV = NODE_ENV;
});
});

describe('plugins', () => {
it('should apply nested plugins correctly', async () => {
function myPlugin() {
return [
{
name: 'plugin-foo',
setup() {},
},
{
name: 'plugin-bar',
setup() {},
},
];
}

const rsbuild = await createRsbuild({
rsbuildConfig: {
source: {
entry: {
index: './src/index.js',
},
},
plugins: [
myPlugin(),
Promise.resolve([
{
name: 'plugin-zoo',
setup() {},
},
]),
],
},
});

expect(rsbuild.isPluginExists('plugin-foo')).toBeTruthy();
expect(rsbuild.isPluginExists('plugin-bar')).toBeTruthy();
expect(rsbuild.isPluginExists('plugin-zoo')).toBeTruthy();
expect(rsbuild.isPluginExists('plugin-404')).toBeFalsy();
});
});
14 changes: 13 additions & 1 deletion scripts/test-helper/src/rsbuild.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { isPromise } from 'node:util/types';
import type {
BundlerPluginInstance,
CreateRsbuildOptions,
RsbuildInstance,
RsbuildPlugin,
RsbuildPlugins,
Rspack,
} from '@rsbuild/core';
Expand Down Expand Up @@ -53,10 +55,20 @@ export async function createStubRsbuild({

const rsbuild = await createRsbuild(rsbuildOptions);

const getFlattenedPlugins = async (pluginOptions: RsbuildPlugins) => {
let plugins = pluginOptions;
do {
// @ts-expect-error Depth is determined by user configuration
plugins = (await Promise.all(plugins)).flat(Number.POSITIVE_INFINITY);
} while (plugins.some((v: any) => isPromise(v)));
9aoy marked this conversation as resolved.
Show resolved Hide resolved

return plugins as Array<RsbuildPlugin | false | null | undefined>;
};

if (plugins) {
// remove all builtin plugins
rsbuild.removePlugins(rsbuild.getPlugins().map((item) => item.name));
rsbuild.addPlugins(await Promise.all(plugins));
rsbuild.addPlugins(await getFlattenedPlugins(plugins));
}

const unwrapConfig = async (index = 0) => {
Expand Down
20 changes: 19 additions & 1 deletion website/docs/en/config/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Falsy = false | null | undefined;
type RsbuildPlugins = (
| RsbuildPlugin
| Falsy
| Promise<RsbuildPlugin | Falsy>
| Promise<RsbuildPlugin | Falsy | RsbuildPlugins>
| RsbuildPlugins
)[];
```

Expand Down Expand Up @@ -47,6 +48,23 @@ By default, plugins are executed in the order they appear in the `plugins` array

When a plugin internally uses fields that control the order, such as `pre` and `post`, the execution order is adjusted based on them. See [Pre Plugins](/plugins/dev/core#pre-pluginss) for more details.

## Nested Plugins

Rsbuild also supports adding nested plugins. You can pass in an array containing multiple plugins, similar to a plugin preset collection. This is particularly useful for implementing complex functionalities that require a combination of multiple plugins (such as framework integration).

```ts title="rsbuild.config.ts"
function myPlugin() {
return [
fooPlugin()
9aoy marked this conversation as resolved.
Show resolved Hide resolved
barPlugin()
];
}

export default {
plugins: [myPlugin()]
}
```

## Local Plugins

If your local code repository contains Rsbuild plugins, you can import them using relative paths.
Expand Down
20 changes: 19 additions & 1 deletion website/docs/zh/config/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ type Falsy = false | null | undefined;
type RsbuildPlugins = (
| RsbuildPlugin
| Falsy
| Promise<RsbuildPlugin | Falsy>
| Promise<RsbuildPlugin | Falsy | RsbuildPlugins>
| RsbuildPlugins
)[];
```

Expand Down Expand Up @@ -47,6 +48,23 @@ export default defineConfig({

当插件内部使用了控制顺序的相关字段,比如 `pre`、`post` 时,执行顺序会基于它们进行调整,详见 [前置插件](/plugins/dev/core#前置插件)。

## 嵌套插件

Rsbuild 还支持添加嵌套插件,你可以传入一个包含多个插件的数组,类似于一个插件预设集合,这对于实现需要多个插件组合的复杂功能(例如框架集成)很有帮助。

```ts title="rsbuild.config.ts"
function myPlugin() {
return [
fooPlugin()
9aoy marked this conversation as resolved.
Show resolved Hide resolved
barPlugin()
];
}

export default {
plugins: [myPlugin()]
}
```

## 本地插件

如果本地代码仓库中包含了一些 Rsbuild 插件,你可以直接通过相对路径引入。
Expand Down
Loading