From c2e2f91c9d4dccbdc1e51f2e1d6b71fbae61bfa6 Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Thu, 26 Mar 2026 03:21:37 +0800 Subject: [PATCH 1/4] fix(css): compile preprocessor langs in virtual CSS modules (e.g. Vue SFC) fixes #848 --- packages/css/src/plugin.ts | 10 +++++++--- packages/css/src/preprocessors.ts | 14 ++++++++++++++ packages/css/src/utils.ts | 2 +- tests/issues.test.ts | 32 +++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/css/src/plugin.ts b/packages/css/src/plugin.ts index 7cf316bb6..883a05362 100644 --- a/packages/css/src/plugin.ts +++ b/packages/css/src/plugin.ts @@ -12,7 +12,11 @@ import { } from './options.ts' import { CssPostPlugin, type CssStyles } from './post.ts' import { processWithPostCSS as runPostCSS } from './postcss.ts' -import { compilePreprocessor, getPreprocessorLang } from './preprocessors.ts' +import { + compilePreprocessor, + getPreprocessorLang, + getPreprocessorLangFromId, +} from './preprocessors.ts' import { CSS_LANGS_RE, CSS_MODULE_RE, @@ -315,7 +319,7 @@ async function processWithLightningCSS( logger: Logger, isModule: boolean, ): Promise { - const lang = getPreprocessorLang(cleanId) + const lang = getPreprocessorLang(cleanId) ?? getPreprocessorLangFromId(id) const cssModules = resolveCssModulesConfig( config.css.modules, isModule, @@ -378,7 +382,7 @@ async function processWithPostCSS( config: CssPluginConfig, isModule: boolean, ): Promise { - const lang = getPreprocessorLang(cleanId) + const lang = getPreprocessorLang(cleanId) ?? getPreprocessorLangFromId(id) if (lang) { const preResult = await compilePreprocessor( diff --git a/packages/css/src/preprocessors.ts b/packages/css/src/preprocessors.ts index 4477412b1..29610baae 100644 --- a/packages/css/src/preprocessors.ts +++ b/packages/css/src/preprocessors.ts @@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises' import path from 'node:path' import { fileURLToPath, pathToFileURL } from 'node:url' import { getSassResolver, resolveWithResolver } from './resolve.ts' +import { CSS_LANGS_RE } from './utils.ts' import type { PreprocessorOptions } from './options.ts' export type PreprocessorLang = 'sass' | 'scss' | 'less' | 'styl' | 'stylus' @@ -26,6 +27,19 @@ export function getPreprocessorLang( return PREPROCESSOR_LANGS[ext] } +/** + * Extract preprocessor lang from a module id (including query string). + * Handles virtual modules like Vue SFC style blocks where the lang + * is encoded in the query (e.g. `App.vue?vue&type=style&lang.scss`). + */ +export function getPreprocessorLangFromId( + id: string, +): PreprocessorLang | undefined { + const match = CSS_LANGS_RE.exec(id) + if (!match) return undefined + return PREPROCESSOR_LANGS[match[1]] +} + export function compilePreprocessor( lang: PreprocessorLang, code: string, diff --git a/packages/css/src/utils.ts b/packages/css/src/utils.ts index 85cbdbf07..db5d1fbe6 100644 --- a/packages/css/src/utils.ts +++ b/packages/css/src/utils.ts @@ -1,7 +1,7 @@ export const RE_CSS: RegExp = /\.css$/ export const RE_INLINE: RegExp = /[?&]inline\b/ export const CSS_LANGS_RE: RegExp = - /\.(?:css|less|sass|scss|styl|stylus)(?:$|\?)/ + /\.(css|less|sass|scss|styl|stylus)(?:$|\?)/ export const RE_CSS_INLINE: RegExp = /\.(?:css|less|sass|scss|styl|stylus)\?(?:.*&)?inline\b/ diff --git a/tests/issues.test.ts b/tests/issues.test.ts index 1ff2e3780..73ec19ed5 100644 --- a/tests/issues.test.ts +++ b/tests/issues.test.ts @@ -197,6 +197,38 @@ describe('issues', () => { expect(fileMap['style.css']).toContain('.btn') }) + test('#848', async (context) => { + const Vue = (await import('unplugin-vue/rolldown')).default + const { outputFiles, fileMap } = await testBuild({ + context, + files: { + 'index.ts': `export { default as App } from './App.vue'`, + 'App.vue': ` +`, + }, + options: { + plugins: [Vue({ isProduction: true })], + deps: { skipNodeModulesBundle: true }, + }, + }) + expect(outputFiles).toContain('style.css') + const css = fileMap['style.css'] + expect(css).toContain('padding') + expect(css).toContain('border-radius') + expect(css).not.toContain('@mixin') + expect(css).not.toContain('@include') + }) + test('#772', async (context) => { const { fileMap, outputFiles } = await testBuild({ context, From 2fb796fda68fbd60a28b33b53f1e99d6b7edd034 Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Thu, 26 Mar 2026 03:22:15 +0800 Subject: [PATCH 2/4] test: add snapshot for #848 --- tests/__snapshots__/issues/848.snap.md | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/__snapshots__/issues/848.snap.md diff --git a/tests/__snapshots__/issues/848.snap.md b/tests/__snapshots__/issues/848.snap.md new file mode 100644 index 000000000..084823c28 --- /dev/null +++ b/tests/__snapshots__/issues/848.snap.md @@ -0,0 +1,33 @@ +## index.mjs + +```mjs +import { createElementBlock, openBlock } from "vue"; +//#region \0/plugin-vue/export-helper +var export_helper_default = (sfc, props) => { + const target = sfc.__vccOpts || sfc; + for (const [key, val] of props) target[key] = val; + return target; +}; +//#endregion +//#region App.vue +const _sfc_main = {}; +function _sfc_render(_ctx, _cache) { + return openBlock(), createElementBlock("button", null, "Click"); +} +var App_default = /* @__PURE__ */ export_helper_default(_sfc_main, [["render", _sfc_render]]); +//#endregion +export { App_default as App }; + +``` + +## style.css + +```css +button { + padding: 10px 20px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 1.2em; +} + +``` From 21fa5a35553c9fbc2c8604959d43242a237da78f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:23:30 +0000 Subject: [PATCH 3/4] [autofix.ci] apply automated fixes --- packages/css/src/utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/css/src/utils.ts b/packages/css/src/utils.ts index db5d1fbe6..4620ea66b 100644 --- a/packages/css/src/utils.ts +++ b/packages/css/src/utils.ts @@ -1,7 +1,6 @@ export const RE_CSS: RegExp = /\.css$/ export const RE_INLINE: RegExp = /[?&]inline\b/ -export const CSS_LANGS_RE: RegExp = - /\.(css|less|sass|scss|styl|stylus)(?:$|\?)/ +export const CSS_LANGS_RE: RegExp = /\.(css|less|sass|scss|styl|stylus)(?:$|\?)/ export const RE_CSS_INLINE: RegExp = /\.(?:css|less|sass|scss|styl|stylus)\?(?:.*&)?inline\b/ From 00a58c84e6774f92c532f5493d60fb270c3f6cd5 Mon Sep 17 00:00:00 2001 From: Kevin Deng Date: Thu, 26 Mar 2026 03:25:59 +0800 Subject: [PATCH 4/4] refactor: simplify to use getPreprocessorLangFromId directly --- packages/css/src/plugin.ts | 5 ++--- packages/css/src/preprocessors.ts | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/css/src/plugin.ts b/packages/css/src/plugin.ts index 883a05362..beed06803 100644 --- a/packages/css/src/plugin.ts +++ b/packages/css/src/plugin.ts @@ -14,7 +14,6 @@ import { CssPostPlugin, type CssStyles } from './post.ts' import { processWithPostCSS as runPostCSS } from './postcss.ts' import { compilePreprocessor, - getPreprocessorLang, getPreprocessorLangFromId, } from './preprocessors.ts' import { @@ -319,7 +318,7 @@ async function processWithLightningCSS( logger: Logger, isModule: boolean, ): Promise { - const lang = getPreprocessorLang(cleanId) ?? getPreprocessorLangFromId(id) + const lang = getPreprocessorLangFromId(id) const cssModules = resolveCssModulesConfig( config.css.modules, isModule, @@ -382,7 +381,7 @@ async function processWithPostCSS( config: CssPluginConfig, isModule: boolean, ): Promise { - const lang = getPreprocessorLang(cleanId) ?? getPreprocessorLangFromId(id) + const lang = getPreprocessorLangFromId(id) if (lang) { const preResult = await compilePreprocessor( diff --git a/packages/css/src/preprocessors.ts b/packages/css/src/preprocessors.ts index 29610baae..0b313d55c 100644 --- a/packages/css/src/preprocessors.ts +++ b/packages/css/src/preprocessors.ts @@ -27,16 +27,11 @@ export function getPreprocessorLang( return PREPROCESSOR_LANGS[ext] } -/** - * Extract preprocessor lang from a module id (including query string). - * Handles virtual modules like Vue SFC style blocks where the lang - * is encoded in the query (e.g. `App.vue?vue&type=style&lang.scss`). - */ export function getPreprocessorLangFromId( id: string, ): PreprocessorLang | undefined { const match = CSS_LANGS_RE.exec(id) - if (!match) return undefined + if (!match) return return PREPROCESSOR_LANGS[match[1]] }