Skip to content

Commit f289b9b

Browse files
feat(manifest): add assets field for standalone CSS entry points (#21015)
Co-authored-by: dalaoshu <165626830+shulaoda@users.noreply.github.com>
1 parent 9ebaeaa commit f289b9b

8 files changed

Lines changed: 110 additions & 48 deletions

File tree

docs/guide/backend-integration.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,10 @@ If you need a custom integration, you can follow the steps in this guide to conf
118118
file: string
119119
/**
120120
* The list of CSS files imported by this chunk
121-
*
122-
* This field is only present in JS chunks.
123121
*/
124122
css?: string[]
125123
/**
126124
* The list of asset files imported by this chunk, excluding CSS files
127-
*
128-
* This field is only present in JS chunks.
129125
*/
130126
assets?: string[]
131127
/**
@@ -164,7 +160,7 @@ If you need a custom integration, you can follow the steps in this guide to conf
164160
- **Asset chunks**: Generated from imported assets like images, fonts. Their key is the relative src path from project root.
165161
- **CSS files**: When [`build.cssCodeSplit`](/config/build-options.md#build-csscodesplit) is `false`, a single CSS file is generated with the key `style.css`. When `build.cssCodeSplit` is not `false`, the key is generated similar to JS chunks (i.e. entry chunks will not have `_` prefix and non-entry chunks will have `_` prefix).
166162

167-
JS chunks (chunks other than assets or CSS) will contain information on their static and dynamic imports (both are keys that map to the corresponding chunk in the manifest), and also their corresponding CSS and asset files (if any).
163+
JS chunks (chunks other than assets or CSS) will contain information on their static and dynamic imports (both are keys that map to the corresponding chunk in the manifest). Chunks also list their corresponding CSS and asset files if they have any.
168164

169165
4. You can use this file to render links or preload directives with hashed filenames.
170166

packages/vite/src/node/build.ts

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
LogOrStringHandler,
99
MinimalPluginContext,
1010
ModuleFormat,
11+
OutputAsset,
1112
OutputBundle,
1213
OutputChunk,
1314
OutputOptions,
@@ -26,7 +27,7 @@ import { viteLoadFallbackPlugin as nativeLoadFallbackPlugin } from 'rolldown/exp
2627
import type { EsbuildTarget } from '#types/internal/esbuildOptions'
2728
import type { RollupCommonJSOptions } from '#dep-types/commonjs'
2829
import type { RollupDynamicImportVarsOptions } from '#dep-types/dynamicImportVars'
29-
import type { ChunkMetadata } from '#types/metadata'
30+
import type { AssetMetadata, ChunkMetadata } from '#types/metadata'
3031
import {
3132
DEFAULT_ASSETS_INLINE_LIMIT,
3233
ESBUILD_BASELINE_WIDELY_AVAILABLE_TARGET,
@@ -863,9 +864,7 @@ async function buildEnvironment(
863864
}
864865
for (const output of res) {
865866
for (const chunk of output.output) {
866-
if (chunk.type === 'chunk') {
867-
injectChunkMetadata(chunkMetadataMap, chunk)
868-
}
867+
injectChunkMetadata(chunkMetadataMap, chunk)
869868
}
870869
}
871870
logger.info(
@@ -1187,26 +1186,35 @@ function isExternal(id: string, test: string | RegExp) {
11871186
}
11881187

11891188
export class ChunkMetadataMap {
1190-
private _inner = new Map<string, ChunkMetadata>()
1189+
private _inner = new Map<string, ChunkMetadata | AssetMetadata>()
11911190
private _resetChunks = new Set<string>()
11921191

1193-
private _getKey(chunk: RenderedChunk | OutputChunk): string {
1192+
private _getKey(chunk: RenderedChunk | OutputChunk | OutputAsset): string {
11941193
return 'preliminaryFileName' in chunk
11951194
? chunk.preliminaryFileName
11961195
: chunk.fileName
11971196
}
11981197

1199-
private _getDefaultValue(chunk: RenderedChunk | OutputChunk): ChunkMetadata {
1200-
return {
1201-
importedAssets: new Set(),
1202-
importedCss: new Set(),
1203-
// NOTE: adding this as a workaround for now ideally we'd want to remove this workaround
1204-
// use shared `chunk.modules` object to allow mutation on js side plugins
1205-
__modules: chunk.modules,
1206-
}
1198+
private _getDefaultValue(
1199+
chunk: RenderedChunk | OutputChunk | OutputAsset,
1200+
): ChunkMetadata | AssetMetadata {
1201+
return chunk.type === 'chunk'
1202+
? {
1203+
importedAssets: new Set(),
1204+
importedCss: new Set(),
1205+
// NOTE: adding this as a workaround for now ideally we'd want to remove this workaround
1206+
// use shared `chunk.modules` object to allow mutation on js side plugins
1207+
__modules: chunk.modules,
1208+
}
1209+
: {
1210+
importedAssets: new Set(),
1211+
importedCss: new Set(),
1212+
}
12071213
}
12081214

1209-
get(chunk: RenderedChunk | OutputChunk): ChunkMetadata {
1215+
get(
1216+
chunk: RenderedChunk | OutputChunk | OutputAsset,
1217+
): ChunkMetadata | AssetMetadata {
12101218
const key = this._getKey(chunk)
12111219
if (!this._inner.has(key)) {
12121220
this._inner.set(key, this._getDefaultValue(chunk))
@@ -1215,7 +1223,7 @@ export class ChunkMetadataMap {
12151223
}
12161224

12171225
// reset chunk metadata on the first RenderChunk call for watch mode
1218-
reset(chunk: RenderedChunk | OutputChunk): void {
1226+
reset(chunk: RenderedChunk | OutputChunk | OutputAsset): void {
12191227
const key = this._getKey(chunk)
12201228
if (this._resetChunks.has(key)) return
12211229

@@ -1418,9 +1426,7 @@ function wrapEnvironmentHook<HookName extends keyof Plugin>(
14181426
if (hookName === 'generateBundle' || hookName === 'writeBundle') {
14191427
const bundle = args[1] as OutputBundle
14201428
for (const chunk of Object.values(bundle)) {
1421-
if (chunk.type === 'chunk') {
1422-
injectChunkMetadata(chunkMetadataMap, chunk)
1423-
}
1429+
injectChunkMetadata(chunkMetadataMap, chunk)
14241430
}
14251431
}
14261432
return fn.call(injectEnvironmentInContext(this, environment), ...args)
@@ -1435,7 +1441,7 @@ function wrapEnvironmentHook<HookName extends keyof Plugin>(
14351441

14361442
function injectChunkMetadata(
14371443
chunkMetadataMap: ChunkMetadataMap,
1438-
chunk: RenderedChunk | OutputChunk,
1444+
chunk: RenderedChunk | OutputChunk | OutputAsset,
14391445
resetChunkMetadata = false,
14401446
) {
14411447
if (resetChunkMetadata) {
@@ -1447,12 +1453,14 @@ function injectChunkMetadata(
14471453
value: chunkMetadataMap.get(chunk),
14481454
enumerable: true,
14491455
})
1450-
Object.defineProperty(chunk, 'modules', {
1451-
get() {
1452-
return chunk.viteMetadata!.__modules
1453-
},
1454-
enumerable: true,
1455-
})
1456+
if (chunk.type === 'chunk') {
1457+
Object.defineProperty(chunk, 'modules', {
1458+
get() {
1459+
return chunk.viteMetadata!.__modules
1460+
},
1461+
enumerable: true,
1462+
})
1463+
}
14561464
}
14571465

14581466
function injectEnvironmentInContext<Context extends MinimalPluginContext>(

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1108,7 +1108,25 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
11081108

11091109
const removedPureCssFiles = removedPureCssFilesCache.get(config)!
11101110
pureCssChunkNames.forEach((fileName) => {
1111-
removedPureCssFiles.set(fileName, bundle[fileName] as RenderedChunk)
1111+
const emptyJsPlaceholder = bundle[fileName] as RenderedChunk
1112+
if (emptyJsPlaceholder.isEntry) {
1113+
const { importedAssets, importedCss } =
1114+
emptyJsPlaceholder.viteMetadata!
1115+
const cssReferenceId = cssEntriesMap
1116+
.get(this.environment)!
1117+
.get(emptyJsPlaceholder.name)!
1118+
const realCssEntryName = this.getFileName(cssReferenceId)
1119+
const realCssEntry = bundle[realCssEntryName]!
1120+
importedCss.delete(realCssEntryName)
1121+
if (importedAssets.size) {
1122+
realCssEntry.viteMetadata!.importedAssets = importedAssets
1123+
}
1124+
if (importedCss.size) {
1125+
realCssEntry.viteMetadata!.importedCss = importedCss
1126+
}
1127+
}
1128+
1129+
removedPureCssFiles.set(fileName, emptyJsPlaceholder)
11121130
delete bundle[fileName]
11131131
delete bundle[`${fileName}.map`]
11141132
})

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

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,10 @@ export interface ManifestChunk {
2222
file: string
2323
/**
2424
* The list of CSS files imported by this chunk
25-
*
26-
* This field is only present in JS chunks.
2725
*/
2826
css?: string[]
2927
/**
3028
* The list of asset files imported by this chunk, excluding CSS files
31-
*
32-
* This field is only present in JS chunks.
3329
*/
3430
assets?: string[]
3531
/**
@@ -114,20 +110,39 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
114110
const asset = bundle[outPath]
115111
if (asset.type === 'asset') {
116112
let manifest: Manifest | undefined
117-
for (const chunk of Object.values(bundle)) {
118-
if (chunk.type !== 'chunk') continue
119-
const importedCss = chunk.viteMetadata?.importedCss
120-
const importedAssets = chunk.viteMetadata?.importedAssets
113+
for (const output of Object.values(bundle)) {
114+
const importedCss = output.viteMetadata?.importedCss
115+
const importedAssets = output.viteMetadata?.importedAssets
121116
if (!importedCss?.size && !importedAssets?.size) continue
122117
manifest ??= JSON.parse(asset.source.toString()) as Manifest
123-
const name = getChunkName(chunk)
124-
const item = manifest[name]
125-
if (!item) continue
126-
if (importedCss?.size) {
127-
item.css = [...importedCss]
128-
}
129-
if (importedAssets?.size) {
130-
item.assets = [...importedAssets]
118+
if (output.type === 'chunk') {
119+
const item = manifest[getChunkName(output)]
120+
if (!item) continue
121+
if (importedCss?.size) {
122+
item.css = [...importedCss]
123+
}
124+
if (importedAssets?.size) {
125+
item.assets = [...importedAssets]
126+
}
127+
} else if (output.type === 'asset' && output.names.length > 0) {
128+
// Add every unique asset to the manifest, keyed by its original name
129+
const keys =
130+
output.originalFileNames.length > 0
131+
? output.originalFileNames
132+
: [`_${path.basename(output.fileName)}`]
133+
134+
for (const key of keys) {
135+
const item = manifest[key]
136+
if (!item) continue
137+
if (!(item.file && endsWithJSRE.test(item.file))) {
138+
if (importedCss?.size) {
139+
item.css = [...importedCss]
140+
}
141+
if (importedAssets?.size) {
142+
item.assets = [...importedAssets]
143+
}
144+
}
145+
}
131146
}
132147
}
133148
const output =
@@ -248,6 +263,13 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
248263
manifestChunk.name = name
249264
// @ts-expect-error keep names field for backward compatibility
250265
manifestChunk.names = asset.names
266+
267+
if (asset.viteMetadata?.importedCss.size) {
268+
manifestChunk.css = [...asset.viteMetadata.importedCss]
269+
}
270+
if (asset.viteMetadata?.importedAssets.size) {
271+
manifestChunk.assets = [...asset.viteMetadata.importedAssets]
272+
}
251273
}
252274
return manifestChunk
253275
}

packages/vite/types/metadata.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
export interface AssetMetadata {
2+
importedAssets: Set<string>
3+
importedCss: Set<string>
4+
}
5+
16
export interface ChunkMetadata {
27
importedAssets: Set<string>
38
importedCss: Set<string>
@@ -25,6 +30,10 @@ export interface CustomPluginOptionsVite {
2530
}
2631

2732
declare module 'rolldown' {
33+
export interface OutputAsset {
34+
viteMetadata?: AssetMetadata
35+
}
36+
2837
export interface RenderedChunk {
2938
viteMetadata?: ChunkMetadata
3039
}

playground/backend-integration/__tests__/backend-integration.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ describe.runIf(isBuild)('build', () => {
7676
// use the entry name
7777
expect(dirFooAssetEntry.file).toMatch('assets/bar-')
7878
expect(dirFooAssetEntry.name).toStrictEqual('bar.css')
79+
expect(dirFooAssetEntry.assets.length).toEqual(1)
7980
expect(customNameAssetEntry.name).toStrictEqual('bar.custom')
8081
expect(iconEntrypointEntry?.file).not.toBeUndefined()
8182
expect(waterContainerEntry?.file).not.toBeUndefined()
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
.windows-path-foo {
22
color: blue;
33
}
4+
.foo-background-image {
5+
background-image: url('./foo-background.svg?no-inline');
6+
}

0 commit comments

Comments
 (0)