@rsbuild/plugin-assets-retry 1.2.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ bun add @rsbuild/plugin-assets-retry -D
37
37
  You can register the plugin in the `rsbuild.config.ts` file:
38
38
 
39
39
  ```ts
40
- import { pluginAssetsRetry } from "@rsbuild/plugin-assets-retry";
40
+ import { pluginAssetsRetry } from '@rsbuild/plugin-assets-retry';
41
41
 
42
42
  export default {
43
43
  plugins: [pluginAssetsRetry()],
@@ -59,33 +59,43 @@ type AssetsRetryHookContext = {
59
59
  isAsyncChunk: boolean;
60
60
  };
61
61
 
62
- type AssetsRetryOptions = {
62
+ type RuntimeRetryOptions = {
63
63
  type?: string[];
64
64
  domain?: string[];
65
65
  max?: number;
66
- test?: string | ((url: string) => boolean);
67
- crossOrigin?: boolean | "anonymous" | "use-credentials";
68
- inlineScript?: boolean;
66
+ test?: string | RegExp | ((url: string) => boolean);
67
+ crossOrigin?: boolean | 'anonymous' | 'use-credentials';
69
68
  delay?: number | ((context: AssetsRetryHookContext) => number);
70
69
  onRetry?: (context: AssetsRetryHookContext) => void;
71
70
  onSuccess?: (context: AssetsRetryHookContext) => void;
72
71
  onFail?: (context: AssetsRetryHookContext) => void;
73
72
  };
73
+
74
+ type AssetsRetryOptions =
75
+ | ({
76
+ inlineScript?: boolean;
77
+ minify?: boolean;
78
+ } & RuntimeRetryOptions)
79
+ | {
80
+ inlineScript?: boolean;
81
+ minify?: boolean;
82
+ rules: RuntimeRetryOptions[];
83
+ };
74
84
  ```
75
85
 
76
86
  - **Default:**
77
87
 
78
88
  ```ts
79
89
  const defaultAssetsRetryOptions = {
80
- type: ["script", "link", "img"],
81
- domain: [],
82
90
  max: 3,
83
- test: "",
91
+ type: ['script', 'link', 'img'],
92
+ domain: [],
84
93
  crossOrigin: false,
94
+ test: '',
85
95
  delay: 0,
86
- onRetry: () => {},
87
- onSuccess: () => {},
88
- onFail: () => {},
96
+ addQuery: false,
97
+ inlineScript: true,
98
+ minify: rsbuildConfig.mode === 'production',
89
99
  };
90
100
  ```
91
101
 
@@ -103,11 +113,11 @@ For example:
103
113
  defineConfig({
104
114
  plugins: [
105
115
  pluginAssetsRetry({
106
- domain: ["cdn1.com", "cdn2.com", "cdn3.com"],
107
- })
116
+ domain: ['cdn1.com', 'cdn2.com', 'cdn3.com'],
117
+ }),
108
118
  ],
109
119
  output: {
110
- assetPrefix: "https://cdn1.com", // or "//cdn1.com"
120
+ assetPrefix: 'https://cdn1.com', // or "//cdn1.com"
111
121
  },
112
122
  });
113
123
  ```
@@ -127,7 +137,7 @@ For example, only script tags and link tags are processed:
127
137
 
128
138
  ```js
129
139
  pluginAssetsRetry({
130
- type: ["script", "link"],
140
+ type: ['script', 'link'],
131
141
  });
132
142
  ```
133
143
 
@@ -182,7 +192,7 @@ The callback function when the asset is being retried. For example:
182
192
  pluginAssetsRetry({
183
193
  onRetry: ({ times, domain, url, tagName, isAsyncChunk }) => {
184
194
  console.log(
185
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
195
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
186
196
  );
187
197
  },
188
198
  });
@@ -198,7 +208,7 @@ The callback function when the asset is successfully retried. For example:
198
208
  pluginAssetsRetry({
199
209
  onSuccess: ({ times, domain, url, tagName, isAsyncChunk }) => {
200
210
  console.log(
201
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
211
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
202
212
  );
203
213
  },
204
214
  });
@@ -214,7 +224,7 @@ The callback function when the asset is failed to be retried. For example:
214
224
  pluginAssetsRetry({
215
225
  onFail: ({ times, domain, url, tagName, isAsyncChunk }) => {
216
226
  console.log(
217
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
227
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
218
228
  );
219
229
  },
220
230
  });
@@ -242,7 +252,7 @@ When set to `true`, `retry=${times}` will be added to the query when requesting,
242
252
 
243
253
  When you want to customize query, you can pass a function, for example:
244
254
 
245
- - **Example:** All assets requested do not contain query:
255
+ - **Example 1:** All assets requested do not contain query:
246
256
 
247
257
  ```js
