Skip to content

Commit ea877c4

Browse files
authored
feat: support for dynamic asset filenames (#4656)
1 parent 23bf4c6 commit ea877c4

File tree

8 files changed

+151
-38
lines changed

8 files changed

+151
-38
lines changed

e2e/cases/assets/filename-function/index.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,28 @@ test('should allow to custom filename by function', async () => {
3232
filename.includes('dist/static/css/async/some-path/foo.css'),
3333
),
3434
).toBeTruthy();
35+
36+
// Image
37+
expect(
38+
filenames.some((filename) =>
39+
filename.includes('dist/static/image/my-icon.png'),
40+
),
41+
).toBeTruthy();
42+
expect(
43+
filenames.some((filename) =>
44+
filename.includes('dist/static/image/some-path/image.png'),
45+
),
46+
).toBeTruthy();
47+
48+
// SVG
49+
expect(
50+
filenames.some((filename) =>
51+
filename.includes('dist/static/svg/my-circle.svg'),
52+
),
53+
).toBeTruthy();
54+
expect(
55+
filenames.some((filename) =>
56+
filename.includes('dist/static/svg/some-path/mobile.svg'),
57+
),
58+
).toBeTruthy();
3559
});

e2e/cases/assets/filename-function/rsbuild.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ export default defineConfig({
1616
}
1717
return '/some-path/[name].css';
1818
},
19+
image: (pathData) => {
20+
if (pathData.filename?.includes('icon.png')) {
21+
return 'my-icon.png';
22+
}
23+
return '/some-path/[name][ext]';
24+
},
25+
svg: (pathData) => {
26+
if (pathData.filename?.includes('circle.svg')) {
27+
return 'my-circle.svg';
28+
}
29+
return '/some-path/[name][ext]';
30+
},
1931
},
2032
},
2133
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
import './index.css';
2+
import circle from '@assets/circle.svg?url';
3+
import icon from '@assets/icon.png?url';
4+
import image from '@assets/image.png?url';
5+
import mobile from '@assets/mobile.svg?url';
6+
7+
console.log(icon, image, circle, mobile);
28

39
import(/* webpackChunkName: "foo" */ './async');

