Skip to content

Commit 92a206b

Browse files
authoredNov 14, 2022
refactor: move CSS emitFile logic closer to rollup (#10909)
1 parent 65f1e4d commit 92a206b

File tree

4 files changed

+34
-300
lines changed

4 files changed

+34
-300
lines changed
 

‎packages/vite/src/node/__tests__/asset.spec.ts

-149
This file was deleted.

‎packages/vite/src/node/plugins/asset.ts

+7-118
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { Buffer } from 'node:buffer'
55
import * as mrmime from 'mrmime'
66
import type {
77
NormalizedOutputOptions,
8-
OutputOptions,
98
PluginContext,
10-
PreRenderedAsset,
119
RenderedChunk
1210
} from 'rollup'
1311
import MagicString from 'magic-string'
@@ -29,10 +27,15 @@ const urlRE = /(\?|&)url(?:&|$)/
2927
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
3028

3129
// chunk.name is the basename for the asset ignoring the directory structure
32-
// For the manifest, we need to preserve the original file path
30+
// For the manifest, we need to preserve the original file path and isEntry
31+
// for CSS assets. We keep a map from referenceId to this information.
32+
export interface GeneratedAssetMeta {
33+
originalName: string
34+
isEntry?: boolean
35+
}
3336
export const generatedAssets = new WeakMap<
3437
ResolvedConfig,
35-
Map<string, { originalName: string }>
38+
Map<string, GeneratedAssetMeta>
3639
>()
3740

3841
// add own dictionary entry by directly assigning mrmime
@@ -248,120 +251,6 @@ export function getPublicAssetFilename(
248251
return publicAssetUrlCache.get(config)?.get(hash)
249252
}
250253

251-
export function resolveAssetFileNames(
252-
config: ResolvedConfig
253-
): string | ((chunkInfo: PreRenderedAsset) => string) {
254-
const output = config.build?.rollupOptions?.output
255-
const defaultAssetFileNames = path.posix.join(
256-
config.build.assetsDir,
257-
'[name].[hash][extname]'
258-
)
259-
// Steps to determine which assetFileNames will be actually used.
260-
// First, if output is an object or string, use assetFileNames in it.
261-
// And a default assetFileNames as fallback.
262-
let assetFileNames: Exclude<OutputOptions['assetFileNames'], undefined> =
263-
(output && !Array.isArray(output) ? output.assetFileNames : undefined) ??
264-
defaultAssetFileNames
265-
if (output && Array.isArray(output)) {
266-
// Second, if output is an array, adopt assetFileNames in the first object.
267-
assetFileNames = output[0].assetFileNames ?? assetFileNames
268-
}
269-
return assetFileNames
270-
}
271-
272-
/**
273-
* converts the source filepath of the asset to the output filename based on the assetFileNames option. \
274-
* this function imitates the behavior of rollup.js. \
275-
* https://rollupjs.org/guide/en/#outputassetfilenames
276-
*
277-
* @example
278-
* ```ts
279-
* const content = Buffer.from('text');
280-
* const fileName = assetFileNamesToFileName(
281-
* 'assets/[name].[hash][extname]',
282-
* '/path/to/file.txt',
283-
* getHash(content),
284-
* content
285-
* )
286-
* // fileName: 'assets/file.982d9e3e.txt'
287-
* ```
288-
*
289-
* @param assetFileNames filename pattern. e.g. `'assets/[name].[hash][extname]'`
290-
* @param file filepath of the asset
291-
* @param contentHash hash of the asset. used for `'[hash]'` placeholder
292-
* @param content content of the asset. passed to `assetFileNames` if `assetFileNames` is a function
293-
* @returns output filename
294-
*/
295-
export function assetFileNamesToFileName(
296-
assetFileNames: Exclude<OutputOptions['assetFileNames'], undefined>,
297-
file: string,
298-
contentHash: string,
299-
content: string | Buffer
300-
): string {
301-
const basename = path.basename(file)
302-
303-
// placeholders for `assetFileNames`
304-
// `hash` is slightly different from the rollup's one
305-
const extname = path.extname(basename)
306-
const ext = extname.substring(1)
307-
const name = basename.slice(0, -extname.length)
308-
const hash = contentHash
309-
310-
if (typeof assetFileNames === 'function') {
311-
assetFileNames = assetFileNames({
312-
name: file,
313-
source: content,
314-
type: 'asset'
315-
})
316-
if (typeof assetFileNames !== 'string') {
317-
throw new TypeError('assetFileNames must return a string')
318-
}
319-
} else if (typeof assetFileNames !== 'string') {
320-
throw new TypeError('assetFileNames must be a string or a function')
321-
}
322-
323-
const fileName = assetFileNames.replace(
324-
/\[\w+\]/g,
325-
(placeholder: string): string => {
326-
switch (placeholder) {
327-
case '[ext]':
328-
return ext
329-
330-
case '[extname]':
331-
return extname
332-
333-
case '[hash]':
334-
return hash
335-
336-
case '[name]':
337-
return sanitizeFileName(name)
338-
}
339-
throw new Error(
340-
`invalid placeholder ${placeholder} in assetFileNames "${assetFileNames}"`
341-
)
342-
}
343-
)
344-
345-
return fileName
346-
}
347-
348-
// taken from https://github.com/rollup/rollup/blob/a8647dac0fe46c86183be8596ef7de25bc5b4e4b/src/utils/sanitizeFileName.ts
349-
// https://datatracker.ietf.org/doc/html/rfc2396
350-
// eslint-disable-next-line no-control-regex
351-
const INVALID_CHAR_REGEX = /[\x00-\x1F\x7F<>*#"{}|^[\]`;?:&=+$,]/g
352-
const DRIVE_LETTER_REGEX = /^[a-z]:/i
353-
function sanitizeFileName(name: string): string {
354-
const match = DRIVE_LETTER_REGEX.exec(name)
355-
const driveLetter = match ? match[0] : ''
356-
357-
// A `:` is only allowed as part of a windows drive letter (ex: C:\foo)
358-
// Otherwise, avoid them because they can refer to NTFS alternate data streams.
359-
return (
360-
driveLetter +
361-
name.substr(driveLetter.length).replace(INVALID_CHAR_REGEX, '_')
362-
)
363-
}
364-
365254
export const publicAssetUrlCache = new WeakMap<
366255
ResolvedConfig,
367256
// hash -> url

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

+10-18
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,14 @@ import {
5252
import type { Logger } from '../logger'
5353
import { addToHTMLProxyTransformResult } from './html'
5454
import {
55-
assetFileNamesToFileName,
5655
assetUrlRE,
5756
checkPublicFile,
5857
fileToUrl,
58+
generatedAssets,
5959
publicAssetUrlCache,
6060
publicAssetUrlRE,
6161
publicFileToBuiltUrl,
62-
renderAssetUrlInJS,
63-
resolveAssetFileNames
62+
renderAssetUrlInJS
6463
} from './asset'
6564
import type { ESBuildOptions } from './esbuild'
6665

@@ -153,8 +152,6 @@ export const removedPureCssFilesCache = new WeakMap<
153152
Map<string, RenderedChunk>
154153
>()
155154

156-
export const cssEntryFilesCache = new WeakMap<ResolvedConfig, Set<string>>()
157-
158155
const postcssConfigCache: Record<
159156
string,
160157
WeakMap<ResolvedConfig, PostCSSConfigResult | null>
@@ -190,7 +187,6 @@ export function cssPlugin(config: ResolvedConfig): Plugin {
190187
cssModulesCache.set(config, moduleCache)
191188

192189
removedPureCssFilesCache.set(config, new Map<string, RenderedChunk>())
193-
cssEntryFilesCache.set(config, new Set())
194190
},
195191

196192
async transform(raw, id, options) {
@@ -470,7 +466,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
470466
return null
471467
}
472468

473-
const cssEntryFiles = cssEntryFilesCache.get(config)!
474469
const publicAssetUrlMap = publicAssetUrlCache.get(config)!
475470

476471
// resolve asset URL placeholders to their built file URLs
@@ -547,24 +542,21 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
547542
const lang = path.extname(cssAssetName).slice(1)
548543
const cssFileName = ensureFileExt(cssAssetName, '.css')
549544

550-
if (chunk.isEntry && isPureCssChunk) cssEntryFiles.add(cssAssetName)
551-
552545
chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssAssetName)
553546
chunkCSS = await finalizeCss(chunkCSS, true, config)
554547

555548
// emit corresponding css file
556-
const fileHandle = this.emitFile({
557-
name: isPreProcessor(lang) ? cssAssetName : cssFileName,
558-
fileName: assetFileNamesToFileName(
559-
resolveAssetFileNames(config),
560-
cssFileName,
561-
getHash(chunkCSS),
562-
chunkCSS
563-
),
549+
const referenceId = this.emitFile({
550+
name: path.basename(cssFileName),
564551
type: 'asset',
565552
source: chunkCSS
566553
})
567-
chunk.viteMetadata.importedCss.add(this.getFileName(fileHandle))
554+
const originalName = isPreProcessor(lang) ? cssAssetName : cssFileName
555+
const isEntry = chunk.isEntry && isPureCssChunk
556+
generatedAssets
557+
.get(config)!
558+
.set(referenceId, { originalName, isEntry })
559+
chunk.viteMetadata.importedCss.add(this.getFileName(referenceId))
568560
} else if (!config.build.ssr) {
569561
// legacy build and inline css
570562

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

+17-15
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { OutputAsset, OutputChunk } from 'rollup'
33
import type { ResolvedConfig } from '..'
44
import type { Plugin } from '../plugin'
55
import { normalizePath } from '../utils'
6-
import { cssEntryFilesCache } from './css'
76
import { generatedAssets } from './asset'
7+
import type { GeneratedAssetMeta } from './asset'
88

99
export type Manifest = Record<string, ManifestChunk>
1010

@@ -101,21 +101,22 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
101101
return manifestChunk
102102
}
103103

104-
function createAsset(chunk: OutputAsset, src: string): ManifestChunk {
104+
function createAsset(
105+
asset: OutputAsset,
106+
src: string,
107+
isEntry?: boolean
108+
): ManifestChunk {
105109
const manifestChunk: ManifestChunk = {
106-
file: chunk.fileName,
110+
file: asset.fileName,
107111
src
108112
}
109-
110-
if (cssEntryFiles.has(chunk.name!)) manifestChunk.isEntry = true
111-
113+
if (isEntry) manifestChunk.isEntry = true
112114
return manifestChunk
113115
}
114116

115-
const cssEntryFiles = cssEntryFilesCache.get(config)!
116-
117-
const fileNameToAssetMeta = new Map<string, { originalName: string }>()
118-
generatedAssets.get(config)!.forEach((asset, referenceId) => {
117+
const fileNameToAssetMeta = new Map<string, GeneratedAssetMeta>()
118+
const assets = generatedAssets.get(config)!
119+
assets.forEach((asset, referenceId) => {
119120
const fileName = this.getFileName(referenceId)
120121
fileNameToAssetMeta.set(fileName, asset)
121122
})
@@ -128,17 +129,18 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
128129
manifest[getChunkName(chunk)] = createChunk(chunk)
129130
} else if (chunk.type === 'asset' && typeof chunk.name === 'string') {
130131
// Add every unique asset to the manifest, keyed by its original name
131-
const src =
132-
fileNameToAssetMeta.get(chunk.fileName)?.originalName ?? chunk.name
133-
const asset = createAsset(chunk, src)
132+
const assetMeta = fileNameToAssetMeta.get(chunk.fileName)
133+
const src = assetMeta?.originalName ?? chunk.name
134+
const asset = createAsset(chunk, src, assetMeta?.isEntry)
134135
manifest[src] = asset
135136
fileNameToAsset.set(chunk.fileName, asset)
136137
}
137138
}
138139

139-
// Add duplicate assets to the manifest
140-
fileNameToAssetMeta.forEach(({ originalName }, fileName) => {
140+
// Add deduplicated assets to the manifest
141+
assets.forEach(({ originalName }, referenceId) => {
141142
if (!manifest[originalName]) {
143+
const fileName = this.getFileName(referenceId)
142144
const asset = fileNameToAsset.get(fileName)
143145
if (asset) {
144146
manifest[originalName] = asset

0 commit comments

Comments
 (0)
Please sign in to comment.