Skip to content

Commit

Permalink
feat: support declare plugin hooks order (#1472)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan committed Jan 31, 2024
1 parent e0e178c commit 7b0c052
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 28 deletions.
60 changes: 59 additions & 1 deletion packages/document/docs/en/plugins/dev/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Called only when running the `rsbuild preview` command or the `rsbuild.preview()

---

## Execution Order
## Hooks Order

### Dev Hooks

Expand Down Expand Up @@ -77,6 +77,64 @@ When executing the `rsbuild preview` command or `rsbuild.preview()` method, Rsbu

---

## Callback Order

### Default Behavior

If multiple plugins register the same hook, the callback functions of the hook will execute in the order in which they were registered.

In the following example, the console will output `'1'` and `'2'` in sequence:

```ts
const plugin1 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('1'));
},
});

const plugin2 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('2'));
},
});

rsbuild.addPlugins([plugin1, plugin2]);
```

### `order` Field

When registering a hook, you can declare the order of hook through the `order` field.

```ts
type HookDescriptor<T extends (...args: any[]) => any> = {
handler: T;
order: 'pre' | 'post' | 'default';
};
```

In the following example, the console will sequentially output `'2'` and `'1'`, because `order` was set to `pre` when plugin2 called `modifyRsbuildConfig`.

```ts
const plugin1 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('1'));
},
});

const plugin2 = () => ({
setup: (api) => {
api.modifyRsbuildConfig({
handler: () => console.log('2'),
order: 'pre',
});
},
});

rsbuild.addPlugins([plugin1, plugin2]);
```

---

## Common Hooks

### modifyRsbuildConfig
Expand Down
60 changes: 59 additions & 1 deletion packages/document/docs/zh/plugins/dev/hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

---

## 执行顺序
## Hooks 顺序

### Dev Hooks

Expand Down Expand Up @@ -77,6 +77,64 @@

---

## 回调函数顺序

### 默认行为

如果多个插件注册了相同的 hook,那么 hook 的回调函数会按照注册时的顺序执行。

在以下例子中,控制台会依次输出 `'1'``'2'`

```ts
const plugin1 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('1'));
},
});

const plugin2 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('2'));
},
});

rsbuild.addPlugins([plugin1, plugin2]);
```

### order 字段

在注册 hook 时,可以通过 `order` 字段来声明 hook 的顺序。

```ts
type HookDescriptor<T extends (...args: any[]) => any> = {
handler: T;
order: 'pre' | 'post' | 'default';
};
```

在以下例子中,控制台会依次输出 `'2'``'1'`,因为 plugin2 在调用 modifyRsbuildConfig 时设置了 order 为 `pre`

```ts
const plugin1 = () => ({
setup: (api) => {
api.modifyRsbuildConfig(() => console.log('1'));
},
});

const plugin2 = () => ({
setup: (api) => {
api.modifyRsbuildConfig({
handler: () => console.log('2'),
order: 'pre',
});
},
});