packages/core/src/helpers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ export function getFilename(
226226
type: Exclude<keyof FilenameConfig, 'js' | 'css'>,
227227
isProd: boolean,
228228
isServer?: boolean,
229-
): string;
229+
): Rspack.AssetModuleFilename;
230230
export function getFilename(
231231
config: NormalizedConfig | NormalizedEnvironmentConfig,
232232
type: keyof FilenameConfig,

packages/core/src/plugins/asset.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import path from 'node:path';
2-
import type { GeneratorOptionsByModuleType } from '@rspack/core';
2+
import type {
3+
AssetModuleFilename,
4+
GeneratorOptionsByModuleType,
5+
} from '@rspack/core';
36
import { CHAIN_ID } from '../configChain';
47
import {
58
AUDIO_EXTENSIONS,
@@ -20,7 +23,7 @@ const chainStaticAssetRule = ({
2023
emit: boolean;
2124
rule: RspackChain.Rule;
2225
maxSize: number;
23-
filename: string;
26+
filename: AssetModuleFilename;
2427
assetType: string;
2528
}) => {
2629
const generatorOptions:
@@ -79,9 +82,17 @@ export const pluginAsset = (): RsbuildPlugin => ({
7982

8083
const getMergedFilename = (
8184
assetType: 'svg' | 'font' | 'image' | 'media' | 'assets',
82-
) => {
85+
): AssetModuleFilename => {
8386
const distDir = config.output.distPath[assetType];
8487
const filename = getFilename(config, assetType, isProd);
88+
89+
if (typeof filename === 'function') {
90+
return (...args) => {
91+
const name = filename(...args);
92+
return path.posix.join(distDir, name);
93+
};
94+
}
95+
8596
return path.posix.join(distDir, filename);
8697
};
8798

packages/core/src/types/config.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,11 @@ export type DistPathConfig = {
759759
};
760760

761761
export type FilenameConfig = {
762+
/**
763+
* The name of HTML files.
764+
* @default `[name].html`
765+
*/
766+
html?: string;
762767
/**
763768
* The name of the JavaScript files.
764769
* @default
@@ -777,32 +782,27 @@ export type FilenameConfig = {
777782
* The name of the SVG images.
778783
* @default '[name].[contenthash:8].svg'
779784
*/
780-
svg?: string;
781-
/**
782-
* The name of HTML files.
783-
* @default `[name].html`
784-
*/
785-
html?: string;
785+
svg?: Rspack.AssetModuleFilename;
786786
/**
787787
* The name of the font files.
788788
* @default '[name].[contenthash:8][ext]'
789789
*/
790-
font?: string;
790+
font?: Rspack.AssetModuleFilename;
791791
/**
792792
* The name of non-SVG images.
793793
* @default '[name].[contenthash:8][ext]'
794794
*/
795-
image?: string;
795+
image?: Rspack.AssetModuleFilename;
796796
/**
797797
* The name of media assets, such as video.
798798
* @default '[name].[contenthash:8][ext]'
799799
*/
800-
media?: string;
800+
media?: Rspack.AssetModuleFilename;
801801
/**
802802
* the name of other assets, except for above (image, svg, font, html, wasm...)
803803
* @default '[name].[contenthash:8][ext]'
804804
*/
805-
assets?: string;
805+
assets?: Rspack.AssetModuleFilename;
806806
};
807807

808808
export type DataUriLimit = {

website/docs/en/config/output/filename.mdx

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ type FilenameConfig = {
77
html?: string;
88
js?: string | Function;
99
css?: string | Function;
10-
svg?: string;
11-
font?: string;
12-
image?: string;
13-
media?: string;
14-
assets?: string;
10+
svg?: string | Function;
11+
font?: string | Function;
12+
image?: string | Function;
13+
media?: string | Function;
14+
assets?: string | Function;
1515
};
1616
```
1717

@@ -49,7 +49,7 @@ After the production build, Rsbuild will add a hash in the middle of the filenam
4949

5050
The following are the details of each filename:
5151

52-
- `html`: The name of the HTML file.
52+
- `html`: The name of the HTML files.
5353
- `js`: The name of the JavaScript files.
5454
- `css`: The name of the CSS files.
5555
- `svg`: The name of the SVG images.
@@ -137,42 +137,72 @@ After specifying the module name as above, the generated file will be `dist/stat
137137

138138
## Using function
139139

140-
You can pass a function to `output.filename.js` or `output.filename.css`, allowing you to dynamically generate filenames based on file information.
140+
You can pass a function to dynamically set the filename based on the file information.
141141

142142
The function receives two parameters:
143143

144144
- `pathData`: An object containing file path information.
145145
- `assetInfo`: An optional object containing additional assets information.
146146

147+
Dynamically set the filename for JavaScript files:
148+
147149
```ts title="rsbuild.config.ts"
150+
const isProd = process.env.NODE_ENV === 'production';
151+
148152
export default {
149153
output: {
150154
filename: {
151155
js: (pathData, assetInfo) => {
152156
console.log(pathData); // You can check the contents of pathData here
153157

154158
if (pathData.chunk?.name === 'index') {
155-
const isProd = process.env.NODE_ENV === 'production';
156159
return isProd ? '[name].[contenthash:8].js' : '[name].js';
157160
}
158-
159161
return '/some-path/[name].js';
160162
},
163+
},
164+
},
165+
};
166+
```
167+
168+
Dynamically set the filename for CSS files:
169+
170+
```ts title="rsbuild.config.ts"
171+
const isProd = process.env.NODE_ENV === 'production';
172+
173+
export default {
174+
output: {
175+
filename: {
161176
css: (pathData, assetInfo) => {
162177
if (pathData.chunk?.name === 'index') {
163-
const isProd = process.env.NODE_ENV === 'production';
164178
return isProd ? '[name].[contenthash:8].css' : '[name].css';
165179
}
166-
167180
return '/some-path/[name].css';
168181
},
169182
},
170183
},
171184
};
172185
```
173186

187+
Dynamically set the filename for image files:
188+
189+
```ts title="rsbuild.config.ts"
190+
export default {
191+
output: {
192+
filename: {
193+
image: (pathData) => {
194+
if (pathData.filename?.includes('foo')) {
195+
return '/foo/[name][ext]';
196+
}
197+
return '/bar/[name][ext]';
198+
},
199+
},
200+
},
201+
};
202+
```
203+
174204
:::tip
175-
Except for `output.filename.js` and `output.filename.css`, other types of files currently do not support using functions.
205+
`output.filename.html` does not support using functions yet.
176206
:::
177207

178208
## Query hash

website/docs/zh/config/output/filename.mdx

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ type FilenameConfig = {
77
html?: string;
88
js?: string | Function;
99
css?: string | Function;
10-
svg?: string;
11-
font?: string;
12-
image?: string;
13-
media?: string;
14-
assets?: string;
10+
svg?: string | Function;
11+
font?: string | Function;
12+
image?: string | Function;
13+
media?: string | Function;
14+
assets?: string | Function;
1515
};
1616
```
1717

@@ -137,42 +137,72 @@ const { add } = await import(
137137

138138
## 使用函数
139139

140-
`output.filename.js``output.filename.css` 可以传入一个函数,这允许你根据文件信息动态生成文件名
140+
你可以传入一个函数来根据文件信息动态设置文件名
141141

142-
函数接收两个参数
142+
这个函数接收两个参数
143143

144144
- `pathData`:一个对象,包含文件路径信息。
145145
- `assetInfo`:一个可选的对象,包含额外的资源信息。
146146

147+
为 JavaScript 文件动态设置文件名:
148+
147149
```ts title="rsbuild.config.ts"
150+
const isProd = process.env.NODE_ENV === 'production';
151+
148152
export default {
149153
output: {
150154
filename: {
151155
js: (pathData, assetInfo) => {
152156
console.log(pathData); // 你可以在这里查看 pathData 的内容
153157

154158
if (pathData.chunk?.name === 'index') {
155-
const isProd = process.env.NODE_ENV === 'production';
156159
return isProd ? '[name].[contenthash:8].js' : '[name].js';
157160
}
158-
159161
return '/some-path/[name].js';
160162
},
163+
},
164+
},
165+
};
166+
```
167+
168+
为 CSS 文件动态设置文件名:
169+
170+
```ts title="rsbuild.config.ts"
171+
const isProd = process.env.NODE_ENV === 'production';
172+
173+
export default {
174+
output: {
175+
filename: {
161176
css: (pathData, assetInfo) => {
162177
if (pathData.chunk?.name === 'index') {
163-
const isProd = process.env.NODE_ENV === 'production';
164178
return isProd ? '[name].[contenthash:8].css' : '[name].css';
165179
}
166-
167180
return '/some-path/[name].css';
168181
},
169182
},
170183
},
171184
};
172185
```
173186

187+
为图片文件动态设置文件名:
188+
189+
```ts title="rsbuild.config.ts"
190+
export default {
191+
output: {
192+
filename: {
193+
image: (pathData) => {
194+
if (pathData.filename?.includes('foo')) {
195+
return '/foo/[name][ext]';
196+
}
197+
return '/bar/[name][ext]';
198+
},
199+
},
200+
},
201+
};
202+
```
203+
174204
:::tip
175-
除了 `output.filename.js``output.filename.css` 以外,其他类型的文件目前暂不支持使用函数
205+
`output.filename.html` 暂不支持使用函数
176206
:::
177207

178208
## Query hash

0 commit comments

Comments
 (0)