Skip to content

Commit bac9f3e

Browse files
Copilotsapphi-redbluwy
authored
fix(css): empty CSS entry points should generate CSS files, not JS files (#20518)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: sapphi-red <[email protected]> Co-authored-by: bluwy <[email protected]>
1 parent ddd76e0 commit bac9f3e

File tree

10 files changed

+30
-16
lines changed

10 files changed

+30
-16
lines changed

packages/vite/src/node/plugins/css.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
620620
},
621621

622622
async renderChunk(code, chunk, opts, meta) {
623-
let chunkCSS = ''
623+
let chunkCSS: string | undefined
624624
const renderedModules = new Proxy(
625625
{} as Record<string, RenderedModule | undefined>,
626626
{
@@ -661,7 +661,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
661661
isPureCssChunk = false
662662
}
663663

664-
chunkCSS += styles.get(id)
664+
chunkCSS = (chunkCSS || '') + styles.get(id)
665665
} else if (!isJsChunkEmpty) {
666666
// if the module does not have a style, then it's not a pure css chunk.
667667
// this is true because in the `transform` hook above, only modules
@@ -824,7 +824,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
824824
}
825825
}
826826

827-
if (chunkCSS) {
827+
if (chunkCSS !== undefined) {
828828
if (isPureCssChunk && (opts.format === 'es' || opts.format === 'cjs')) {
829829
// this is a shared CSS-only chunk that is empty.
830830
pureCssChunks.add(chunk)
@@ -852,7 +852,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
852852

853853
// wait for previous tasks as well
854854
chunkCSS = await codeSplitEmitQueue.run(async () => {
855-
return finalizeCss(chunkCSS, true, config)
855+
return finalizeCss(chunkCSS!, true, config)
856856
})
857857

858858
// emit corresponding css file

playground/css-codesplit-cjs/__tests__/css-codesplit-cjs.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test('should load both stylesheets', async () => {
88

99
describe.runIf(isBuild)('build', () => {
1010
test('should remove empty chunk', async () => {
11-
expect(findAssetFile(/style.*\.js$/)).toBe('')
11+
expect(findAssetFile(/style.*\.js$/)).toBeUndefined()
1212
expect(findAssetFile('main.*.js$')).toMatch(`/* empty css`)
1313
expect(findAssetFile('other.*.js$')).toMatch(`/* empty css`)
1414
})

playground/css-codesplit/__tests__/css-codesplit-consistent.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ for (let i = 0; i < 5; i++) {
99
describe.runIf(isBuild)('css-codesplit build', () => {
1010
test('should be consistent with same content', () => {
1111
expect(findAssetFile(/style-.+\.css/)).toMatch('h2{color:#00f}')
12-
expect(findAssetFile(/style2-.+\.css/)).toBe('')
12+
expect(findAssetFile(/style2-.+\.css/)).toBeUndefined()
1313
})
1414
})
1515
}

playground/css-codesplit/__tests__/css-codesplit.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ test('style order should be consistent when style tag is inserted by JS', async
3838

3939
describe.runIf(isBuild)('build', () => {
4040
test('should remove empty chunk', async () => {
41-
expect(findAssetFile(/style-.*\.js$/)).toBe('')
41+
expect(findAssetFile(/style-.*\.js$/)).toBeUndefined()
4242
expect(findAssetFile('main.*.js$')).toMatch(`/* empty css`)
4343
expect(findAssetFile('other.*.js$')).toMatch(`/* empty css`)
44-
expect(findAssetFile(/async-[-\w]{8}\.js$/)).toBe('')
44+
expect(findAssetFile(/async-[-\w]{8}\.js$/)).toBeUndefined()
4545

4646
const assets = listAssets()
4747
expect(assets).not.toContainEqual(

playground/css-sourcemap/__tests__/lib-entry/css-sourcemap-lib-entry.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { findAssetFile, isBuild } from '~utils'
33

44
describe.runIf(isBuild)('css lib entry', () => {
55
test('remove useless js sourcemap', async () => {
6-
expect(findAssetFile('linked.js.map', 'lib-entry', './')).toBe('')
6+
expect(findAssetFile('linked.js.map', 'lib-entry', './')).toBeUndefined()
77
})
88
})

playground/css/__tests__/tests.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ export const tests = (isLightningCSS: boolean) => {
308308
'.async{color:plum}',
309309
)
310310
// should have no chunk!
311-
expect(findAssetFile(/async-treeshaken/)).toBe('')
311+
expect(findAssetFile(/async-treeshaken/)).toBeUndefined()
312312
} else {
313313
// should be present in dev
314314
const el = await page.$('.async-treeshaken')
@@ -519,7 +519,11 @@ export const tests = (isLightningCSS: boolean) => {
519519
test.runIf(isBuild)('manual chunk path', async () => {
520520
// assert that the manual-chunk css is output in the directory specified in manualChunk (#12072)
521521
expect(
522-
findAssetFile(/dir\/dir2\/manual-chunk-[-\w]{8}\.css$/),
522+
findAssetFile(
523+
/manual-chunk-[-\w]{8}\.css$/,
524+
undefined,
525+
'assets/dir/dir2',
526+
),
523527
).not.toBeUndefined()
524528
})
525529

@@ -539,4 +543,13 @@ export const tests = (isLightningCSS: boolean) => {
539543
expect(await getColor('.treeshake-scoped-order')).toBe('red')
540544
expect(await getBgColor('.treeshake-scoped-order')).toBe('blue')
541545
})
546+
547+
test.runIf(isBuild)(
548+
'empty CSS files should generate .css assets, not .js assets',
549+
() => {
550+
// Check that empty CSS entry point generates a .css file, not a .js file
551+
expect(findAssetFile(/empty-[-\w]{8}\.css$/)).not.toBeUndefined()
552+
expect(findAssetFile(/empty-[-\w]{8}\.js$/)).toBeUndefined()
553+
},
554+
)
542555
}

playground/css/empty.css

Whitespace-only changes.

playground/css/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default defineConfig({
4646
__dirname,
4747
'./treeshake-scoped/index.html',
4848
),
49+
empty: path.resolve(__dirname, './empty.css'),
4950
},
5051
output: {
5152
manualChunks(id) {

playground/ssr-webworker/__tests__/ssr-webworker.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ test('supports nodejs_compat', async () => {
3232

3333
test.runIf(isBuild)('inlineDynamicImports', () => {
3434
const dynamicJsContent = findAssetFile(/dynamic-[-\w]+\.js/, 'worker')
35-
expect(dynamicJsContent).toBe('')
35+
expect(dynamicJsContent).toBeUndefined()
3636
})

playground/test-utils.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,14 +189,14 @@ export function findAssetFile(
189189
base = '',
190190
assets = 'assets',
191191
matchAll = false,
192-
): string {
192+
): string | undefined {
193193
const assetsDir = path.join(testDir, 'dist', base, assets)
194194
let files: string[]
195195
try {
196196
files = fs.readdirSync(assetsDir)
197197
} catch (e) {
198198
if (e.code === 'ENOENT') {
199-
return ''
199+
return undefined
200200
}
201201
throw e
202202
}
@@ -208,12 +208,12 @@ export function findAssetFile(
208208
fs.readFileSync(path.resolve(assetsDir, file), 'utf-8'),
209209
)
210210
.join('')
211-
: ''
211+
: undefined
212212
} else {
213213
const matchedFile = files.find((file) => file.match(match))
214214
return matchedFile
215215
? fs.readFileSync(path.resolve(assetsDir, matchedFile), 'utf-8')
216-
: ''
216+
: undefined
217217
}
218218
}
219219

0 commit comments

Comments
 (0)