rsbuild.addPlugins([plugin1, plugin2]);
```

---

## Common Hooks

### modifyRsbuildConfig
Expand Down
32 changes: 26 additions & 6 deletions packages/shared/src/createHook.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { isFunction } from './utils';

type HookOrder = 'pre' | 'post' | 'default';

export type HookDescriptor<T extends (...args: any[]) => any> = {
handler: T;
order: HookOrder;
};

export type AsyncHook<Callback extends (...args: any[]) => any> = {
tap: (cb: Callback) => void;
tap: (cb: Callback | HookDescriptor<Callback>) => void;
call: (...args: Parameters<Callback>) => Promise<Parameters<Callback>>;
};

export function createAsyncHook<
Callback extends (...args: any[]) => any,
>(): AsyncHook<Callback> {
const callbacks: Callback[] = [];
const preGroup: Callback[] = [];
const postGroup: Callback[] = [];
const defaultGroup: Callback[] = [];

const tap = (cb: Callback) => {
callbacks.push(cb);
const tap = (cb: Callback | HookDescriptor<Callback>) => {
if (isFunction(cb)) {
defaultGroup.push(cb);
} else if (cb.order === 'pre') {
preGroup.push(cb.handler);
} else if (cb.order === 'post') {
postGroup.push(cb.handler);
} else {
defaultGroup.push(cb.handler);
}
};

const call = async (...args: Parameters<Callback>) => {
const params = args.slice(0) as Parameters<Callback>;
const callbacks = [...preGroup, ...defaultGroup, ...postGroup];

for (const cb of callbacks) {
const result = await cb(...params);
for (const callback of callbacks) {
const result = await callback(...params);

if (result !== undefined) {
params[0] = result;
Expand Down
44 changes: 24 additions & 20 deletions packages/shared/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
Configuration as WebpackConfig,
} from 'webpack';
import type { ChainIdentifier } from '../chain';
import type { HookDescriptor } from '../createHook';

export type ModifyRspackConfigFn = (
config: RspackConfig,
Expand Down Expand Up @@ -143,23 +144,36 @@ export type GetRsbuildConfig = {
(type: 'normalized'): NormalizedConfig;
};

type PluginHook<T extends (...args: any[]) => any> = (
options: T | HookDescriptor<T>,
) => void;

/**
* Define a generic Rsbuild plugin API that provider can extend as needed.
*/
export type RsbuildPluginAPI = {
context: Readonly<RsbuildContext>;
isPluginExists: PluginStore['isPluginExists'];

onExit: (fn: OnExitFn) => void;
onAfterBuild: (fn: OnAfterBuildFn) => void;
onBeforeBuild: (fn: OnBeforeBuildFn) => void;
onDevCompileDone: (fn: OnDevCompileDoneFn) => void;
onAfterStartDevServer: (fn: OnAfterStartDevServerFn) => void;
onBeforeStartDevServer: (fn: OnBeforeStartDevServerFn) => void;
onAfterStartProdServer: (fn: OnAfterStartProdServerFn) => void;
onBeforeStartProdServer: (fn: OnBeforeStartProdServerFn) => void;
onAfterCreateCompiler: (fn: OnAfterCreateCompilerFn) => void;
onBeforeCreateCompiler: (fn: OnBeforeCreateCompilerFn) => void;
onExit: PluginHook<OnExitFn>;
onAfterBuild: PluginHook<OnAfterBuildFn>;
onBeforeBuild: PluginHook<OnBeforeBuildFn>;
onDevCompileDone: PluginHook<OnDevCompileDoneFn>;
onAfterStartDevServer: PluginHook<OnAfterStartDevServerFn>;
onBeforeStartDevServer: PluginHook<OnBeforeStartDevServerFn>;
onAfterStartProdServer: PluginHook<OnAfterStartProdServerFn>;
onBeforeStartProdServer: PluginHook<OnBeforeStartProdServerFn>;
onAfterCreateCompiler: PluginHook<OnAfterCreateCompilerFn>;
onBeforeCreateCompiler: PluginHook<OnBeforeCreateCompilerFn>;

modifyRsbuildConfig: PluginHook<ModifyRsbuildConfigFn>;
modifyBundlerChain: PluginHook<ModifyBundlerChainFn>;
/** Only works when bundler is Rspack */
modifyRspackConfig: PluginHook<ModifyRspackConfigFn>;
/** Only works when bundler is Webpack */
modifyWebpackChain: PluginHook<ModifyWebpackChainFn>;
/** Only works when bundler is Webpack */
modifyWebpackConfig: PluginHook<ModifyWebpackConfigFn>;

/**
* Get the relative paths of generated HTML files.
Expand All @@ -168,14 +182,4 @@ export type RsbuildPluginAPI = {
getHTMLPaths: () => Record<string, string>;
getRsbuildConfig: GetRsbuildConfig;
getNormalizedConfig: () => NormalizedConfig;

modifyRsbuildConfig: (fn: ModifyRsbuildConfigFn) => void;
modifyBundlerChain: (fn: ModifyBundlerChainFn) => void;

/** Only works when bundler is Rspack */
modifyRspackConfig: (fn: ModifyRspackConfigFn) => void;
/** Only works when bundler is Webpack */
modifyWebpackChain: (fn: ModifyWebpackChainFn) => void;
/** Only works when bundler is Webpack */
modifyWebpackConfig: (fn: ModifyWebpackConfigFn) => void;
};
49 changes: 49 additions & 0 deletions packages/shared/tests/createAsyncHook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,53 @@ describe('createAsyncHook', () => {
const result = await myHook.call(1);
expect(result).toEqual([3]);
});

test('should allow to specify hook order', async () => {
const myHook = createAsyncHook();
const result: number[] = [];

myHook.tap(() => {
result.push(1);
});
myHook.tap({
handler: () => {
result.push(2);
},
order: 'post',
});
myHook.tap({
handler: () => {
result.push(3);
},
order: 'post',
});
myHook.tap({
handler: () => {
result.push(4);
},
order: 'default',
});
myHook.tap({
handler: () => {
result.push(5);
},
order: 'pre',
});
myHook.tap({
handler: () => {
result.push(6);
},
order: 'pre',
});
myHook.tap({
handler: () => {
result.push(7);
},
order: 'default',
});

await myHook.call();

expect(result).toEqual([5, 6, 1, 4, 7, 2, 3]);
});
});

0 comments on commit 7b0c052

Please sign in to comment.