diff --git a/e2e/cases/assets/addtional-assets/index.test.ts b/e2e/cases/assets/addtional-assets/index.test.ts new file mode 100644 index 000000000..c76f1bd7c --- /dev/null +++ b/e2e/cases/assets/addtional-assets/index.test.ts @@ -0,0 +1,79 @@ +import path from 'node:path'; +import { build } from '@e2e/helper'; +import { expect, test } from '@playwright/test'; + +function isIncludeFile(filenames: string[], includeFilename: string) { + return filenames.some((filename) => filename.includes(includeFilename)); +} + +test('should allow to configure additional assets and match by RegExp', async () => { + const rsbuild = await build({ + cwd: __dirname, + rsbuildConfig: { + source: { + assetsInclude: [/\.json5$/], + }, + }, + }); + + const files = await rsbuild.unwrapOutputJSON(); + const filenames = Object.keys(files); + + const indexJs = await rsbuild.getIndexFile(); + expect(indexJs.content).toContain('data:application/json5;base64,'); + + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-large.json5'), + ).toBeTruthy(); + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-small.json5'), + ).toBeFalsy(); +}); + +test('should allow to configure additional assets and match by path', async () => { + const rsbuild = await build({ + cwd: __dirname, + rsbuildConfig: { + source: { + assetsInclude: path.resolve(__dirname, 'src/assets'), + }, + }, + }); + + const files = await rsbuild.unwrapOutputJSON(); + const filenames = Object.keys(files); + + const indexJs = await rsbuild.getIndexFile(); + expect(indexJs.content).toContain('data:application/json5;base64,'); + + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-large.json5'), + ).toBeTruthy(); + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-small.json5'), + ).toBeFalsy(); +}); + +test('should allow to disable emit for additional assets', async () => { + const rsbuild = await build({ + cwd: __dirname, + rsbuildConfig: { + source: { + assetsInclude: [/\.json5$/], + }, + output: { + emitAssets: false, + }, + }, + }); + + const files = await rsbuild.unwrapOutputJSON(); + const filenames = Object.keys(files); + + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-large.json5'), + ).toBeFalsy(); + expect( + isIncludeFile(filenames, 'dist/static/assets/test-temp-small.json5'), + ).toBeFalsy(); +}); diff --git a/e2e/cases/assets/addtional-assets/rsbuild.config.ts b/e2e/cases/assets/addtional-assets/rsbuild.config.ts new file mode 100644 index 000000000..3ca8395f8 --- /dev/null +++ b/e2e/cases/assets/addtional-assets/rsbuild.config.ts @@ -0,0 +1,18 @@ +import { join } from 'node:path'; +import { defineConfig } from '@rsbuild/core'; +import { outputFileSync } from 'fs-extra'; + +outputFileSync( + join(__dirname, 'src/assets/test-temp-small.json5'), + JSON.stringify({ a: 1 }), +); +outputFileSync( + join(__dirname, 'src/assets/test-temp-large.json5'), + JSON.stringify({ a: '1'.repeat(10000) }), +); + +export default defineConfig({ + output: { + filenameHash: false, + }, +}); diff --git a/e2e/cases/assets/addtional-assets/src/index.js b/e2e/cases/assets/addtional-assets/src/index.js new file mode 100644 index 000000000..97d0c583b --- /dev/null +++ b/e2e/cases/assets/addtional-assets/src/index.js @@ -0,0 +1,5 @@ +import large from './assets/test-temp-large.json5'; +import small from './assets/test-temp-small.json5'; + +console.log(large); +console.log(small); diff --git a/packages/core/src/plugins/asset.ts b/packages/core/src/plugins/asset.ts index 958588600..38efb9890 100644 --- a/packages/core/src/plugins/asset.ts +++ b/packages/core/src/plugins/asset.ts @@ -2,6 +2,7 @@ import path from 'node:path'; import type { GeneratorOptionsByModuleType } from '@rspack/core'; import { AUDIO_EXTENSIONS, + DEFAULT_DATA_URL_SIZE, FONT_EXTENSIONS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS, @@ -126,6 +127,25 @@ export const pluginAsset = (): RsbuildPlugin => ({ if (!emitAssets) { chain.module.generator.merge({ 'asset/resource': { emit: false } }); } + + // additional assets + const { assetsInclude } = config.source; + if (assetsInclude) { + const { dataUriLimit } = config.output; + const rule = chain.module.rule('additional-assets').test(assetsInclude); + const maxSize = + typeof dataUriLimit === 'number' + ? dataUriLimit + : DEFAULT_DATA_URL_SIZE; + + chainStaticAssetRule({ + emit: emitAssets, + rule, + maxSize, + filename: assetsFilename, + assetType: 'additional', + }); + } }); }, }); diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts index aab0b6b65..942fd1561 100644 --- a/packages/core/src/types/config.ts +++ b/packages/core/src/types/config.ts @@ -183,6 +183,11 @@ export interface SourceConfig { * and the `alias` option in the bundler. */ aliasStrategy?: AliasStrategy; + /** + * Include additional files that should be treated as static assets. + * @default undefined + */ + assetsInclude?: Rspack.RuleSetCondition; /** * Specify directories or modules that need additional compilation. * In order to maintain faster compilation speed, Rsbuild will not compile files under node_modules through diff --git a/website/docs/en/config/source/assets-include.mdx b/website/docs/en/config/source/assets-include.mdx new file mode 100644 index 000000000..1c91a6314 --- /dev/null +++ b/website/docs/en/config/source/assets-include.mdx @@ -0,0 +1,44 @@ +# source.assetsInclude + +- **Type:** [Rspack.RuleSetCondition](https://rspack.dev/config/module#condition) +- **Default:** `undefined` + +Include additional files that should be treated as static assets. + +By default, Rsbuild treats common image, font, audio, and video files as static assets. Through the `source.assetsInclude` config, you can specify additional file types that should be treated as static assets. These added static assets are processed using the same rules as the built-in supported static assets, see [Static Assets](/guide/basic/static-assets). + +The value of `source.assetsInclude` is the same as the `test` option in Rspack loader. It can be a regular expression, string, array, logical condition, etc. For more details, see [Rspack RuleSetCondition](https://rspack.dev/config/module#condition). + +## Example + +- Treating `.json5` files as static assets: + +```ts +export default defineConfig({ + source: { + assetsInclude: /\.json5$/, + }, +}); +``` + +- Treating multiple file types as static assets: + +```ts +export default defineConfig({ + source: { + assetsInclude: [/\.json5$/, /\.pdf$/], + }, +}); +``` + +- Treating specific files as static assets: + +```ts +import path from 'node:path'; + +export default defineConfig({ + source: { + assetsInclude: path.resolve(__dirname, 'src/assets/foo.json5'), + }, +}); +``` diff --git a/website/docs/en/config/source/exclude.mdx b/website/docs/en/config/source/exclude.mdx index c74e3032b..c5d23ab55 100644 --- a/website/docs/en/config/source/exclude.mdx +++ b/website/docs/en/config/source/exclude.mdx @@ -1,6 +1,6 @@ # source.exclude -- **Type:** [RuleSetCondition[]](https://rspack.dev/config/module#condition) +- **Type:** [Rspack.RuleSetCondition](https://rspack.dev/config/module#condition) - **Default:** `[]` Specifies JavaScript/TypeScript files that do not need to be compiled. The usage is consistent with [Rule.exclude](https://rspack.dev/config/module#ruleexclude) in Rspack, which supports passing in strings or regular expressions to match the module path. diff --git a/website/docs/en/config/source/include.mdx b/website/docs/en/config/source/include.mdx index ca11d10a9..86d86188f 100644 --- a/website/docs/en/config/source/include.mdx +++ b/website/docs/en/config/source/include.mdx @@ -1,6 +1,6 @@ # source.include -- **Type:** [RuleSetCondition[]](https://rspack.dev/config/module#condition) +- **Type:** [Rspack.RuleSetCondition](https://rspack.dev/config/module#condition) - **Default:** ```ts diff --git a/website/docs/zh/config/source/assets-include.mdx b/website/docs/zh/config/source/assets-include.mdx new file mode 100644 index 000000000..9b3e01957 --- /dev/null +++ b/website/docs/zh/config/source/assets-include.mdx @@ -0,0 +1,44 @@ +# source.assetsInclude + +- **类型:** [Rspack.RuleSetCondition](https://rspack.dev/config/module#condition) +- **默认值:** `undefined` + +指定需要被视为静态资源的额外文件类型。 + +Rsbuild 默认会将常见的图片、字体、音频、视频等文件视为静态资源。通过配置 `source.assetsInclude`,你可以添加更多的文件类型,这些新增的静态资源将按照与内置静态资源相同的规则进行处理,详见 [引用静态资源](/guide/basic/static-assets)。 + +`source.assetsInclude` 的值与 Rspack loader 的 `test` 选项相同,可以是正则表达式、字符串、数组、逻辑条件等,详见 [Rspack RuleSetCondition](https://rspack.dev/config/module#condition)。 + +## 示例 + +- 将 `.json5` 文件视为静态资源: + +```ts +export default defineConfig({ + source: { + assetsInclude: /\.json5$/, + }, +}); +``` + +- 将多种文件类型视为静态资源: + +```ts +export default defineConfig({ + source: { + assetsInclude: [/\.json5$/, /\.pdf$/], + }, +}); +``` + +- 将指定文件视为静态资源: + +```ts +import path from 'node:path'; + +export default defineConfig({ + source: { + assetsInclude: path.resolve(__dirname, 'src/assets/foo.json5'), + }, +}); +```