From e6913d1440fa91de475f8e56a0d70bef5fce5afa Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Fri, 7 Jun 2024 19:31:55 +0800 Subject: [PATCH] fix: backport to 5.2 (#17411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikhail Avdeev <39246971+easymikey@users.noreply.github.com> Co-authored-by: 翠 / green --- .../src/node/__tests__/plugins/css.spec.ts | 50 ++++++++++++++++ packages/vite/src/node/plugins/css.ts | 11 +++- .../src/node/plugins/importAnalysisBuild.ts | 59 ++++++++++++++++++- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/__tests__/plugins/css.spec.ts b/packages/vite/src/node/__tests__/plugins/css.spec.ts index cfd7dc6e6d4e47..e1c435211c9593 100644 --- a/packages/vite/src/node/__tests__/plugins/css.spec.ts +++ b/packages/vite/src/node/__tests__/plugins/css.spec.ts @@ -9,6 +9,7 @@ import { cssUrlRE, getEmptyChunkReplacer, hoistAtRules, + preprocessCSS, } from '../../plugins/css' describe('search css url function', () => { @@ -65,6 +66,7 @@ background: #f0f; }`, }, { + configFile: false, resolve: { alias: [ { @@ -101,6 +103,7 @@ position: fixed; test('custom generateScopedName', async () => { const { transform, resetMock } = await createCssPluginTransform(undefined, { + configFile: false, css: { modules: { generateScopedName: 'custom__[hash:base64:5]', @@ -338,3 +341,50 @@ require("other-module");` ) }) }) + +describe('preprocessCSS', () => { + test('works', async () => { + const resolvedConfig = await resolveConfig({ configFile: false }, 'serve') + const result = await preprocessCSS( + `\ +.foo { + color:red; + background: url(./foo.png); +}`, + 'foo.css', + resolvedConfig, + ) + expect(result.code).toMatchInlineSnapshot(` + ".foo { + color:red; + background: url(./foo.png); + }" + `) + }) + + test('works with lightningcss', async () => { + const resolvedConfig = await resolveConfig( + { + configFile: false, + css: { transformer: 'lightningcss' }, + }, + 'serve', + ) + const result = await preprocessCSS( + `\ +.foo { + color: red; + background: url(./foo.png); +}`, + 'foo.css', + resolvedConfig, + ) + expect(result.code).toMatchInlineSnapshot(` + ".foo { + color: red; + background: url("./foo.png"); + } + " + `) + }) +}) diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 47276079f9e224..4fbb8612725fcc 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -82,6 +82,7 @@ import { import type { ESBuildOptions } from './esbuild' import { getChunkOriginalFileName } from './manifest' +const decoder = new TextDecoder() // const debug = createDebugger('vite:css') export interface CSSOptions { @@ -1808,8 +1809,12 @@ async function minifyCSS( ), ) } + + // NodeJS res.code = Buffer + // Deno res.code = Uint8Array + // For correct decode compiled css need to use TextDecoder // LightningCSS output does not return a linebreak at the end - return code.toString() + (inlined ? '' : '\n') + return decoder.decode(code) + (inlined ? '' : '\n') } try { const { code, warnings } = await transform(css, { @@ -2698,8 +2703,6 @@ function isPreProcessor(lang: any): lang is PreprocessLang { } const importLightningCSS = createCachedImport(() => import('lightningcss')) - -const decoder = new TextDecoder() async function compileLightningCSS( id: string, src: string, @@ -2780,6 +2783,8 @@ async function compileLightningCSS( if (urlReplacer) { const replaceUrl = await urlReplacer(dep.url, id) css = css.replace(dep.placeholder, () => replaceUrl) + } else { + css = css.replace(dep.placeholder, () => dep.url) } break default: diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index a00ed2461c404b..876badb2358153 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -318,7 +318,64 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { }, generateBundle({ format }, bundle) { - if (format !== 'es' || ssr || isWorker) { + if (format !== 'es') { + return + } + + if (ssr || isWorker) { + const removedPureCssFiles = removedPureCssFilesCache.get(config) + if (removedPureCssFiles && removedPureCssFiles.size > 0) { + for (const file in bundle) { + const chunk = bundle[file] + if (chunk.type === 'chunk' && chunk.code.includes('import')) { + const code = chunk.code + let imports!: ImportSpecifier[] + try { + imports = parseImports(code)[0].filter((i) => i.d > -1) + } catch (e: any) { + const loc = numberToPos(code, e.idx) + this.error({ + name: e.name, + message: e.message, + stack: e.stack, + cause: e.cause, + pos: e.idx, + loc: { ...loc, file: chunk.fileName }, + frame: generateCodeFrame(code, loc), + }) + } + + for (const imp of imports) { + const { + n: name, + s: start, + e: end, + ss: expStart, + se: expEnd, + } = imp + let url = name + if (!url) { + const rawUrl = code.slice(start, end) + if (rawUrl[0] === `"` && rawUrl[rawUrl.length - 1] === `"`) + url = rawUrl.slice(1, -1) + } + if (!url) continue + + const normalizedFile = path.posix.join( + path.posix.dirname(chunk.fileName), + url, + ) + if (removedPureCssFiles.has(normalizedFile)) { + // remove with Promise.resolve({}) while preserving source map location + chunk.code = + chunk.code.slice(0, expStart) + + `Promise.resolve({${''.padEnd(expEnd - expStart - 19, ' ')}})` + + chunk.code.slice(expEnd) + } + } + } + } + } return }