From 28ccede5254d0801ba158f391df5a22844306368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Sun, 12 Nov 2023 00:00:12 +0900 Subject: [PATCH] fix(css): correctly set manifest source name and emit CSS file (#14945) --- packages/vite/src/node/plugins/css.ts | 18 ++++---- packages/vite/src/node/plugins/manifest.ts | 43 +++++++++++++------ .../__tests__/backend-integration.spec.ts | 10 +++-- playground/backend-integration/dir/foo.css | 2 +- .../frontend/dynamic/foo.css | 3 -- .../frontend/dynamic/foo.ts | 1 - .../frontend/entrypoints/foo.pcss | 3 ++ .../frontend/entrypoints/main.ts | 1 - playground/css/__tests__/css.spec.ts | 7 +++ playground/css/main.js | 1 + playground/css/manual-chunk.css | 3 ++ playground/css/vite.config.js | 9 ++++ 12 files changed, 67 insertions(+), 34 deletions(-) delete mode 100644 playground/backend-integration/frontend/dynamic/foo.css delete mode 100644 playground/backend-integration/frontend/dynamic/foo.ts create mode 100644 playground/backend-integration/frontend/entrypoints/foo.pcss create mode 100644 playground/css/manual-chunk.css diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 842daf68710a18..c7c5bd4bf9cacd 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -70,6 +70,7 @@ import { renderAssetUrlInJS, } from './asset' import type { ESBuildOptions } from './esbuild' +import { getChunkOriginalFileName } from './manifest' // const debug = createDebugger('vite:css') @@ -610,15 +611,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { } if (opts.format === 'es' || opts.format === 'cjs') { const isEntry = chunk.isEntry && isPureCssChunk - const cssAssetName = normalizePath( - !isEntry && chunk.facadeModuleId - ? path.relative(config.root, chunk.facadeModuleId) - : chunk.name, + const cssAssetName = ensureFileExt(chunk.name, '.css') + const originalFilename = getChunkOriginalFileName( + chunk, + config.root, + opts.format, ) - const lang = path.extname(cssAssetName).slice(1) - const cssFileName = ensureFileExt(cssAssetName, '.css') - chunkCSS = resolveAssetUrlsInCss(chunkCSS, cssAssetName) const previousTask = emitTasks[emitTasks.length - 1] @@ -639,14 +638,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { // emit corresponding css file const referenceId = this.emitFile({ - name: path.basename(cssFileName), + name: cssAssetName, type: 'asset', source: chunkCSS, }) - const originalName = isPreProcessor(lang) ? cssAssetName : cssFileName generatedAssets .get(config)! - .set(referenceId, { originalName, isEntry }) + .set(referenceId, { originalName: originalFilename, isEntry }) chunk.viteMetadata!.importedCss.add(this.getFileName(referenceId)) if (emitTasksLength === emitTasks.length) { diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts index 7f7484c1a7ae5e..eff3212d2fc06c 100644 --- a/packages/vite/src/node/plugins/manifest.ts +++ b/packages/vite/src/node/plugins/manifest.ts @@ -1,5 +1,10 @@ import path from 'node:path' -import type { OutputAsset, OutputChunk } from 'rollup' +import type { + InternalModuleFormat, + OutputAsset, + OutputChunk, + RenderedChunk, +} from 'rollup' import jsonStableStringify from 'json-stable-stringify' import type { ResolvedConfig } from '..' import type { Plugin } from '../plugin' @@ -34,19 +39,7 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { generateBundle({ format }, bundle) { function getChunkName(chunk: OutputChunk) { - if (chunk.facadeModuleId) { - let name = normalizePath( - path.relative(config.root, chunk.facadeModuleId), - ) - if (format === 'system' && !chunk.name.includes('-legacy')) { - const ext = path.extname(name) - const endPos = ext.length !== 0 ? -ext.length : undefined - name = name.slice(0, endPos) + `-legacy` + ext - } - return name.replace(/\0/g, '') - } else { - return `_` + path.basename(chunk.fileName) - } + return getChunkOriginalFileName(chunk, config.root, format) } function getInternalImports(imports: string[]): string[] { @@ -133,6 +126,10 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { const assetMeta = fileNameToAssetMeta.get(chunk.fileName) const src = assetMeta?.originalName ?? chunk.name const asset = createAsset(chunk, src, assetMeta?.isEntry) + + // If JS chunk and asset chunk are both generated from the same source file, + // prioritize JS chunk as it contains more information + if (manifest[src]?.file.endsWith('.js')) continue manifest[src] = asset fileNameToAsset.set(chunk.fileName, asset) } @@ -165,3 +162,21 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { }, } } + +export function getChunkOriginalFileName( + chunk: OutputChunk | RenderedChunk, + root: string, + format: InternalModuleFormat, +): string { + if (chunk.facadeModuleId) { + let name = normalizePath(path.relative(root, chunk.facadeModuleId)) + if (format === 'system' && !chunk.name.includes('-legacy')) { + const ext = path.extname(name) + const endPos = ext.length !== 0 ? -ext.length : undefined + name = name.slice(0, endPos) + `-legacy` + ext + } + return name.replace(/\0/g, '') + } else { + return `_` + path.basename(chunk.fileName) + } +} diff --git a/playground/backend-integration/__tests__/backend-integration.spec.ts b/playground/backend-integration/__tests__/backend-integration.spec.ts index 7356d8eff15c8c..eb017f4498b507 100644 --- a/playground/backend-integration/__tests__/backend-integration.spec.ts +++ b/playground/backend-integration/__tests__/backend-integration.spec.ts @@ -35,22 +35,24 @@ describe.runIf(isBuild)('build', () => { const manifest = readManifest('dev') const htmlEntry = manifest['index.html'] const cssAssetEntry = manifest['global.css'] + const pcssAssetEntry = manifest['foo.pcss'] const scssAssetEntry = manifest['nested/blue.scss'] const imgAssetEntry = manifest['../images/logo.png'] - const dirFooAssetEntry = manifest['../dynamic/foo.css'] // '\\' should not be used even on windows + const dirFooAssetEntry = manifest['../../dir/foo.css'] expect(htmlEntry.css.length).toEqual(1) expect(htmlEntry.assets.length).toEqual(1) expect(cssAssetEntry?.file).not.toBeUndefined() expect(cssAssetEntry?.isEntry).toEqual(true) + expect(pcssAssetEntry?.file).not.toBeUndefined() + expect(pcssAssetEntry?.isEntry).toEqual(true) expect(scssAssetEntry?.file).not.toBeUndefined() expect(scssAssetEntry?.src).toEqual('nested/blue.scss') expect(scssAssetEntry?.isEntry).toEqual(true) expect(imgAssetEntry?.file).not.toBeUndefined() expect(imgAssetEntry?.isEntry).toBeUndefined() - expect(dirFooAssetEntry).not.toBeUndefined() + expect(dirFooAssetEntry).not.toBeUndefined() // '\\' should not be used even on windows // use the entry name - expect(manifest['bar.css']).not.toBeUndefined() - expect(manifest['foo.css']).toBeUndefined() + expect(dirFooAssetEntry.file).toMatch('assets/bar-') }) }) diff --git a/playground/backend-integration/dir/foo.css b/playground/backend-integration/dir/foo.css index 1e31c585bebc9c..c2fad7486d3ab6 100644 --- a/playground/backend-integration/dir/foo.css +++ b/playground/backend-integration/dir/foo.css @@ -1,3 +1,3 @@ -.entry-name-foo { +.windows-path-foo { color: blue; } diff --git a/playground/backend-integration/frontend/dynamic/foo.css b/playground/backend-integration/frontend/dynamic/foo.css deleted file mode 100644 index c2fad7486d3ab6..00000000000000 --- a/playground/backend-integration/frontend/dynamic/foo.css +++ /dev/null @@ -1,3 +0,0 @@ -.windows-path-foo { - color: blue; -} diff --git a/playground/backend-integration/frontend/dynamic/foo.ts b/playground/backend-integration/frontend/dynamic/foo.ts deleted file mode 100644 index c2441c49231d80..00000000000000 --- a/playground/backend-integration/frontend/dynamic/foo.ts +++ /dev/null @@ -1 +0,0 @@ -import './foo.css' diff --git a/playground/backend-integration/frontend/entrypoints/foo.pcss b/playground/backend-integration/frontend/entrypoints/foo.pcss new file mode 100644 index 00000000000000..a49e02c1cc73bf --- /dev/null +++ b/playground/backend-integration/frontend/entrypoints/foo.pcss @@ -0,0 +1,3 @@ +.foo_pcss { + color: blue; +} diff --git a/playground/backend-integration/frontend/entrypoints/main.ts b/playground/backend-integration/frontend/entrypoints/main.ts index d63a57a023847e..f5a332191dd9e4 100644 --- a/playground/backend-integration/frontend/entrypoints/main.ts +++ b/playground/backend-integration/frontend/entrypoints/main.ts @@ -1,5 +1,4 @@ import 'vite/modulepreload-polyfill' -import('../dynamic/foo') // should be dynamic import to split chunks export const colorClass = 'text-black' diff --git a/playground/css/__tests__/css.spec.ts b/playground/css/__tests__/css.spec.ts index b424faace62b81..8b4f498430e099 100644 --- a/playground/css/__tests__/css.spec.ts +++ b/playground/css/__tests__/css.spec.ts @@ -508,3 +508,10 @@ test('async css order with css modules', async () => { test('@import scss', async () => { expect(await getColor('.at-import-scss')).toBe('red') }) + +test.runIf(isBuild)('manual chunk path', async () => { + // assert that the manual-chunk css is output in the directory specified in manualChunk (#12072) + expect( + findAssetFile(/dir\/dir2\/manual-chunk-[-\w]{8}\.css$/), + ).not.toBeUndefined() +}) diff --git a/playground/css/main.js b/playground/css/main.js index 62fc9f4d2fe8ff..b0b405a96a7baf 100644 --- a/playground/css/main.js +++ b/playground/css/main.js @@ -4,6 +4,7 @@ import './sugarss.sss' import './sass.scss' import './less.less' import './stylus.styl' +import './manual-chunk.css' import rawCss from './raw-imported.css?raw' text('.raw-imported-css', rawCss) diff --git a/playground/css/manual-chunk.css b/playground/css/manual-chunk.css new file mode 100644 index 00000000000000..dc41883115cc1d --- /dev/null +++ b/playground/css/manual-chunk.css @@ -0,0 +1,3 @@ +.manual-chunk { + color: blue; +} diff --git a/playground/css/vite.config.js b/playground/css/vite.config.js index 0cce195572a584..3d301ae03bec3e 100644 --- a/playground/css/vite.config.js +++ b/playground/css/vite.config.js @@ -12,6 +12,15 @@ globalThis.location = new URL('http://localhost/') export default defineConfig({ build: { cssTarget: 'chrome61', + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('manual-chunk.css')) { + return 'dir/dir2/manual-chunk' + } + }, + }, + }, }, esbuild: { logOverride: {