Skip to content

Commit 18f603e

Browse files
authored
feat: add cssModules.mode option (#2459)
1 parent a5ba0f4 commit 18f603e

File tree

7 files changed

+220
-99
lines changed

7 files changed

+220
-99
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { build } from '@e2e/helper';
2+
import { expect, test } from '@playwright/test';
3+
4+
test('should compile CSS Modules global mode correctly', async () => {
5+
const rsbuild = await build({
6+
cwd: __dirname,
7+
rsbuildConfig: {
8+
output: {
9+
cssModules: {
10+
mode: 'global',
11+
},
12+
},
13+
},
14+
});
15+
const files = await rsbuild.unwrapOutputJSON();
16+
17+
const content =
18+
files[Object.keys(files).find((file) => file.endsWith('.css'))!];
19+
20+
expect(content).toEqual(
21+
'.foo{position:relative}.foo .bar,.foo .baz{height:100%;overflow:hidden}.foo .lol{width:80%}',
22+
);
23+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.foo {
2+
position: relative;
3+
}
4+
5+
.foo .bar,
6+
.foo .baz {
7+
height: 100%;
8+
overflow: hidden;
9+
}
10+
11+
.foo .lol {
12+
width: 80%;
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './a.module.css';

packages/core/src/plugins/css.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,9 @@ const getCSSLoaderOptions = ({
219219
const defaultOptions: CSSLoaderOptions = {
220220
importLoaders,
221221
modules: {
222-
auto: cssModules.auto,
223-
namedExport: false,
224-
exportGlobals: cssModules.exportGlobals,
225-
exportLocalsConvention: cssModules.exportLocalsConvention,
222+
...cssModules,
226223
localIdentName,
224+
namedExport: false,
227225
},
228226
sourceMap: config.output.sourceMap.css,
229227
};

packages/shared/src/types/config/output.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {
33
Externals,
44
SwcJsMinimizerRspackPluginOptions,
55
} from '@rspack/core';
6-
import type { HTMLPluginOptions } from '../../types';
6+
import type { CSSLoaderModulesOptions, HTMLPluginOptions } from '../../types';
77
import type { RsbuildTarget } from '../rsbuild';
88
import type { RspackConfig } from '../rspack';
99

@@ -147,26 +147,23 @@ export type CSSModules = {
147147
/**
148148
* Allows CSS Modules to be automatically enabled based on their filenames.
149149
*/
150-
auto?:
151-
| boolean
152-
| RegExp
153-
| ((
154-
resourcePath: string,
155-
resourceQuery: string,
156-
resourceFragment: string,
157-
) => boolean);
150+
auto?: CSSLoaderModulesOptions['auto'];
158151
/**
159152
* Allows exporting names from global class names, so you can use them via import.
160153
*/
161154
exportGlobals?: boolean;
155+
/**
156+
* Style of exported class names.
157+
*/
158+
exportLocalsConvention?: CSSModulesLocalsConvention;
162159
/**
163160
* Set the local ident name of CSS Modules.
164161
*/
165162
localIdentName?: string;
166163
/**
167-
* Style of exported class names.
164+
* Controls the level of compilation applied to the input styles.
168165
*/
169-
exportLocalsConvention?: CSSModulesLocalsConvention;
166+
mode?: CSSLoaderModulesOptions['mode'];
170167
};
171168

172169
export type Minify =
@@ -329,9 +326,10 @@ export interface NormalizedOutputConfig extends OutputConfig {
329326
injectStyles: boolean;
330327
cssModules: {
331328
auto: CSSModules['auto'];
332-
localIdentName?: string;
333329
exportGlobals: boolean;
334330
exportLocalsConvention: CSSModulesLocalsConvention;
331+
localIdentName?: string;
332+
mode?: CSSModules['mode'];
335333
};
336334
emitAssets: EmitAssets;
337335
}

website/docs/en/config/output/css-modules.mdx

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
11
# output.cssModules
22

3-
- **Type:**
4-
5-
```ts
6-
type CSSModules = {
7-
auto?:
8-
| boolean
9-
| RegExp
10-
| ((
11-
resourcePath: string,
12-
resourceQuery: string,
13-
resourceFragment: string,
14-
) => boolean);
15-
localIdentName?: string;
16-
exportLocalsConvention?: CSSModulesLocalsConvention;
17-
};
18-
```
3+
- **Type:** `CSSModules`
194

205
For custom CSS Modules configuration.
216

@@ -94,6 +79,48 @@ export default {
9479
};
9580
```
9681

82+
## cssModules.exportGlobals
83+
84+
- **Type:** `boolean`
85+
- **Default:** `false`
86+
87+
Allows exporting names from global class names, so you can use them via import.
88+
89+
### Example
90+
91+
Set the `exportGlobals` to `true`:
92+
93+
```ts
94+
export default {
95+
output: {
96+
cssModules: {
97+
exportGlobals: true,
98+
},
99+
},
100+
};
101+
```
102+
103+
Use `:global()` in CSS Modules:
104+
105+
```css title="style.module.css"
106+
:global(.blue) {
107+
color: blue;
108+
}
109+
110+
.red {
111+
color: red;
112+
}
113+
```
114+
115+
Then you can import the class name wrapped with `:global()`:
116+
117+
```tsx title="Button.tsx"
118+
import styles from './style.module.css';
119+
120+
console.log(styles.blue); // 'blue'
121+
console.log(styles.red); // 'red-[hash]'
122+
```
123+
97124
## cssModules.localIdentName
98125

99126
- **Type:** `string`
@@ -149,44 +176,59 @@ export default {
149176
};
150177
```
151178

152-
## cssModules.exportGlobals
179+
## cssModules.mode
153180

154-
- **Type:** `boolean`
155-
- **Default:** `false`
181+
- **Type:**
156182

157-
Allows exporting names from global class names, so you can use them via import.
183+
```ts
184+
type Mode =
185+
| 'local'
186+
| 'global'
187+
| 'pure'
188+
| 'icss'
189+
| ((resourcePath: string) => 'local' | 'global' | 'pure' | 'icss');
190+
```
158191

159-
### Example
192+
- **Default:** `'local'`
160193

161-
Set the `exportGlobals` to `true`:
194+
Controls the mode of compilation applied to the CSS Modules.
195+
196+
### Optional values
197+
198+
`cssModules.mode` can take one of the following values:
199+
200+
1. `'local'` (default): This enables the CSS Modules specification for scoping CSS locally. Class and ID selectors are rewritten to be module-scoped, and `@value` bindings are injected.
201+
2. `'global'`: This opts-out of the CSS Modules behavior, disabling both local scoping and injecting `@value` bindings. Global selectors are preserved as-is.
202+
3. `'pure'`: This enables dead-code elimination by removing any unused local classnames and values from the final CSS. It still performs local scoping and `@value` injection.
203+
4. `'icss'`: This compiles to the low-level Interoperable CSS format, which provides a syntax for declaring `:import` and `:export` dependencies between CSS and other languages. It does not perform any scoping or `@value` injection.
204+
205+
The `'local'` mode is the most common use case for CSS Modules, enabling modular and locally-scoped styles within components. The other modes may be used in specific scenarios.
206+
207+
For example:
162208

163209
```ts
164210
export default {
165211
output: {
166212
cssModules: {
167-
exportGlobals: true,
213+
mode: 'global',
168214
},
169215
},
170216
};
171217
```
172218

173-
Use `:global()` in CSS Modules:
219+
### Function
174220

175-
```css title="style.module.css"
176-
:global(.blue) {
177-
color: blue;
178-
}
221+
You can also pass a function to `modules.mode` that determines the mode based on the resource path, query, or fragment. This allows you to use different modes for different files.
179222

180-
.red {
181-
color: red;
182-
}
183-
```
184-
185-
Then you can import the class name wrapped with `:global()`:
223+
For example, to use local scoping for `.module.css` files and global styles for other files:
186224

187-
```tsx title="Button.tsx"
188-
import styles from './style.module.css';
189-
190-
console.log(styles.blue); // 'blue'
191-
console.log(styles.red); // 'red-[hash]'
225+
```js
226+
modules: {
227+
mode: (resourcePath) => {
228+
if (/\.module\.\css$/.test(resourcePath)) {
229+
return 'local';
230+
}
231+
return 'global';
232+
};
233+
}
192234
```

0 commit comments

Comments
 (0)