Skip to content

Commit 3e3c203

Browse files
committed
fix: do not include css string in bundle if not needed
This reduces bundle size when using esbuild as minifier.
1 parent 15bb5a1 commit 3e3c203

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const cssModuleRE = new RegExp(`\\.module${cssLangs}`)
8888
const directRequestRE = /(\?|&)direct\b/
8989
const commonjsProxyRE = /\?commonjs-proxy/
9090
const inlineRE = /(\?|&)inline\b/
91+
const usedRE = /(\?|&)used\b/
9192

9293
const enum PreprocessLang {
9394
less = 'less',
@@ -315,7 +316,11 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
315316
}
316317

317318
return {
318-
code: modulesCode || `export default ${JSON.stringify(css)}`,
319+
code:
320+
modulesCode ||
321+
(usedRE.test(id)
322+
? `export default ${JSON.stringify(css)}`
323+
: `export default ''`),
319324
map: { mappings: '' },
320325
// avoid the css module from being tree-shaken so that we can retrieve
321326
// it in renderChunk()

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

+27-7
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ import { Plugin } from '../plugin'
44
import MagicString from 'magic-string'
55
import { ImportSpecifier, init, parse as parseImports } from 'es-module-lexer'
66
import { OutputChunk } from 'rollup'
7-
import { chunkToEmittedCssFileMap, removedPureCssFilesCache } from './css'
7+
import {
8+
chunkToEmittedCssFileMap,
9+
isCSSRequest,
10+
removedPureCssFilesCache
11+
} from './css'
812
import { transformImportGlob } from '../importGlob'
13+
import { bareImportRE } from '../utils'
914

1015
/**
1116
* A flag for injected helpers. This flag will be set to `false` if the output
@@ -116,7 +121,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
116121
let imports: readonly ImportSpecifier[] = []
117122
try {
118123
imports = parseImports(source)[0]
119-
} catch (e) {
124+
} catch (e: any) {
120125
this.error(e, e.idx)
121126
}
122127

@@ -133,15 +138,15 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
133138
s: start,
134139
e: end,
135140
ss: expStart,
141+
n: specifier,
136142
d: dynamicIndex
137143
} = imports[index]
138144

139-
const isGlob =
145+
// import.meta.glob
146+
if (
140147
source.slice(start, end) === 'import.meta' &&
141148
source.slice(end, end + 5) === '.glob'
142-
143-
// import.meta.glob
144-
if (isGlob) {
149+
) {
145150
const { importsString, exp, endIndex, isEager } =
146151
await transformImportGlob(
147152
source,
@@ -167,6 +172,21 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
167172
const replacement = `${preloadMethod}(() => ${original},${isModernFlag}?"${preloadMarker}":void 0)`
168173
str().overwrite(dynamicIndex, dynamicEnd, replacement)
169174
}
175+
176+
// Differentiate CSS imports that use the default export from those that
177+
// do not by injecting a ?used query - this allows us to avoid including
178+
// the CSS string when unnecessary (esbuild has trouble treeshaking
179+
// them)
180+
if (
181+
specifier &&
182+
isCSSRequest(specifier) &&
183+
source.slice(expStart, start).includes('from') &&
184+
// edge case for package names ending with .css (e.g normalize.css)
185+
!(bareImportRE.test(specifier) && !specifier.includes('/'))
186+
) {
187+
const url = specifier.replace(/\?|$/, (m) => `?used${m ? '&' : ''}`)
188+
str().overwrite(start, end, dynamicIndex > -1 ? `'${url}'` : url)
189+
}
170190
}
171191

172192
if (
@@ -225,7 +245,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
225245
let imports: ImportSpecifier[]
226246
try {
227247
imports = parseImports(code)[0].filter((i) => i.d > -1)
228-
} catch (e) {
248+
} catch (e: any) {
229249
this.error(e, e.idx)
230250
}
231251

0 commit comments

Comments
 (0)