diff --git a/README.md b/README.md index 1ecf785..308649d 100644 --- a/README.md +++ b/README.md @@ -473,6 +473,32 @@ If you want Assets Retry plugin to work on resources in custom templates, you ca ``` +#### Identifying retry scripts in HTML templates + +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. + +You can import the attribute constant: + +```js +import { ASSETS_RETRY_DATA_ATTRIBUTE } from '@rsbuild/plugin-assets-retry'; +``` + +The attribute values are: +- `"inline"` for inline scripts (when `inlineScript: true`) +- `"external"` for external scripts (when `inlineScript: false`) + +Example usage in HTML templates: + +```html + +<%= htmlWebpackPlugin.tags.headTags.filter(tag => tag.attributes['data-rsbuild-assets-retry'] === 'inline') %> + + +<%= htmlWebpackPlugin.tags.headTags.filter(tag => !tag.attributes['data-rsbuild-assets-retry']) %> +``` + +This allows you to place retry scripts at the top of your HTML head for optimal loading order. + ## License [MIT](./LICENSE). diff --git a/README.zh-CN.md b/README.zh-CN.md index 23c6d2d..5d1900b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -471,6 +471,32 @@ Assets Retry 插件通过监听页面 error 事件来获悉当前资源是否加 ``` +#### 在 HTML 模板中识别重试脚本 + +Assets Retry 插件为重试脚本添加了唯一的 `data-rsbuild-assets-retry` 属性,使您可以在自定义 HTML 模板中轻松识别它们。 + +您可以导入属性常量: + +```js +import { ASSETS_RETRY_DATA_ATTRIBUTE } from '@rsbuild/plugin-assets-retry'; +``` + +属性值包括: +- `"inline"` 用于内联脚本(当 `inlineScript: true` 时) +- `"external"` 用于外部脚本(当 `inlineScript: false` 时) + +在 HTML 模板中的使用示例: + +```html + +<%= htmlWebpackPlugin.tags.headTags.filter(tag => tag.attributes['data-rsbuild-assets-retry'] === 'inline') %> + + +<%= htmlWebpackPlugin.tags.headTags.filter(tag => !tag.attributes['data-rsbuild-assets-retry']) %> +``` + +这允许您将重试脚本放置在 HTML 头部的顶部以获得最佳的加载顺序。 + ## License [MIT](./LICENSE). diff --git a/src/index.ts b/src/index.ts index 6ae81fe..de88473 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,6 +21,9 @@ export type { PluginAssetsRetryOptions }; export const PLUGIN_ASSETS_RETRY_NAME = 'rsbuild:assets-retry'; +// Unique identifier for filtering retry scripts in HTML templates +export const ASSETS_RETRY_DATA_ATTRIBUTE = 'data-rsbuild-assets-retry'; + function getRuntimeOptions( userOptions: PluginAssetsRetryOptions, defaultCrossOrigin: boolean | 'anonymous' | 'use-credentials', @@ -117,7 +120,9 @@ export const pluginAssetsRetry = ( headTags.unshift({ tag: 'script', - attrs: {}, + attrs: { + [ASSETS_RETRY_DATA_ATTRIBUTE]: 'inline', + }, children: code, }); @@ -133,6 +138,7 @@ export const pluginAssetsRetry = ( tag: 'script', attrs: { src: url, + [ASSETS_RETRY_DATA_ATTRIBUTE]: 'external', }, }); diff --git a/test/basic/dataAttribute.test.ts b/test/basic/dataAttribute.test.ts new file mode 100644 index 0000000..edcba1f --- /dev/null +++ b/test/basic/dataAttribute.test.ts @@ -0,0 +1,96 @@ +import { expect, test } from '@playwright/test'; +import { createRsbuild } from '@rsbuild/core'; +import { ASSETS_RETRY_DATA_ATTRIBUTE, pluginAssetsRetry } from '../../dist'; + +test('should add data attribute to inline retry script', async ({ page }) => { + const rsbuild = await createRsbuild({ + cwd: import.meta.dirname, + rsbuildConfig: { + plugins: [ + pluginAssetsRetry({ + inlineScript: true, + }), + ], + }, + }); + + const { server, urls } = await rsbuild.startDevServer(); + + await page.goto(urls[0]); + + // Check if inline script has correct data attribute + const inlineScript = await page.locator( + `script[${ASSETS_RETRY_DATA_ATTRIBUTE}="inline"]`, + ); + expect(await inlineScript.count()).toBe(1); + + // Verify script content contains retry logic + const scriptContent = await inlineScript.innerHTML(); + expect(scriptContent).toContain('document.addEventListener'); + + await server.close(); +}); + +test('should add data attribute to external retry script', async ({ page }) => { + const rsbuild = await createRsbuild({ + cwd: import.meta.dirname, + rsbuildConfig: { + plugins: [ + pluginAssetsRetry({ + inlineScript: false, + }), + ], + }, + }); + + const { server, urls } = await rsbuild.startDevServer(); + + await page.goto(urls[0]); + + // Check if external script has correct data attribute + const externalScript = await page.locator( + `script[${ASSETS_RETRY_DATA_ATTRIBUTE}="external"]`, + ); + expect(await externalScript.count()).toBe(1); + + // Verify script has src attribute + const src = await externalScript.getAttribute('src'); + expect(src).toContain('assets-retry'); + + await server.close(); +}); + +test('should be able to filter retry script in HTML template', async ({ + page, +}) => { + const rsbuild = await createRsbuild({ + cwd: import.meta.dirname, + rsbuildConfig: { + plugins: [ + pluginAssetsRetry({ + inlineScript: true, + }), + ], + }, + }); + + const { server, urls } = await rsbuild.startDevServer(); + + await page.goto(urls[0]); + + // Simulate using htmlWebpackPlugin.tags.headTags.filter in HTML template + // Verify that retry scripts can be filtered by data attribute + const allScripts = await page.locator('script'); + const retryScripts = await page.locator( + `script[${ASSETS_RETRY_DATA_ATTRIBUTE}]`, + ); + + const allScriptsCount = await allScripts.count(); + const retryScriptsCount = await retryScripts.count(); + + // Should have at least one script, and retry script should be one of them + expect(allScriptsCount).toBeGreaterThan(0); + expect(retryScriptsCount).toBe(1); + + await server.close(); +});