From a9a4b80a7773f2289bc18a37d1cb6d220418806f Mon Sep 17 00:00:00 2001 From: Qingyu Wang Date: Sun, 29 Mar 2026 17:35:34 +0800 Subject: [PATCH 1/2] fix: resolve duplicate theme variables in CSS modules --- src/postcss.ts | 2 +- test/css-modules-dedup-color/index.test.ts | 26 ++++++++++++++++++++ test/css-modules-dedup-color/src/a.css | 3 +++ test/css-modules-dedup-color/src/b.css | 3 +++ test/css-modules-dedup-color/src/index.js | 2 ++ test/css-modules-dedup/index.test.ts | 28 ++++++++++++++++++++++ test/css-modules-dedup/src/a.css | 3 +++ test/css-modules-dedup/src/b.css | 3 +++ test/css-modules-dedup/src/index.js | 2 ++ 9 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/css-modules-dedup-color/index.test.ts create mode 100644 test/css-modules-dedup-color/src/a.css create mode 100644 test/css-modules-dedup-color/src/b.css create mode 100644 test/css-modules-dedup-color/src/index.js create mode 100644 test/css-modules-dedup/index.test.ts create mode 100644 test/css-modules-dedup/src/a.css create mode 100644 test/css-modules-dedup/src/b.css create mode 100644 test/css-modules-dedup/src/index.js diff --git a/src/postcss.ts b/src/postcss.ts index 1af6ca9..3e3e274 100644 --- a/src/postcss.ts +++ b/src/postcss.ts @@ -19,7 +19,7 @@ const injectTailwindThemePlugin: PluginCreator = ( } root.prepend( new AtRule({ - name: 'import', + name: 'reference', params: JSON.stringify(themePath), }), ); diff --git a/test/css-modules-dedup-color/index.test.ts b/test/css-modules-dedup-color/index.test.ts new file mode 100644 index 0000000..a280f0d --- /dev/null +++ b/test/css-modules-dedup-color/index.test.ts @@ -0,0 +1,26 @@ +import fs from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; +import { createRsbuild } from '@rsbuild/core'; +import { pluginTailwindCSS } from '../../src'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +test('deduplicates theme color variables', async () => { + const rsbuild = await createRsbuild({ + cwd: __dirname, + rsbuildConfig: { + plugins: [pluginTailwindCSS()], + }, + }); + await rsbuild.build(); + + const cssDir = join(__dirname, 'dist/static/css'); + const cssFiles = fs.readdirSync(cssDir).filter((f) => f.endsWith('.css')); + const cssContent = fs.readFileSync(join(cssDir, cssFiles[0]), 'utf-8'); + + // Find how many times --color-red-500 is defined + const matches = cssContent.match(/--color-red-500:/g) || []; + expect(matches.length).toBeLessThan(5); +}); diff --git a/test/css-modules-dedup-color/src/a.css b/test/css-modules-dedup-color/src/a.css new file mode 100644 index 0000000..644ab42 --- /dev/null +++ b/test/css-modules-dedup-color/src/a.css @@ -0,0 +1,3 @@ +.a { + @apply text-red-500; +} diff --git a/test/css-modules-dedup-color/src/b.css b/test/css-modules-dedup-color/src/b.css new file mode 100644 index 0000000..183d52f --- /dev/null +++ b/test/css-modules-dedup-color/src/b.css @@ -0,0 +1,3 @@ +.b { + @apply text-red-500; +} diff --git a/test/css-modules-dedup-color/src/index.js b/test/css-modules-dedup-color/src/index.js new file mode 100644 index 0000000..a0ec795 --- /dev/null +++ b/test/css-modules-dedup-color/src/index.js @@ -0,0 +1,2 @@ +import './a.css'; +import './b.css'; diff --git a/test/css-modules-dedup/index.test.ts b/test/css-modules-dedup/index.test.ts new file mode 100644 index 0000000..7520e9f --- /dev/null +++ b/test/css-modules-dedup/index.test.ts @@ -0,0 +1,28 @@ +import fs from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; +import { createRsbuild } from '@rsbuild/core'; +import { pluginTailwindCSS } from '../../src'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +test('deduplicates theme variables in multiple CSS modules', async () => { + const rsbuild = await createRsbuild({ + cwd: __dirname, + rsbuildConfig: { + plugins: [pluginTailwindCSS()], + }, + }); + + await rsbuild.build(); + + const cssDir = join(__dirname, 'dist/static/css'); + const cssFiles = fs.readdirSync(cssDir).filter((f) => f.endsWith('.css')); + const cssContent = fs.readFileSync(join(cssDir, cssFiles[0]), 'utf-8'); + + // If theme was injected via @import, does it result in duplicated variables? + // We'll count the number of times `--spacing` or some known theme variable appears. + const rootMatches = cssContent.match(/:root/g) || []; + expect(rootMatches.length).toBeLessThan(2); +}); diff --git a/test/css-modules-dedup/src/a.css b/test/css-modules-dedup/src/a.css new file mode 100644 index 0000000..939da27 --- /dev/null +++ b/test/css-modules-dedup/src/a.css @@ -0,0 +1,3 @@ +.a { + @apply flex; +} diff --git a/test/css-modules-dedup/src/b.css b/test/css-modules-dedup/src/b.css new file mode 100644 index 0000000..5f0d71e --- /dev/null +++ b/test/css-modules-dedup/src/b.css @@ -0,0 +1,3 @@ +.b { + @apply flex; +} diff --git a/test/css-modules-dedup/src/index.js b/test/css-modules-dedup/src/index.js new file mode 100644 index 0000000..a0ec795 --- /dev/null +++ b/test/css-modules-dedup/src/index.js @@ -0,0 +1,2 @@ +import './a.css'; +import './b.css'; From 8cfa866ef9e1270c321b225310bfafda80ad248f Mon Sep 17 00:00:00 2001 From: Qingyu Wang Date: Sun, 29 Mar 2026 18:04:47 +0800 Subject: [PATCH 2/2] fix: conditionally inject @import or @reference based on file type to fix css modules dedup without breaking global css --- src/postcss.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/postcss.ts b/src/postcss.ts index 3e3e274..fe1f2ff 100644 --- a/src/postcss.ts +++ b/src/postcss.ts @@ -17,9 +17,16 @@ const injectTailwindThemePlugin: PluginCreator = ( if (!themePath) { return; } + + const file = root.source?.input.file || ''; + + // Use @reference for CSS modules to avoid duplicating theme variables in every module. + // Use @import for global CSS and regular CSS files to ensure theme variables are emitted. + const isCssModule = /\.module\.(css|scss|sass|less|styl)$/i.test(file); + root.prepend( new AtRule({ - name: 'reference', + name: isCssModule ? 'reference' : 'import', params: JSON.stringify(themePath), }), );