Skip to content

Commit a4da6af

Browse files
authored
fix(css): default splitting to true in unbundle mode for CSS inject (#867)
1 parent 03ade19 commit a4da6af

File tree

5 files changed

+56
-7
lines changed

5 files changed

+56
-7
lines changed

packages/css/src/options.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ describe('resolveCssOptions', () => {
6363
})
6464
})
6565

66+
test('splitting defaults to true when unbundle is true', () => {
67+
const result = resolveCssOptions({}, undefined, true)
68+
expect(result.splitting).toBe(true)
69+
})
70+
71+
test('explicit splitting=false overrides unbundle default', () => {
72+
const result = resolveCssOptions({ splitting: false }, undefined, true)
73+
expect(result.splitting).toBe(false)
74+
})
75+
6676
test('custom options are passed through', () => {
6777
const result = resolveCssOptions({
6878
transformer: 'postcss',

packages/css/src/options.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export interface CssOptions {
6161
* Enable/disable CSS code splitting.
6262
* When set to `false`, all CSS in the entire project will be extracted into a single CSS file named by {@link fileName}.
6363
* When set to `true`, CSS imported in async JS chunks will be preserved as chunks.
64-
* @default false
64+
*
65+
* Defaults to `false`, but if `unbundle` is `true`, it defaults to `true` to preserve chunk splitting.
6566
*/
6667
splitting?: boolean
6768

@@ -204,6 +205,7 @@ export const defaultCssBundleName = 'style.css'
204205
export function resolveCssOptions(
205206
options: CssOptions = {},
206207
topLevelTarget?: string[],
208+
unbundle?: boolean,
207209
): ResolvedCssOptions {
208210
let cssTarget: string[] | undefined
209211
if (options.target === false) {
@@ -216,7 +218,7 @@ export function resolveCssOptions(
216218

217219
return {
218220
transformer: options.transformer ?? 'lightningcss',
219-
splitting: options.splitting ?? false,
221+
splitting: options.splitting ?? !!unbundle,
220222
fileName: options.fileName ?? defaultCssBundleName,
221223
minify: options.minify ?? false,
222224
inject: options.inject ?? false,

packages/css/src/plugin.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function CssPlugin(
4141
{ logger }: { logger: Logger },
4242
): Plugin[] {
4343
const cssConfig: CssPluginConfig = {
44-
css: resolveCssOptions(config.css, config.target),
44+
css: resolveCssOptions(config.css, config.target, config.unbundle),
4545
cwd: config.cwd,
4646
target: config.target,
4747
}
@@ -198,19 +198,19 @@ export function CssPlugin(
198198
if (
199199
chunk.type !== 'chunk' ||
200200
chunk.exports.length ||
201-
!chunk.moduleIds.length ||
202-
chunk.isEntry ||
203-
chunk.isDynamicEntry
201+
!chunk.moduleIds.length
204202
)
205203
continue
206204
// Strict: all modules are CSS
207205
if (chunk.moduleIds.every((id) => styles.has(id))) {
208206
pureCssChunks.add(chunk.fileName)
209207
continue
210208
}
211-
// Relaxed: chunk has CSS modules and code is trivially empty
209+
// Relaxed: non-entry chunk has CSS modules and code is trivially empty
212210
// (e.g. a JS file whose only purpose is `import './foo.css'`)
213211
if (
212+
!chunk.isEntry &&
213+
!chunk.isDynamicEntry &&
214214
chunk.moduleIds.some((id) => styles.has(id)) &&
215215
isEmptyChunkCode(chunk.code)
216216
) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## button.css
2+
3+
```css
4+
.button {
5+
color: red;
6+
}
7+
8+
```
9+
10+
## index.mjs
11+
12+
```mjs
13+
import "./button.css";
14+
//#region index.ts
15+
const a = 1;
16+
//#endregion
17+
export { a };
18+
19+
```

tests/css.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,24 @@ describe('css', () => {
15231523
expect(asyncCode).not.toContain('empty css')
15241524
})
15251525

1526+
test('with unbundle=true', async (context) => {
1527+
const { outputFiles, fileMap } = await testBuild({
1528+
context,
1529+
files: {
1530+
'index.ts': `import './button.css'\nexport const a = 1`,
1531+
'button.css': `.button { color: red }`,
1532+
},
1533+
options: {
1534+
unbundle: true,
1535+
css: { inject: true },
1536+
},
1537+
})
1538+
expect(outputFiles).toContain('button.css')
1539+
expect(outputFiles).toContain('index.mjs')
1540+
expect(fileMap['index.mjs']).toContain('import "./button.css"')
1541+
expect(fileMap['index.mjs']).not.toContain('empty css')
1542+
})
1543+
15261544
test('rewrites shared css-only chunk imports to css files', async (context) => {
15271545
const { outputFiles, fileMap } = await testBuild({
15281546
context,

0 commit comments

Comments
 (0)