Skip to content

feat: support multiple rules #20

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

Merged
merged 5 commits into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
}
},
"cSpell.words": ["Rsbuild"]
}
104 changes: 94 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,43 @@ type AssetsRetryHookContext = {
isAsyncChunk: boolean;
};

type AssetsRetryOptions = {
type RuntimeRetryOptions = {
type?: string[];
domain?: string[];
max?: number;
test?: string | ((url: string) => boolean);
test?: string | RegExp | ((url: string) => boolean);
crossOrigin?: boolean | 'anonymous' | 'use-credentials';
inlineScript?: boolean;
delay?: number | ((context: AssetsRetryHookContext) => number);
onRetry?: (context: AssetsRetryHookContext) => void;
onSuccess?: (context: AssetsRetryHookContext) => void;
onFail?: (context: AssetsRetryHookContext) => void;
};

type AssetsRetryOptions =
| ({
inlineScript?: boolean;
minify?: boolean;
} & RuntimeRetryOptions)
| {
inlineScript?: boolean;
minify?: boolean;
rules: RuntimeRetryOptions[];
};
```

- **Default:**

```ts
const defaultAssetsRetryOptions = {
max: 3,
type: ['script', 'link', 'img'],
domain: [],
max: 3,
test: '',
crossOrigin: false,
test: '',
delay: 0,
onRetry: () => {},
onSuccess: () => {},
onFail: () => {},
addQuery: false,
inlineScript: true,
minify: rsbuildConfig.mode === 'production',
};
```

Expand Down Expand Up @@ -242,7 +252,7 @@ When set to `true`, `retry=${times}` will be added to the query when requesting,

When you want to customize query, you can pass a function, for example:

- **Example:** All assets requested do not contain query:
- **Example 1:** All assets requested do not contain query:

```js
pluginAssetsRetry({
Expand All @@ -254,7 +264,7 @@ pluginAssetsRetry({
});
```

- **Example:** If there is a query in some of the requested assets, you can read it with `originalQuery`:
- **Example 2:** If there is a query in some of the requested assets, you can read it with `originalQuery`:

```js
pluginAssetsRetry({
Expand Down Expand Up @@ -325,6 +335,80 @@ pluginAssetsRetry({
});
```

### rules

- **Type:** `RuntimeRetryOptions[]`
- **Default:** `undefined`

Configure multiple retry rules with different options. Each rule will be evaluated in order, and the first matching rule will be used for retry logic. This is useful when you have different retry requirements for different types of assets or domains.

When using `rules`, the plugin will:

1. Check each rule in order by `test` `domain` `type`

2. If the rule is matched, the rule's configuration will be used to retry

3. If no rule is matched, the resource will not be retried

Each rule supports all the same options as the top-level configuration, including `type`, `domain`, `max`, `test`, `crossOrigin`, `delay`, `onRetry`, `onSuccess`, and `onFail`.

- **Example 1:** Different retry strategies for different CDNs:

```js
pluginAssetsRetry({
rules: [
{
// Rule for primary CDN
test: /cdn1\.example\.com/,
domain: ['cdn1.example.com', 'cdn1-backup.example.com'],
max: 3,
delay: 1000,
},
{
// Rule for secondary CDN with more retries
test: /cdn2\.example\.com/,
domain: ['cdn2.example.com', 'cdn2-backup.example.com'],
max: 5,
delay: 500,
},
{
// Default rule for other assets
domain: ['default.example.com', 'default-backup.example.com'],
max: 2,
},
],
});
```

- **Example 2:** Different retry strategies for different asset types:

```js
pluginAssetsRetry({
rules: [
{
// Critical JavaScript files get more retries
type: ['script'],
// Or test: /\.js$/,
max: 5,
delay: 1000,
onFail: ({ url }) => console.error(`Critical JS failed: ${url}`),
},
{
// CSS files get fewer retries
test: /\.css$/,
max: 2,
delay: 500,
},
{
// Images get minimal retries
test: /\.(png|jpg|gif|svg)$/,
max: 1,
delay: 0,
},
],
});
```

## Notes

When you use Assets Retry plugin, the Rsbuild injects some runtime code into the HTML and [Rspack Runtime](https://rspack.dev/misc/glossary#runtime), then serializes the Assets Retry plugin config, inserting it into the runtime code. Therefore, you need to be aware of the following:
Expand Down
104 changes: 94 additions & 10 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,43 @@ type AssetsRetryHookContext = {
isAsyncChunk: boolean;
};

type AssetsRetryOptions = {
type RuntimeRetryOptions = {
type?: string[];
domain?: string[];
max?: number;
test?: string | ((url: string) => boolean);
crossOrigin?: boolean | 'anonymous' | 'use-credentials';
inlineScript?: boolean;
delay?: number | ((context: AssetsRetryHookContext) => number);
onRetry?: (context: AssetsRetryHookContext) => void;
onSuccess?: (context: AssetsRetryHookContext) => void;
onFail?: (context: AssetsRetryHookContext) => void;
};

type AssetsRetryOptions =
| ({
inlineScript?: boolean;
minify?: boolean;
} & RuntimeRetryOptions)
| {
inlineScript?: boolean;
minify?: boolean;
rules: RuntimeRetryOptions[];
};
```

- **默认值:**

```ts
const defaultOptions = {
const defaultAssetsRetryOptions = {
max: 3,
type: ['script', 'link', 'img'],
domain: [],
max: 3,
test: '',
crossOrigin: false,
test: '',
delay: 0,
onRetry: () => {},
onSuccess: () => {},
onFail: () => {},
addQuery: false,
inlineScript: true,
minify: rsbuildConfig.mode === 'production',
};
```

Expand Down Expand Up @@ -240,7 +250,7 @@ type AddQuery =

当你想要自定义 query 时,可以传入一个函数,比如:

- **示例:** 请求的所有资源都不含 query:
- **示例 1** 请求的所有资源都不含 query:

```js
pluginAssetsRetry({
Expand All @@ -252,7 +262,7 @@ pluginAssetsRetry({
});
```

- **示例:** 当请求的某些资源中含有 query 时,可以使用 `originalQuery` 读取:
- **示例 2** 当请求的某些资源中含有 query 时,可以使用 `originalQuery` 读取:

```js
pluginAssetsRetry({
Expand Down Expand Up @@ -323,6 +333,80 @@ pluginAssetsRetry({
});
```

### rules

- **类型:** `RuntimeRetryOptions[]`
- **默认值:** `undefined`

配置多个重试规则,每个规则可以有不同的选项。规则会按顺序进行评估,第一个匹配的规则将用于重试逻辑。这在你对不同类型的资源或域名有不同的重试需求时非常有用。

使用 `rules` 时,插件会:

1. 按顺序通过 `test` `domain` `type` 检查每个规则

2. 如果匹配到规则,会使用规则的配置进行重试

3. 如果没有匹配到规则,则不会重试该资源

每个规则支持与顶层配置相同的所有选项,包括 `type``domain``test``max``crossOrigin``delay``onRetry``onSuccess``onFail`

- **示例 1:** 不同 CDN 的不同重试策略:

```js
pluginAssetsRetry({
rules: [
{
// 主 CDN 的规则
test: /cdn1\.example\.com/,
domain: ['cdn1.example.com', 'cdn1-backup.example.com'],
max: 3,
delay: 1000,
},
{
// 次要 CDN 的规则,更多重试次数
test: /cdn2\.example\.com/,
domain: ['cdn2.example.com', 'cdn2-backup.example.com'],
max: 5,
delay: 500,
},
{
// 其他资源的默认规则
domain: ['default.example.com', 'default-backup.example.com'],
max: 2,
},
],
});
```

- **示例 2:** 不同资源类型的不同重试策略:

```js
pluginAssetsRetry({
rules: [
{
// 关键 JavaScript 文件获得更多重试次数
test: /\.js$/,
// 或者 type: ['script'],
max: 5,
delay: 1000,
onFail: ({ url }) => console.error(`关键 JS 失败: ${url}`),
},
{
// CSS 文件获得较少的重试次数
test: /\.css$/,
max: 2,
delay: 500,
},
{
// 图片获得最少的重试次数
test: /\.(png|jpg|gif|svg)$/,
max: 1,
delay: 0,
},
],
});
```

## 注意事项

当你使用 Assets Retry 插件时,Rsbuild 会分别向 HTML 和 [Rspack Runtime](https://rspack.dev/zh/misc/glossary#runtime) 中注入运行时代码,并将 Assets Retry 插件配置的内容序列化后插入到这些代码中,因此你需要注意:
Expand Down
56 changes: 55 additions & 1 deletion playground/rsbuild.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,60 @@ import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginAssetsRetry } from '../dist';

export function createBlockMiddleware({ urlPrefix, blockNum, onBlock }) {
let counter = 0;

return (req, res, next) => {
if (req.url?.startsWith(urlPrefix)) {
counter++;
// if blockNum is 3, 1 2 3 would be blocked, 4 would be passed
const isBlocked = counter % (blockNum + 1) !== 0;

if (isBlocked && onBlock) {
onBlock({
url: req.url,
count: counter,
timestamp: Date.now(),
});
}
if (isBlocked) {
res.statusCode = 404;
}
res.setHeader('block-async', counter);
}
next();
};
}

export default defineConfig({
plugins: [pluginAssetsRetry(), pluginReact()],
dev: {
setupMiddlewares: [
(middlewares) => {
middlewares.unshift(
createBlockMiddleware({
urlPrefix: '/static/js/async/src_AsyncCompTest_tsx.js',
blockNum: 3,
onBlock: ({ url, count }) => {
console.info(`Blocked ${url} for the ${count}th time`);
},
}),
);
},
],
},
plugins: [
pluginAssetsRetry({
minify: true,
onRetry(context) {
console.info('onRetry', context);
},
onSuccess(context) {
console.info('onSuccess', context);
},
onFail(context) {
console.info('onFail', context);
},
}),
pluginReact(),
],
});
4 changes: 2 additions & 2 deletions src/AsyncChunkRetryPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ class AsyncChunkRetryPlugin implements Rspack.RspackPluginInstance {
readonly name = 'ASYNC_CHUNK_RETRY_PLUGIN';
readonly isRspack: boolean;
readonly minify: boolean;
readonly runtimeOptions: NormalizedRuntimeRetryOptions;
readonly runtimeOptions: NormalizedRuntimeRetryOptions[];

constructor(
options: NormalizedRuntimeRetryOptions,
options: NormalizedRuntimeRetryOptions[],
isRspack: boolean,
minify: boolean,
) {
Expand Down
Loading