From 3bee309f79a867f4754b829a9b91472838a44392 Mon Sep 17 00:00:00 2001 From: ChrisJr404 Date: Tue, 12 May 2026 09:43:27 -0400 Subject: [PATCH 1/2] fix(css): pass deprecated `name` and `originalFileName` to synthetic `assetFileNames` call `getCssAssetDirname` invokes the user's `assetFileNames` callback to figure out which directory the CSS file ends up in, so url() rewrites can compute a correct relative path. The rolldown migration dropped `name` and `originalFileName` (singular) from that synthetic call. Both fields are marked deprecated in rolldown's `PreRenderedAsset` type but are still populated for the real asset emit. User callbacks commonly branch on `chunkInfo.name` (the karlvr/vite-relative-base-bug repro does), so the synthetic call now misses the css branch and falls through to the default pattern. The dirname then comes out as `.` instead of e.g. `css`, and `url(../img/foo.png)` regresses to `url(./img/foo.png)`. Restore the two deprecated fields in the synthetic call so it matches what user code sees during the real emit. fix #22434 Signed-off-by: ChrisJr404 --- .../vite/src/node/__tests__/build.spec.ts | 42 +++++++++++++++++++ .../css-relative-base-subfolder/entry.js | 1 + .../css-relative-base-subfolder/img/foo.png | 0 .../css-relative-base-subfolder/style.css | 3 ++ packages/vite/src/node/plugins/css.ts | 6 +++ 5 files changed, 52 insertions(+) create mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js create mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png create mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index d978dcfe2e2613..d15819127d74f3 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -234,6 +234,48 @@ describe('build', () => { }, ) + test('css url() should be relative to css output dir when base is "./" and assetFileNames places css in a subfolder (#22434)', async () => { + const root = resolve(dirname, 'fixtures/css-relative-base-subfolder') + const result = (await build({ + root, + logLevel: 'silent', + base: './', + build: { + write: false, + assetsInlineLimit: 0, + rollupOptions: { + input: resolve(root, 'entry.js'), + output: { + assetFileNames(chunkInfo) { + const name = chunkInfo.name ?? '' + if (name.endsWith('.css')) { + return 'css/[name]-[hash].[ext]' + } + if (name.match(/\.(png|jpe?g|gif|svg|webp)$/)) { + return 'img/[name]-[hash].[ext]' + } + return '[name]-[hash].[ext]' + }, + }, + }, + }, + })) as RolldownOutput + + const cssAsset = result.output.find( + (o) => o.type === 'asset' && o.fileName.endsWith('.css'), + ) as { source: string; fileName: string } | undefined + expect(cssAsset).toBeDefined() + expect(cssAsset!.fileName).toMatch(/^css\//) + const cssSource = + typeof cssAsset!.source === 'string' + ? cssAsset!.source + : Buffer.from(cssAsset!.source).toString() + // CSS lives in dist/css/, image lives in dist/img/. + // The url() should walk up out of css/ and into img/. + expect(cssSource).toMatch(/url\(\.\.\/img\/foo-[\w-]+\.png\)/) + expect(cssSource).not.toMatch(/url\(\.\/img\//) + }) + test('external modules should not be hoisted in library build', async () => { const [esBundle] = (await build({ logLevel: 'silent', diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js new file mode 100644 index 00000000000000..cab743adef7757 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js @@ -0,0 +1 @@ +import './style.css' diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css new file mode 100644 index 00000000000000..064eca0b6304e6 --- /dev/null +++ b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css @@ -0,0 +1,3 @@ +.box { + background: url('./img/foo.png'); +} diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 1353831780a45b..6d0465329f5daf 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -491,7 +491,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { return path.dirname( assetFileNames({ type: 'asset', + // `name` and `originalFileName` are deprecated in rolldown but + // still populated for the real asset emit, so user-provided + // `assetFileNames` callbacks commonly read them. Pass them here + // too so this synthetic call matches what user code expects. + name: cssAssetName, names: [cssAssetName], + originalFileName, originalFileNames: originalFileName ? [originalFileName] : [], source: '/* vite internal call, ignore */', }), From 0b3869e58fc828eed1945f7b7bdc26c8a2d62438 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Thu, 14 May 2026 12:14:27 +0900 Subject: [PATCH 2/2] chore: refactor --- .../vite/src/node/__tests__/build.spec.ts | 42 ------------------- .../css-relative-base-subfolder/entry.js | 1 - .../css-relative-base-subfolder/img/foo.png | 0 .../css-relative-base-subfolder/style.css | 3 -- packages/vite/src/node/plugins/css.ts | 4 -- 5 files changed, 50 deletions(-) delete mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js delete mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png delete mode 100644 packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index d15819127d74f3..d978dcfe2e2613 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -234,48 +234,6 @@ describe('build', () => { }, ) - test('css url() should be relative to css output dir when base is "./" and assetFileNames places css in a subfolder (#22434)', async () => { - const root = resolve(dirname, 'fixtures/css-relative-base-subfolder') - const result = (await build({ - root, - logLevel: 'silent', - base: './', - build: { - write: false, - assetsInlineLimit: 0, - rollupOptions: { - input: resolve(root, 'entry.js'), - output: { - assetFileNames(chunkInfo) { - const name = chunkInfo.name ?? '' - if (name.endsWith('.css')) { - return 'css/[name]-[hash].[ext]' - } - if (name.match(/\.(png|jpe?g|gif|svg|webp)$/)) { - return 'img/[name]-[hash].[ext]' - } - return '[name]-[hash].[ext]' - }, - }, - }, - }, - })) as RolldownOutput - - const cssAsset = result.output.find( - (o) => o.type === 'asset' && o.fileName.endsWith('.css'), - ) as { source: string; fileName: string } | undefined - expect(cssAsset).toBeDefined() - expect(cssAsset!.fileName).toMatch(/^css\//) - const cssSource = - typeof cssAsset!.source === 'string' - ? cssAsset!.source - : Buffer.from(cssAsset!.source).toString() - // CSS lives in dist/css/, image lives in dist/img/. - // The url() should walk up out of css/ and into img/. - expect(cssSource).toMatch(/url\(\.\.\/img\/foo-[\w-]+\.png\)/) - expect(cssSource).not.toMatch(/url\(\.\/img\//) - }) - test('external modules should not be hoisted in library build', async () => { const [esBundle] = (await build({ logLevel: 'silent', diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js deleted file mode 100644 index cab743adef7757..00000000000000 --- a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/entry.js +++ /dev/null @@ -1 +0,0 @@ -import './style.css' diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/img/foo.png deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css b/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css deleted file mode 100644 index 064eca0b6304e6..00000000000000 --- a/packages/vite/src/node/__tests__/fixtures/css-relative-base-subfolder/style.css +++ /dev/null @@ -1,3 +0,0 @@ -.box { - background: url('./img/foo.png'); -} diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 6d0465329f5daf..e6925094a04583 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -491,10 +491,6 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { return path.dirname( assetFileNames({ type: 'asset', - // `name` and `originalFileName` are deprecated in rolldown but - // still populated for the real asset emit, so user-provided - // `assetFileNames` callbacks commonly read them. Pass them here - // too so this synthetic call matches what user code expects. name: cssAssetName, names: [cssAssetName], originalFileName,