diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 2a119808a4eb72..8f1f3516c36387 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -2,7 +2,7 @@ import path from 'path' import { parse as parseUrl } from 'url' import fs, { promises as fsp } from 'fs' import * as mrmime from 'mrmime' -import type { OutputOptions, PluginContext } from 'rollup' +import type { OutputOptions, PluginContext, PreRenderedAsset } from 'rollup' import MagicString from 'magic-string' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' @@ -217,6 +217,27 @@ export function getAssetFilename( return assetHashToFilenameMap.get(config)?.get(hash) } +export function resolveAssetFileNames( + config: ResolvedConfig +): string | ((chunkInfo: PreRenderedAsset) => string) { + const output = config.build?.rollupOptions?.output + const defaultAssetFileNames = path.posix.join( + config.build.assetsDir, + '[name].[hash][extname]' + ) + // Steps to determine which assetFileNames will be actually used. + // First, if output is an object or string, use assetFileNames in it. + // And a default assetFileNames as fallback. + let assetFileNames: Exclude = + (output && !Array.isArray(output) ? output.assetFileNames : undefined) ?? + defaultAssetFileNames + if (output && Array.isArray(output)) { + // Second, if output is an array, adopt assetFileNames in the first object. + assetFileNames = output[0].assetFileNames ?? assetFileNames + } + return assetFileNames +} + /** * converts the source filepath of the asset to the output filename based on the assetFileNames option. \ * this function imitates the behavior of rollup.js. \ @@ -364,25 +385,9 @@ async function fileToBuiltUrl( const contentHash = getHash(content) const { search, hash } = parseUrl(id) const postfix = (search || '') + (hash || '') - const output = config.build?.rollupOptions?.output - - const defaultAssetFileNames = path.posix.join( - config.build.assetsDir, - '[name].[hash][extname]' - ) - // Steps to determine which assetFileNames will be actually used. - // First, if output is an object or string, use assetFileNames in it. - // And a default assetFileNames as fallback. - let assetFileNames: Exclude = - (output && !Array.isArray(output) ? output.assetFileNames : undefined) ?? - defaultAssetFileNames - if (output && Array.isArray(output)) { - // Second, if output is an array, adopt assetFileNames in the first object. - assetFileNames = output[0].assetFileNames ?? assetFileNames - } const fileName = assetFileNamesToFileName( - assetFileNames, + resolveAssetFileNames(config), file, contentHash, content diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index a2728507093401..0d057fa4750238 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -46,13 +46,15 @@ import { import type { Logger } from '../logger' import { addToHTMLProxyTransformResult } from './html' import { + assetFileNamesToFileName, assetUrlRE, checkPublicFile, fileToUrl, getAssetFilename, publicAssetUrlCache, publicAssetUrlRE, - publicFileToBuiltUrl + publicFileToBuiltUrl, + resolveAssetFileNames } from './asset' // const debug = createDebugger('vite:css') @@ -487,13 +489,22 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { return chunkCSS } + function ensureFileExt(name: string, ext: string) { + return path.format({ ...path.parse(name), base: undefined, ext }) + } + if (config.build.cssCodeSplit) { if (isPureCssChunk) { // this is a shared CSS-only chunk that is empty. pureCssChunks.add(chunk.fileName) } if (opts.format === 'es' || opts.format === 'cjs') { - const cssAssetName = chunk.name + '.css' + const cssAssetName = ensureFileExt( + chunk.facadeModuleId + ? normalizePath(path.relative(config.root, chunk.facadeModuleId)) + : chunk.name, + '.css' + ) chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssAssetName) chunkCSS = await finalizeCss(chunkCSS, true, config) @@ -501,6 +512,12 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { // emit corresponding css file const fileHandle = this.emitFile({ name: cssAssetName, + fileName: assetFileNamesToFileName( + resolveAssetFileNames(config), + cssAssetName, + getHash(chunkCSS), + chunkCSS + ), type: 'asset', source: chunkCSS }) diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts index 9784597195dc7c..b496caf4dbae96 100644 --- a/packages/vite/src/node/plugins/manifest.ts +++ b/packages/vite/src/node/plugins/manifest.ts @@ -1,5 +1,5 @@ import path from 'path' -import type { OutputChunk } from 'rollup' +import type { OutputAsset, OutputChunk } from 'rollup' import type { ResolvedConfig } from '..' import type { Plugin } from '../plugin' import { normalizePath } from '../utils' @@ -99,10 +99,19 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { return manifestChunk } + function createAsset(chunk: OutputAsset): ManifestChunk { + return { + file: chunk.fileName, + src: chunk.name + } + } + for (const file in bundle) { const chunk = bundle[file] if (chunk.type === 'chunk') { manifest[getChunkName(chunk)] = createChunk(chunk) + } else if (chunk.type === 'asset' && typeof chunk.name === 'string') { + manifest[chunk.name] = createAsset(chunk) } } diff --git a/playground/backend-integration/__tests__/backend-integration.spec.ts b/playground/backend-integration/__tests__/backend-integration.spec.ts index 314ccfb0582680..db3790ee67e4ac 100644 --- a/playground/backend-integration/__tests__/backend-integration.spec.ts +++ b/playground/backend-integration/__tests__/backend-integration.spec.ts @@ -32,8 +32,12 @@ describe.runIf(isBuild)('build', () => { test('manifest', async () => { const manifest = readManifest('dev') const htmlEntry = manifest['index.html'] + const cssAssetEntry = manifest['global.css'] + const imgAssetEntry = manifest['../images/logo.png'] expect(htmlEntry.css.length).toEqual(1) expect(htmlEntry.assets.length).toEqual(1) + expect(cssAssetEntry?.file).not.toBeUndefined() + expect(imgAssetEntry?.file).not.toBeUndefined() }) })