248
258
  pluginAssetsRetry({
@@ -254,7 +264,7 @@ pluginAssetsRetry({
254
264
  });
255
265
  ```
256
266
 
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`:
258
268
 
259
269
  ```js
260
270
  pluginAssetsRetry({
@@ -321,7 +331,81 @@ Or pass a function that receives `AssetsRetryHookContext` and returns the delay
321
331
  ```js
322
332
  // Calculate delay based on retry attempts
323
333
  pluginAssetsRetry({
324
- delay: (ctx) => (ctx.times + 1) * 1000,
334
+ delay: ctx => (ctx.times + 1) * 1000,
335
+ });
336
+ ```
337
+
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
+ ],
325
409
  });
326
410
  ```
327
411
 
@@ -336,12 +420,12 @@ When you use Assets Retry plugin, the Rsbuild injects some runtime code into the
336
420
  Here's an example of incorrect usage:
337
421
 
338
422
  ```js
339
- import { someMethod } from "utils";
423
+ import { someMethod } from 'utils';
340
424
 
341
425
  pluginAssetsRetry({
342
426
  onRetry() {
343
427
  // Incorrect usage, includes sensitive information
344
- const privateToken = "a-private-token";
428
+ const privateToken = 'a-private-token';
345
429
 
346
430
  // Incorrect usage, uses an external method
347
431
  someMethod(privateToken);
@@ -353,6 +437,10 @@ pluginAssetsRetry({
353
437
 
354
438
  Assets Retry plugin may not work in the following scenarios:
355
439
 
440
+ ### Sync script tag loaded resources
441
+
442
+ `<script src="..."></script>` tags load resources synchronously, and retrying them does not guarantee the order of resource loading. Therefore, the Assets Retry plugin will not retry resources loaded by synchronous script tags. It will only retry resources loaded by async/defer script tags.
443
+
356
444
  ### Module Federation
357
445
 
358
446
  For remote modules loaded by Module Federation, you can use the [@module-federation/retry-plugin](https://www.npmjs.com/package/@module-federation/retry-plugin) from Module Federation 2.0 to implement static asset retries.
@@ -385,6 +473,32 @@ If you want Assets Retry plugin to work on resources in custom templates, you ca
385
473
  </html>
386
474
  ```
387
475
 
476
+ #### Identifying retry scripts in HTML templates
477
+
478
+ The Assets Retry plugin adds a unique `data-rsbuild-assets-retry` attribute to retry scripts, allowing you to easily identify them in custom HTML templates.
479
+
480
+ You can import the attribute constant:
481
+
482
+ ```js
483
+ import { ASSETS_RETRY_DATA_ATTRIBUTE } from '@rsbuild/plugin-assets-retry';
484
+ ```
485
+
486
+ The attribute values are:
487
+ - `"inline"` for inline scripts (when `inlineScript: true`)
488
+ - `"external"` for external scripts (when `inlineScript: false`)
489
+
490
+ Example usage in HTML templates:
491
+
492
+ ```html
493
+ <!-- Filter retry scripts -->
494
+ <%= htmlWebpackPlugin.tags.headTags.filter(tag => tag.attributes['data-rsbuild-assets-retry'] === 'inline') %>
495
+
496
+ <!-- Filter non-retry scripts -->
497
+ <%= htmlWebpackPlugin.tags.headTags.filter(tag => !tag.attributes['data-rsbuild-assets-retry']) %>
498
+ ```
499
+
500
+ This allows you to place retry scripts at the top of your HTML head for optimal loading order.
501
+
388
502
  ## License
389
503
 
390
504
  [MIT](./LICENSE).
package/README.zh-CN.md CHANGED
@@ -35,7 +35,7 @@ bun add @rsbuild/plugin-assets-retry -D
35
35
  你可以在 `rsbuild.config.ts` 文件中注册插件:
36
36
 
37
37
  ```ts
38
- import { pluginAssetsRetry } from "@rsbuild/plugin-assets-retry";
38
+ import { pluginAssetsRetry } from '@rsbuild/plugin-assets-retry';
39
39
 
40
40
  export default {
41
41
  plugins: [pluginAssetsRetry()],
@@ -57,33 +57,43 @@ type AssetsRetryHookContext = {
57
57
  isAsyncChunk: boolean;
58
58
  };
59
59
 
60
- type AssetsRetryOptions = {
60
+ type RuntimeRetryOptions = {
61
61
  type?: string[];
62
62
  domain?: string[];
63
63
  max?: number;
64
64
  test?: string | ((url: string) => boolean);
65
- crossOrigin?: boolean | "anonymous" | "use-credentials";
66
- inlineScript?: boolean;
65
+ crossOrigin?: boolean | 'anonymous' | 'use-credentials';
67
66
  delay?: number | ((context: AssetsRetryHookContext) => number);
68
67
  onRetry?: (context: AssetsRetryHookContext) => void;
69
68
  onSuccess?: (context: AssetsRetryHookContext) => void;
70
69
  onFail?: (context: AssetsRetryHookContext) => void;
71
70
  };
71
+
72
+ type AssetsRetryOptions =
73
+ | ({
74
+ inlineScript?: boolean;
75
+ minify?: boolean;
76
+ } & RuntimeRetryOptions)
77
+ | {
78
+ inlineScript?: boolean;
79
+ minify?: boolean;
80
+ rules: RuntimeRetryOptions[];
81
+ };
72
82
  ```
73
83
 
74
84
  - **默认值:**
75
85
 
76
86
  ```ts
77
- const defaultOptions = {
78
- type: ["script", "link", "img"],
79
- domain: [],
87
+ const defaultAssetsRetryOptions = {
80
88
  max: 3,
81
- test: "",
89
+ type: ['script', 'link', 'img'],
90
+ domain: [],
82
91
  crossOrigin: false,
92
+ test: '',
83
93
  delay: 0,
84
- onRetry: () => {},
85
- onSuccess: () => {},
86
- onFail: () => {},
94
+ addQuery: false,
95
+ inlineScript: true,
96
+ minify: rsbuildConfig.mode === 'production',
87
97
  };
88
98
  ```
89
99
 
@@ -101,11 +111,11 @@ const defaultOptions = {
101
111
  defineConfig({
102
112
  plugins: [
103
113
  pluginAssetsRetry({
104
- domain: ["cdn1.com", "cdn2.com", "cdn3.com"],
105
- })
114
+ domain: ['cdn1.com', 'cdn2.com', 'cdn3.com'],
115
+ }),
106
116
  ],
107
117
  output: {
108
- assetPrefix: "https://cdn1.com", // 或者 "//cdn1.com"
118
+ assetPrefix: 'https://cdn1.com', // 或者 "//cdn1.com"
109
119
  },
110
120
  });
111
121
  ```
@@ -125,7 +135,7 @@ defineConfig({
125
135
 
126
136
  ```js
127
137
  pluginAssetsRetry({
128
- type: ["script", "link"],
138
+ type: ['script', 'link'],
129
139
  });
130
140
  ```
131
141
 
@@ -180,7 +190,7 @@ pluginAssetsRetry({
180
190
  pluginAssetsRetry({
181
191
  onRetry: ({ times, domain, url, tagName, isAsyncChunk }) => {
182
192
  console.log(
183
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
193
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
184
194
  );
185
195
  },
186
196
  });
@@ -196,7 +206,7 @@ pluginAssetsRetry({
196
206
  pluginAssetsRetry({
197
207
  onSuccess: ({ times, domain, url, tagName, isAsyncChunk }) => {
198
208
  console.log(
199
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
209
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
200
210
  );
201
211
  },
202
212
  });
@@ -212,7 +222,7 @@ pluginAssetsRetry({
212
222
  pluginAssetsRetry({
213
223
  onFail: ({ times, domain, url, tagName, isAsyncChunk }) => {
214
224
  console.log(
215
- `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`
225
+ `Retry ${times} times, domain: ${domain}, url: ${url}, tagName: ${tagName}, isAsyncChunk: ${isAsyncChunk}`,
216
226
  );
217
227
  },
218
228
  });
@@ -240,7 +250,7 @@ type AddQuery =
240
250
 
241
251
  当你想要自定义 query 时,可以传入一个函数,比如:
242
252
 
243
- - **示例:** 请求的所有资源都不含 query:
253
+ - **示例 1:** 请求的所有资源都不含 query:
244
254
 
245
255
  ```js
246
256
  pluginAssetsRetry({
@@ -252,7 +262,7 @@ pluginAssetsRetry({
252
262
  });
253
263
  ```
254
264
 
255
- - **示例:** 当请求的某些资源中含有 query 时,可以使用 `originalQuery` 读取:
265
+ - **示例 2:** 当请求的某些资源中含有 query 时,可以使用 `originalQuery` 读取:
256
266
 
257
267
  ```js
258
268
  pluginAssetsRetry({
@@ -319,7 +329,81 @@ pluginAssetsRetry({
319
329
  ```js
320
330
  // 通过次数来计算延迟时间
321
331
  pluginAssetsRetry({
322
- delay: (ctx) => (ctx.times + 1) * 1000,
332
+ delay: ctx => (ctx.times + 1) * 1000,
333
+ });
334
+ ```
335
+
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
+ ],
323
407
  });
324
408
  ```
325
409
 
@@ -334,12 +418,12 @@ pluginAssetsRetry({
334
418
  以下是一个错误示例:
335
419
 
336
420
  ```js
337
- import { someMethod } from "utils";
421
+ import { someMethod } from 'utils';
338
422
 
339
423
  pluginAssetsRetry({
340
424
  onRetry() {
341
425
  // 错误用法,包含了敏感信息
342
- const privateToken = "a-private-token";
426
+ const privateToken = 'a-private-token';
343
427
 
344
428
  // 错误用法,使用了外部的方法
345
429
  someMethod(privateToken);
@@ -351,6 +435,10 @@ pluginAssetsRetry({
351
435
 
352
436
  以下场景 Assets Retry 插件可能无法生效:
353
437
 
438
+ ### 同步 script 标签加载的资源
439
+
440
+ `<script src="..."></script>` 标签加载的资源是同步加载的,如果进行重试无法保证资源加载的顺序,因此 Assets Retry 插件不会对同步加载的 script 标签进行重试。只会对 async/defer 的 script 标签进行重试。
441
+
354
442
  ### 模块联邦
355
443
 
356
444
  对于模块联邦加载的远程模块,你可以使用模块联邦 2.0 的 [@module-federation/retry-plugin](https://www.npmjs.com/package/@module-federation/retry-plugin) 来实现静态资源重试。
@@ -383,6 +471,32 @@ Assets Retry 插件通过监听页面 error 事件来获悉当前资源是否加
383
471
  </html>
384
472
  ```
385
473
 
474
+ #### 在 HTML 模板中识别重试脚本
475
+
476
+ Assets Retry 插件为重试脚本添加了唯一的 `data-rsbuild-assets-retry` 属性,使您可以在自定义 HTML 模板中轻松识别它们。
477
+
478
+ 您可以导入属性常量:
479
+
480
+ ```js
481
+ import { ASSETS_RETRY_DATA_ATTRIBUTE } from '@rsbuild/plugin-assets-retry';
482
+ ```
483
+
484
+ 属性值包括:
485
+ - `"inline"` 用于内联脚本(当 `inlineScript: true` 时)
486
+ - `"external"` 用于外部脚本(当 `inlineScript: false` 时)
487
+
488
+ 在 HTML 模板中的使用示例:
489
+
490
+ ```html
491
+ <!-- 筛选重试脚本 -->
492
+ <%= htmlWebpackPlugin.tags.headTags.filter(tag => tag.attributes['data-rsbuild-assets-retry'] === 'inline') %>
493
+
494
+ <!-- 筛选非重试脚本 -->
495
+ <%= htmlWebpackPlugin.tags.headTags.filter(tag => !tag.attributes['data-rsbuild-assets-retry']) %>
496
+ ```
497
+
498
+ 这允许您将重试脚本放置在 HTML 头部的顶部以获得最佳的加载顺序。
499
+
386
500
  ## License
387
501
 
388
502
  [MIT](./LICENSE).