diff --git a/e2e/cases/emit-assets/index.test.ts b/e2e/cases/emit-assets/index.test.ts
index f7a6ef9b93..8b8e25b08e 100644
--- a/e2e/cases/emit-assets/index.test.ts
+++ b/e2e/cases/emit-assets/index.test.ts
@@ -1,6 +1,10 @@
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 disable emit assets for node target', async () => {
const rsbuild = await build({
cwd: __dirname,
@@ -9,15 +13,24 @@ test('should allow to disable emit assets for node target', async () => {
const files = await rsbuild.unwrapOutputJSON();
const filenames = Object.keys(files);
+ expect(isIncludeFile(filenames, 'dist/static/image/icon.png')).toBeTruthy();
+
expect(
- filenames.some((filename) =>
- filename.includes('dist/static/image/icon.png'),
- ),
- ).toBeTruthy();
+ isIncludeFile(filenames, 'dist/server/static/image/icon.png'),
+ ).toBeFalsy();
+});
+
+test('should allow to disable emit assets for json assets', async () => {
+ const rsbuild = await build({
+ cwd: __dirname,
+ });
+
+ const files = await rsbuild.unwrapOutputJSON();
+ const filenames = Object.keys(files);
+
+ expect(isIncludeFile(filenames, 'dist/static/assets/test.json')).toBeTruthy();
expect(
- filenames.some((filename) =>
- filename.includes('dist/server/static/image/icon.png'),
- ),
+ isIncludeFile(filenames, 'dist/server/static/assets/test.json'),
).toBeFalsy();
});
diff --git a/e2e/cases/emit-assets/src/assets/test.json b/e2e/cases/emit-assets/src/assets/test.json
new file mode 100644
index 0000000000..8d6b85c7b3
--- /dev/null
+++ b/e2e/cases/emit-assets/src/assets/test.json
@@ -0,0 +1,3 @@
+{
+ "a": 1
+}
diff --git a/e2e/cases/emit-assets/src/index.js b/e2e/cases/emit-assets/src/index.js
index fba1a3e9d1..1890fa7296 100644
--- a/e2e/cases/emit-assets/src/index.js
+++ b/e2e/cases/emit-assets/src/index.js
@@ -1,3 +1,5 @@
import small from '../../../assets/icon.png?url';
+const testJson = new URL('./assets/test.json', import.meta.url).href;
console.log(small);
+console.log(testJson);
diff --git a/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap b/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap
index 8b9a70a4cf..9feb53e111 100644
--- a/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap
+++ b/packages/compat/webpack/tests/__snapshots__/default.test.ts.snap
@@ -227,6 +227,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly 1`] = `
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "static/js/[name].js",
@@ -592,6 +593,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when produ
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].[contenthash:8].js",
"filename": "static/js/[name].[contenthash:8].js",
"hashFunction": "xxhash64",
@@ -956,6 +958,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when targe
"splitChunks": false,
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "[name].js",
"filename": "[name].js",
"hashFunction": "xxhash64",
@@ -1252,6 +1255,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when targe
"splitChunks": false,
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].[contenthash:8].js",
"filename": "static/js/[name].[contenthash:8].js",
"hashFunction": "xxhash64",
diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts
index 8c19e779a2..05d736387e 100644
--- a/packages/core/src/config.ts
+++ b/packages/core/src/config.ts
@@ -4,6 +4,7 @@ import type { WatchOptions } from 'chokidar';
import color from 'picocolors';
import RspackChain from 'rspack-chain';
import {
+ ASSETS_DIST_DIR,
CSS_DIST_DIR,
DEFAULT_ASSET_PREFIX,
DEFAULT_DATA_URL_SIZE,
@@ -141,6 +142,7 @@ const getDefaultOutputConfig = (): NormalizedOutputConfig => ({
wasm: WASM_DIST_DIR,
image: IMAGE_DIST_DIR,
media: MEDIA_DIST_DIR,
+ assets: ASSETS_DIST_DIR,
},
// Temporary placeholder, default: `${server.base}`
assetPrefix: DEFAULT_ASSET_PREFIX,
diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts
index 6a781ee6d3..29a2fca7f9 100644
--- a/packages/core/src/constants.ts
+++ b/packages/core/src/constants.ts
@@ -11,6 +11,7 @@ export const FONT_DIST_DIR = 'static/font';
export const WASM_DIST_DIR = 'static/wasm';
export const IMAGE_DIST_DIR = 'static/image';
export const MEDIA_DIST_DIR = 'static/media';
+export const ASSETS_DIST_DIR = 'static/assets';
export const LOADER_PATH: string = join(__dirname);
export const STATIC_PATH: string = join(__dirname, '../static');
export const COMPILED_PATH: string = join(__dirname, '../compiled');
diff --git a/packages/core/src/helpers/index.ts b/packages/core/src/helpers/index.ts
index 3ba9804ac3..28c4c0c605 100644
--- a/packages/core/src/helpers/index.ts
+++ b/packages/core/src/helpers/index.ts
@@ -255,6 +255,8 @@ export function getFilename(
return filename.image ?? `[name]${hash}[ext]`;
case 'media':
return filename.media ?? `[name]${hash}[ext]`;
+ case 'assets':
+ return filename.assets ?? `[name]${hash}[ext]`;
default:
throw new Error(`unknown key ${type} in "output.filename"`);
}
diff --git a/packages/core/src/plugins/asset.ts b/packages/core/src/plugins/asset.ts
index 5e8d8d0d11..9585886007 100644
--- a/packages/core/src/plugins/asset.ts
+++ b/packages/core/src/plugins/asset.ts
@@ -76,14 +76,20 @@ export const pluginAsset = (): RsbuildPlugin => ({
api.modifyBundlerChain((chain, { isProd, environment }) => {
const { config } = environment;
+ const getMergedFilename = (
+ assetType: 'svg' | 'font' | 'image' | 'media' | 'assets',
+ ) => {
+ const distDir = config.output.distPath[assetType];
+ const filename = getFilename(config, assetType, isProd);
+ return path.posix.join(distDir, filename);
+ };
+
const createAssetRule = (
- assetType: 'image' | 'media' | 'font' | 'svg',
+ assetType: 'svg' | 'font' | 'image' | 'media',
exts: string[],
emit: boolean,
) => {
const regExp = getRegExpForExts(exts);
- const distDir = config.output.distPath[assetType];
- const filename = getFilename(config, assetType, isProd);
const { dataUriLimit } = config.output;
const maxSize =
typeof dataUriLimit === 'number'
@@ -95,21 +101,31 @@ export const pluginAsset = (): RsbuildPlugin => ({
emit,
rule,
maxSize,
- filename: path.posix.join(distDir, filename),
+ filename: getMergedFilename(assetType),
assetType,
});
};
const { emitAssets } = config.output;
+ // image
createAssetRule('image', IMAGE_EXTENSIONS, emitAssets);
+ // svg
createAssetRule('svg', ['svg'], emitAssets);
+ // media
createAssetRule(
'media',
[...VIDEO_EXTENSIONS, ...AUDIO_EXTENSIONS],
emitAssets,
);
+ // font
createAssetRule('font', FONT_EXTENSIONS, emitAssets);
+ // assets
+ const assetsFilename = getMergedFilename('assets');
+ chain.output.assetModuleFilename(assetsFilename);
+ if (!emitAssets) {
+ chain.module.generator.merge({ 'asset/resource': { emit: false } });
+ }
});
},
});
diff --git a/packages/core/src/types/config.ts b/packages/core/src/types/config.ts
index bcdf0ee6f7..c2d47a7bc8 100644
--- a/packages/core/src/types/config.ts
+++ b/packages/core/src/types/config.ts
@@ -652,6 +652,11 @@ export type DistPathConfig = {
* @default 'static/media'
*/
media?: string;
+ /**
+ * The output directory of assets, except for above (image, svg, font, html, wasm...)
+ * @default 'static/assets'
+ */
+ assets?: string;
};
export type FilenameConfig = {
@@ -694,6 +699,11 @@ export type FilenameConfig = {
* @default '[name].[contenthash:8][ext]'
*/
media?: string;
+ /**
+ * the name of other assets, except for above (image, svg, font, html, wasm...)
+ * @default '[name].[contenthash:8][ext]'
+ */
+ assets?: string;
};
export type DataUriLimit = {
diff --git a/packages/core/tests/__snapshots__/asset.test.ts.snap b/packages/core/tests/__snapshots__/asset.test.ts.snap
index a006134923..f21f706635 100644
--- a/packages/core/tests/__snapshots__/asset.test.ts.snap
+++ b/packages/core/tests/__snapshots__/asset.test.ts.snap
@@ -114,6 +114,9 @@ exports[`plugin-asset > should add image rules correctly 1`] = `
},
],
},
+ "output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
+ },
"plugins": [
RsbuildCorePlugin {},
],
@@ -234,6 +237,9 @@ exports[`plugin-asset > should add image rules correctly 2`] = `
},
],
},
+ "output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
+ },
"plugins": [
RsbuildCorePlugin {},
],
@@ -354,6 +360,9 @@ exports[`plugin-asset > should allow to use distPath.image to modify dist path 1
},
],
},
+ "output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
+ },
"plugins": [
RsbuildCorePlugin {},
],
@@ -474,6 +483,9 @@ exports[`plugin-asset > should allow to use filename.image to modify filename 1`
},
],
},
+ "output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
+ },
"plugins": [
RsbuildCorePlugin {},
],
diff --git a/packages/core/tests/__snapshots__/builder.test.ts.snap b/packages/core/tests/__snapshots__/builder.test.ts.snap
index c8f439e194..bbe37537ae 100644
--- a/packages/core/tests/__snapshots__/builder.test.ts.snap
+++ b/packages/core/tests/__snapshots__/builder.test.ts.snap
@@ -291,6 +291,7 @@ exports[`should use rspack as default bundler > apply rspack correctly 1`] = `
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "static/js/[name].js",
diff --git a/packages/core/tests/__snapshots__/default.test.ts.snap b/packages/core/tests/__snapshots__/default.test.ts.snap
index 0198b7185f..16bf4d9188 100644
--- a/packages/core/tests/__snapshots__/default.test.ts.snap
+++ b/packages/core/tests/__snapshots__/default.test.ts.snap
@@ -291,6 +291,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly 1`] = `
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "static/js/[name].js",
@@ -718,6 +719,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when prod
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].[contenthash:8].js",
"filename": "static/js/[name].[contenthash:8].js",
"hashFunction": "xxhash64",
@@ -1103,6 +1105,7 @@ exports[`applyDefaultPlugins > should apply default plugins correctly when targe
"splitChunks": false,
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "[name].js",
"filename": "[name].js",
"hashFunction": "xxhash64",
@@ -1454,6 +1457,7 @@ exports[`tools.rspack > should match snapshot 1`] = `
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "static/js/[name].js",
diff --git a/packages/core/tests/__snapshots__/environments.test.ts.snap b/packages/core/tests/__snapshots__/environments.test.ts.snap
index 3811ad6bd7..50734250f7 100644
--- a/packages/core/tests/__snapshots__/environments.test.ts.snap
+++ b/packages/core/tests/__snapshots__/environments.test.ts.snap
@@ -49,6 +49,7 @@ exports[`environment config > should normalize environment config correctly 1`]
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -176,6 +177,7 @@ exports[`environment config > should normalize environment config correctly 2`]
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -303,6 +305,7 @@ exports[`environment config > should print environment config when inspect confi
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -461,6 +464,7 @@ exports[`environment config > should print environment config when inspect confi
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -637,6 +641,7 @@ exports[`environment config > should support modify environment config by api.mo
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -796,6 +801,7 @@ exports[`environment config > should support modify environment config by api.mo
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -956,6 +962,7 @@ exports[`environment config > should support modify environment config by api.mo
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -1119,6 +1126,7 @@ exports[`environment config > should support modify single environment config by
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -1278,6 +1286,7 @@ exports[`environment config > should support modify single environment config by
"svg": 4096,
},
"distPath": {
+ "assets": "static/assets",
"css": "static/css",
"font": "static/font",
"html": "./",
@@ -1681,6 +1690,7 @@ exports[`environment config > tools.rspack / bundlerChain can be configured in e
},
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "static/js/async/[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "static/js/[name].js",
@@ -2008,6 +2018,7 @@ exports[`environment config > tools.rspack / bundlerChain can be configured in e
"splitChunks": false,
},
"output": {
+ "assetModuleFilename": "static/assets/[name].[contenthash:8][ext]",
"chunkFilename": "[name].js",
"devtoolModuleFilenameTemplate": [Function],
"filename": "bundle.js",
diff --git a/website/docs/en/config/output/dist-path.mdx b/website/docs/en/config/output/dist-path.mdx
index 95997c4958..10802a5c18 100644
--- a/website/docs/en/config/output/dist-path.mdx
+++ b/website/docs/en/config/output/dist-path.mdx
@@ -15,6 +15,7 @@ type DistPathConfig = {
wasm?: string;
image?: string;
media?: string;
+ assets?: string;
};
```
@@ -33,6 +34,7 @@ const defaultDistPath = {
wasm: 'static/wasm',
image: 'static/image',
media: 'static/media',
+ assets: 'static/assets',
};
```
@@ -51,6 +53,7 @@ Detail:
- `wasm`: The output directory of WebAssembly files.
- `image`: The output directory of non-SVG images.
- `media`: The output directory of media assets, such as videos.
+- `assets`: The output directory of assets except the types mentioned above, which also match [Asset Modules](https://rspack.dev/guide/features/asset-module).
### Root Directory
diff --git a/website/docs/en/config/output/filename.mdx b/website/docs/en/config/output/filename.mdx
index bfb62bdc41..02d74ac87f 100644
--- a/website/docs/en/config/output/filename.mdx
+++ b/website/docs/en/config/output/filename.mdx
@@ -13,6 +13,7 @@ type FilenameConfig = {
font?: string;
image?: string;
media?: string;
+ assets?: string;
};
```
@@ -28,6 +29,7 @@ const devDefaultFilename = {
font: '[name].[contenthash:8][ext]',
image: '[name].[contenthash:8][ext]',
media: '[name].[contenthash:8][ext]',
+ assets: '[name].[contenthash:8][ext]',
};
// Production mode
@@ -39,6 +41,7 @@ const prodDefaultFilename = {
font: '[name].[contenthash:8][ext]',
image: '[name].[contenthash:8][ext]',
media: '[name].[contenthash:8][ext]',
+ assets: '[name].[contenthash:8][ext]',
};
```
@@ -55,6 +58,7 @@ The following are the details of each filename:
- `font`: The name of the font files.
- `image`: The name of non-SVG images.
- `media`: The name of media assets, such as video.
+- `assets`: The name of assets except the types mentioned above, which match [Asset Modules](https://rspack.dev/guide/features/asset-module).
> See [Output Files](/guide/basic/output-files) for more information.
diff --git a/website/docs/en/guide/basic/static-assets.mdx b/website/docs/en/guide/basic/static-assets.mdx
index 43b2074df7..05b0e28b9d 100644
--- a/website/docs/en/guide/basic/static-assets.mdx
+++ b/website/docs/en/guide/basic/static-assets.mdx
@@ -23,7 +23,7 @@ SVG image is a special case. Rsbuild support convert SVG to React components, so
## Import Assets in JS file
-In JS files, you can import static assets in relative paths:
+In JS files, you can import static assets with relative paths:
```tsx
// Import the logo.png image in the static directory
@@ -32,7 +32,7 @@ import logo from './static/logo.png';
export default = () => ;
```
-Import with [alias](/guide/advanced/alias) are also supported:
+Import with [alias](/guide/advanced/alias) is also available:
```tsx
import logo from '@/static/logo.png';
@@ -40,6 +40,14 @@ import logo from '@/static/logo.png';
export default = () => ;
```
+You can also combine [URL](https://developer.mozilla.org/docs/Web/API/URL) with [import.meta.url](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import.meta) which are native ESM features to import static assets.
+
+```tsx
+const logo = new URL('./static/logo.png', import.meta.url).href;
+
+export default = () => ;
+```
+
## Import Assets in CSS file
In CSS files, you can reference static assets in relative paths:
diff --git a/website/docs/zh/config/output/dist-path.mdx b/website/docs/zh/config/output/dist-path.mdx
index 14b0eaeea9..93c2ccb0f6 100644
--- a/website/docs/zh/config/output/dist-path.mdx
+++ b/website/docs/zh/config/output/dist-path.mdx
@@ -15,6 +15,7 @@ type DistPathConfig = {
wasm?: string;
image?: string;
media?: string;
+ assets?: string;
};
```
@@ -33,6 +34,7 @@ const defaultDistPath = {
wasm: 'static/wasm',
image: 'static/image',
media: 'static/media',
+ assets: 'static/assets';
};
```
@@ -51,6 +53,7 @@ const defaultDistPath = {
- `wasm`:表示 WebAssembly 文件的输出目录。
- `image`:表示非 SVG 图片的输出目录。
- `media`:表示视频等媒体资源的输出目录。
+- `assets`:表示除了上述类型之外,其他也命中 [Asset Modules](https://rspack.dev/guide/features/asset-module) 的文件的输出目录。
### 根目录
diff --git a/website/docs/zh/config/output/filename.mdx b/website/docs/zh/config/output/filename.mdx
index f8713be63a..07a860bc73 100644
--- a/website/docs/zh/config/output/filename.mdx
+++ b/website/docs/zh/config/output/filename.mdx
@@ -28,6 +28,7 @@ const devDefaultFilename = {
font: '[name].[contenthash:8][ext]',
image: '[name].[contenthash:8][ext]',
media: '[name].[contenthash:8][ext]',
+ assets: '[name].[contenthash:8][ext]',
};
// 生产模式构建
@@ -39,6 +40,7 @@ const prodDefaultFilename = {
font: '[name].[contenthash:8][ext]',
image: '[name].[contenthash:8][ext]',
media: '[name].[contenthash:8][ext]',
+ assets: '[name].[contenthash:8][ext]',
};
```
@@ -55,6 +57,7 @@ const prodDefaultFilename = {
- `font`:表示字体文件的名称。
- `image`:表示非 SVG 图片的名称。
- `media`:表示视频等媒体资源的名称。
+- `assets`:表示除了上述类型之外,其他命中 [Asset Modules](https://rspack.dev/guide/features/asset-module) 的文件名称。
> 查看 [构建产物目录](/guide/basic/output-files) 了解更多。
diff --git a/website/docs/zh/guide/basic/static-assets.mdx b/website/docs/zh/guide/basic/static-assets.mdx
index eb746ccfb1..c14d3d5961 100644
--- a/website/docs/zh/guide/basic/static-assets.mdx
+++ b/website/docs/zh/guide/basic/static-assets.mdx
@@ -32,7 +32,7 @@ import logo from './static/logo.png';
export default = () => ;
```
-也支持使用[路径别名](/guide/advanced/alias)来引用:
+也可以使用[路径别名](/guide/advanced/alias)来引用:
```tsx
import logo from '@/static/logo.png';
@@ -40,6 +40,14 @@ import logo from '@/static/logo.png';
export default = () => ;
```
+也支持使用 ESM 原生功能中的 [URL](https://developer.mozilla.org/docs/Web/API/URL) 和 [import.meta.url](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import.meta) 相配合,来引用静态资源。
+
+```tsx
+const logo = new URL('./static/logo.png', import.meta.url).href;
+
+export default = () => ;
+```
+
## 在 CSS 文件中引用
在 CSS 文件中,可以引用相对路径下的静态资源: