Skip to content

Commit f970bed

Browse files
authored
feat: support multiple rules (#20)
* chore: update * chore: update * chore: adopt suggestions
1 parent bd2f34d commit f970bed

File tree

13 files changed

+843
-175
lines changed

13 files changed

+843
-175
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
},
1515
"[css]": {
1616
"editor.defaultFormatter": "biomejs.biome"
17-
}
17+
},
18+
"cSpell.words": ["Rsbuild"]
1819
}

README.md

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,43 @@ type AssetsRetryHookContext = {
5959
isAsyncChunk: boolean;
6060
};
6161

62-
type AssetsRetryOptions = {
62+
type RuntimeRetryOptions = {
6363
type?: string[];
6464
domain?: string[];
6565
max?: number;
66-
test?: string | ((url: string) => boolean);
66+
test?: string | RegExp | ((url: string) => boolean);
6767
crossOrigin?: boolean | 'anonymous' | 'use-credentials';
68-
inlineScript?: boolean;
6968
delay?: number | ((context: AssetsRetryHookContext) => number);
7069
onRetry?: (context: AssetsRetryHookContext) => void;
7170
onSuccess?: (context: AssetsRetryHookContext) => void;
7271
onFail?: (context: AssetsRetryHookContext) => void;
7372
};
73+
74+
type AssetsRetryOptions =
75+
| ({
76+
inlineScript?: boolean;
77+
minify?: boolean;
78+
} & RuntimeRetryOptions)
79+
| {
80+
inlineScript?: boolean;
81+
minify?: boolean;
82+
rules: RuntimeRetryOptions[];
83+
};
7484
```
7585

7686
- **Default:**
7787

7888
```ts
7989
const defaultAssetsRetryOptions = {
90+
max: 3,
8091
type: ['script', 'link', 'img'],
8192
domain: [],
82-
max: 3,
83-
test: '',
8493
crossOrigin: false,
94+
test: '',
8595
delay: 0,
86-
onRetry: () => {},
87-
onSuccess: () => {},
88-
onFail: () => {},
96+
addQuery: false,
97+
inlineScript: true,
98+
minify: rsbuildConfig.mode === 'production',
8999
};
90100
```
91101

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

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

245-
- **Example:** All assets requested do not contain query:
255+
- **Example 1:** All assets requested do not contain query:
246256

247257
```js
248258
pluginAssetsRetry({
@@ -254,7 +264,7 @@ pluginAssetsRetry({
254264
});
255265
```
256266

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

259269
```js
260270
pluginAssetsRetry({
@@ -325,6 +335,80 @@ pluginAssetsRetry({
325335
});
326336
```
327337

338+
### rules
339+
340+
- **Type:** `RuntimeRetryOptions[]`
341+
- **Default:** `undefined`
342+
343+
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.
344+
345+
When using `rules`, the plugin will:
346+
347+
1. Check each rule in order by `test` `domain` `type`
348+
349+
2. If the rule is matched, the rule's configuration will be used to retry
350+
351+
3. If no rule is matched, the resource will not be retried
352+
353+
Each rule supports all the same options as the top-level configuration, including `type`, `domain`, `max`, `test`, `crossOrigin`, `delay`, `onRetry`, `onSuccess`, and `onFail`.
354+
355+
- **Example 1:** Different retry strategies for different CDNs:
356+
357+
```js
358+
pluginAssetsRetry({
359+
rules: [
360+
{
361+
// Rule for primary CDN
362+
test: /cdn1\.example\.com/,
363+
domain: ['cdn1.example.com', 'cdn1-backup.example.com'],
364+
max: 3,
365+
delay: 1000,
366+
},
367+
{
368+
// Rule for secondary CDN with more retries
369+
test: /cdn2\.example\.com/,
370+
domain: ['cdn2.example.com', 'cdn2-backup.example.com'],
371+
max: 5,
372+
delay: 500,
373+
},
374+
{
375+
// Default rule for other assets
376+
domain: ['default.example.com', 'default-backup.example.com'],
377+
max: 2,
378+
},
379+
],
380+
});
381+
```
382+
383+
- **Example 2:** Different retry strategies for different asset types:
384+
385+
```js
386+
pluginAssetsRetry({
387+
rules: [
388+
{
389+
// Critical JavaScript files get more retries
390+
type: ['script'],
391+
// Or test: /\.js$/,
392+
max: 5,
393+
delay: 1000,
394+
onFail: ({ url }) => console.error(`Critical JS failed: ${url}`),
395+
},
396+
{
397+
// CSS files get fewer retries
398+
test: /\.css$/,
399+
max: 2,
400+
delay: 500,
401+
},
402+
{
403+
// Images get minimal retries
404+
test: /\.(png|jpg|gif|svg)$/,
405+
max: 1,
406+
delay: 0,
407+
},
408+
],
409+
});
410+
```
411+
328412
## Notes
329413

330414
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:

README.zh-CN.md

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,33 +57,43 @@ type AssetsRetryHookContext = {
5757
isAsyncChunk: boolean;
5858
};
5959

60-
type AssetsRetryOptions = {
60+
type RuntimeRetryOptions = {
6161
type?: string[];
6262
domain?: string[];
6363
max?: number;
6464
test?: string | ((url: string) => boolean);
6565
crossOrigin?: boolean | 'anonymous' | 'use-credentials';
66-
inlineScript?: boolean;
6766
delay?: number | ((context: AssetsRetryHookContext) => number);
6867
onRetry?: (context: AssetsRetryHookContext) => void;
6968
onSuccess?: (context: AssetsRetryHookContext) => void;
7069
onFail?: (context: AssetsRetryHookContext) => void;
7170
};
71+
72+
type AssetsRetryOptions =
73+
| ({
74+
inlineScript?: boolean;
75+
minify?: boolean;
76+
} & RuntimeRetryOptions)
77+
| {
78+
inlineScript?: boolean;
79+
minify?: boolean;
80+
rules: RuntimeRetryOptions[];
81+
};
7282
```
7383

7484
- **默认值:**
7585

7686
```ts
77-
const defaultOptions = {
87+
const defaultAssetsRetryOptions = {
88+
max: 3,
7889
type: ['script', 'link', 'img'],
7990
domain: [],
80-
max: 3,
81-
test: '',
8291
crossOrigin: false,
92+
test: '',
8393
delay: 0,
84-
onRetry: () => {},
85-
onSuccess: () => {},
86-
onFail: () => {},
94+
addQuery: false,
95+
inlineScript: true,
96+
minify: rsbuildConfig.mode === 'production',
8797
};
8898
```
8999

@@ -240,7 +250,7 @@ type AddQuery =
240250

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

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

245255
```js
246256
pluginAssetsRetry({
@@ -252,7 +262,7 @@ pluginAssetsRetry({
252262
});
253263
```
254264

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

257267
```js
258268
pluginAssetsRetry({
@@ -323,6 +333,80 @@ pluginAssetsRetry({
323333
});
324334
```
325335

336+
### rules
337+
338+
- **类型:** `RuntimeRetryOptions[]`
339+
- **默认值:** `undefined`
340+
341+
配置多个重试规则,每个规则可以有不同的选项。规则会按顺序进行评估,第一个匹配的规则将用于重试逻辑。这在你对不同类型的资源或域名有不同的重试需求时非常有用。
342+
343+
使用 `rules` 时,插件会:
344+
345+
1. 按顺序通过 `test` `domain` `type` 检查每个规则
346+
347+
2. 如果匹配到规则,会使用规则的配置进行重试
348+
349+
3. 如果没有匹配到规则,则不会重试该资源
350+
351+
每个规则支持与顶层配置相同的所有选项,包括 `type``domain``test``max``crossOrigin``delay``onRetry``onSuccess``onFail`
352+
353+
- **示例 1:** 不同 CDN 的不同重试策略:
354+
355+
```js
356+
pluginAssetsRetry({
357+
rules: [
358+
{
359+
// 主 CDN 的规则
360+
test: /cdn1\.example\.com/,
361+
domain: ['cdn1.example.com', 'cdn1-backup.example.com'],
362+
max: 3,
363+
delay: 1000,
364+
},
365+
{
366+
// 次要 CDN 的规则,更多重试次数
367+
test: /cdn2\.example\.com/,
368+
domain: ['cdn2.example.com', 'cdn2-backup.example.com'],
369+
max: 5,
370+
delay: 500,
371+
},
372+
{
373+
// 其他资源的默认规则
374+
domain: ['default.example.com', 'default-backup.example.com'],
375+
max: 2,
376+
},
377+
],
378+
});
379+
```
380+
381+
- **示例 2:** 不同资源类型的不同重试策略:
382+
383+
```js
384+
pluginAssetsRetry({
385+
rules: [
386+
{
387+
// 关键 JavaScript 文件获得更多重试次数
388+
test: /\.js$/,
389+
// 或者 type: ['script'],
390+
max: 5,
391+
delay: 1000,
392+
onFail: ({ url }) => console.error(`关键 JS 失败: ${url}`),
393+
},
394+
{
395+
// CSS 文件获得较少的重试次数
396+
test: /\.css$/,
397+
max: 2,
398+
delay: 500,
399+
},
400+
{
401+
// 图片获得最少的重试次数
402+
test: /\.(png|jpg|gif|svg)$/,
403+
max: 1,
404+
delay: 0,
405+
},
406+
],
407+
});
408+
```
409+
326410
## 注意事项
327411

328412
当你使用 Assets Retry 插件时,Rsbuild 会分别向 HTML 和 [Rspack Runtime](https://rspack.dev/zh/misc/glossary#runtime) 中注入运行时代码,并将 Assets Retry 插件配置的内容序列化后插入到这些代码中,因此你需要注意:

playground/rsbuild.config.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@ import { defineConfig } from '@rsbuild/core';
22
import { pluginReact } from '@rsbuild/plugin-react';
33
import { pluginAssetsRetry } from '../dist';
44

5+
export function createBlockMiddleware({ urlPrefix, blockNum, onBlock }) {
6+
let counter = 0;
7+
8+
return (req, res, next) => {
9+
if (req.url?.startsWith(urlPrefix)) {
10+
counter++;
11+
// if blockNum is 3, 1 2 3 would be blocked, 4 would be passed
12+
const isBlocked = counter % (blockNum + 1) !== 0;
13+
14+
if (isBlocked && onBlock) {
15+
onBlock({
16+
url: req.url,
17+
count: counter,
18+
timestamp: Date.now(),
19+
});
20+
}
21+
if (isBlocked) {
22+
res.statusCode = 404;
23+
}
24+
res.setHeader('block-async', counter);
25+
}
26+
next();
27+
};
28+
}
29+
530
export default defineConfig({
6-
plugins: [pluginAssetsRetry(), pluginReact()],
31+
dev: {
32+
setupMiddlewares: [
33+
(middlewares) => {
34+
middlewares.unshift(
35+
createBlockMiddleware({
36+
urlPrefix: '/static/js/async/src_AsyncCompTest_tsx.js',
37+
blockNum: 3,
38+
onBlock: ({ url, count }) => {
39+
console.info(`Blocked ${url} for the ${count}th time`);
40+
},
41+
}),
42+
);
43+
},
44+
],
45+
},
46+
plugins: [
47+
pluginAssetsRetry({
48+
minify: true,
49+
onRetry(context) {
50+
console.info('onRetry', context);
51+
},
52+
onSuccess(context) {
53+
console.info('onSuccess', context);
54+
},
55+
onFail(context) {
56+
console.info('onFail', context);
57+
},
58+
}),
59+
pluginReact(),
60+
],
761
});

src/AsyncChunkRetryPlugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ class AsyncChunkRetryPlugin implements Rspack.RspackPluginInstance {
5050
readonly name = 'ASYNC_CHUNK_RETRY_PLUGIN';
5151
readonly isRspack: boolean;
5252
readonly minify: boolean;
53-
readonly runtimeOptions: NormalizedRuntimeRetryOptions;
53+
readonly runtimeOptions: NormalizedRuntimeRetryOptions[];
5454

5555
constructor(
56-
options: NormalizedRuntimeRetryOptions,
56+
options: NormalizedRuntimeRetryOptions[],
5757
isRspack: boolean,
5858
minify: boolean,
5959
) {

0 commit comments

Comments
 (0)