From 0d9f07efda882a2c701611812e6007fca7682b69 Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Wed, 29 Jan 2025 11:51:28 +0100
Subject: [PATCH 1/4] Do not scan `tailwind-merge` sources for cadndidates
---
packages/@tailwindcss-vite/src/index.ts | 14 ++++++++++++++
playgrounds/vite/package.json | 1 +
playgrounds/vite/src/app.tsx | 4 +++-
pnpm-lock.yaml | 20 ++++++++++++++------
4 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 2fedf73231f2..c8976c0276da 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -9,6 +9,8 @@ import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite
const DEBUG = env.DEBUG
const SPECIAL_QUERY_RE = /[?&](raw|url)\b/
+const IGNORED_DEPENDENCIES = ['tailwind-merge']
+
export default function tailwindcss(): Plugin[] {
let servers: ViteDevServer[] = []
let config: ResolvedConfig | null = null
@@ -62,6 +64,18 @@ export default function tailwindcss(): Plugin[] {
})
function scanFile(id: string, content: string, extension: string, isSSR: boolean) {
+ for (let dependency of IGNORED_DEPENDENCIES) {
+ // We validated that Vite IDs always use posix style path separators, even on Windows.
+ // In dev build, Vite precompiles dependencies
+ if (id.includes(`.vite/deps/${dependency}.js`)) {
+ return
+ }
+ // In prod builds, use the node_modules path
+ if (id.includes(`/node_modules/${dependency}/`)) {
+ return
+ }
+ }
+
let updated = false
for (let candidate of moduleGraphScanner.scanFiles([{ content, extension }])) {
updated = true
diff --git a/playgrounds/vite/package.json b/playgrounds/vite/package.json
index 742893ef8b07..ae4ec471cc95 100644
--- a/playgrounds/vite/package.json
+++ b/playgrounds/vite/package.json
@@ -13,6 +13,7 @@
"@vitejs/plugin-react": "^4.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "tailwind-merge": "^2.6.0",
"tailwindcss": "workspace:^"
},
"devDependencies": {
diff --git a/playgrounds/vite/src/app.tsx b/playgrounds/vite/src/app.tsx
index 4abc17cb52e1..190af0103c4b 100644
--- a/playgrounds/vite/src/app.tsx
+++ b/playgrounds/vite/src/app.tsx
@@ -1,7 +1,9 @@
+import { twMerge } from 'tailwind-merge'
+
export function App() {
return (
-
Hello World
+
Hello World
)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3d70043b4ac5..2b4264e53b1b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -487,6 +487,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
tailwindcss:
specifier: workspace:^
version: link:../../packages/tailwindcss
@@ -3687,6 +3690,9 @@ packages:
resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==}
engines: {node: '>=18'}
+ tailwind-merge@2.6.0:
+ resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
+
tailwindcss@3.4.14:
resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==}
engines: {node: '>=14.0.0'}
@@ -5770,7 +5776,7 @@ snapshots:
eslint: 9.18.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.18.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.18.0(jiti@2.4.2))
@@ -5790,7 +5796,7 @@ snapshots:
eslint: 9.18.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.18.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.18.0(jiti@2.4.2))
@@ -5821,7 +5827,7 @@ snapshots:
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -5840,7 +5846,7 @@ snapshots:
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -5869,7 +5875,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -5898,7 +5904,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -7249,6 +7255,8 @@ snapshots:
system-architecture@0.1.0: {}
+ tailwind-merge@2.6.0: {}
+
tailwindcss@3.4.14:
dependencies:
'@alloc/quick-lru': 5.2.0
From ba6d88d70389a458b0b7841d3f8feb6413ea03cd Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Wed, 29 Jan 2025 16:53:38 +0100
Subject: [PATCH 2/4] Revert playground changes
---
playgrounds/vite/package.json | 1 -
playgrounds/vite/src/app.tsx | 4 +---
pnpm-lock.yaml | 20 ++++++--------------
3 files changed, 7 insertions(+), 18 deletions(-)
diff --git a/playgrounds/vite/package.json b/playgrounds/vite/package.json
index ae4ec471cc95..742893ef8b07 100644
--- a/playgrounds/vite/package.json
+++ b/playgrounds/vite/package.json
@@ -13,7 +13,6 @@
"@vitejs/plugin-react": "^4.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "tailwind-merge": "^2.6.0",
"tailwindcss": "workspace:^"
},
"devDependencies": {
diff --git a/playgrounds/vite/src/app.tsx b/playgrounds/vite/src/app.tsx
index 190af0103c4b..4abc17cb52e1 100644
--- a/playgrounds/vite/src/app.tsx
+++ b/playgrounds/vite/src/app.tsx
@@ -1,9 +1,7 @@
-import { twMerge } from 'tailwind-merge'
-
export function App() {
return (
-
Hello World
+
Hello World
)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2b4264e53b1b..3d70043b4ac5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -487,9 +487,6 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.0.0(react@19.0.0)
- tailwind-merge:
- specifier: ^2.6.0
- version: 2.6.0
tailwindcss:
specifier: workspace:^
version: link:../../packages/tailwindcss
@@ -3690,9 +3687,6 @@ packages:
resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==}
engines: {node: '>=18'}
- tailwind-merge@2.6.0:
- resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
-
tailwindcss@3.4.14:
resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==}
engines: {node: '>=14.0.0'}
@@ -5776,7 +5770,7 @@ snapshots:
eslint: 9.18.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.18.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.18.0(jiti@2.4.2))
@@ -5796,7 +5790,7 @@ snapshots:
eslint: 9.18.0(jiti@2.4.2)
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.18.0(jiti@2.4.2))
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-jsx-a11y: 6.10.1(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react: 7.37.2(eslint@9.18.0(jiti@2.4.2))
eslint-plugin-react-hooks: 5.0.0(eslint@9.18.0(jiti@2.4.2))
@@ -5827,7 +5821,7 @@ snapshots:
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -5846,7 +5840,7 @@ snapshots:
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2))
+ eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2))
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@@ -5875,7 +5869,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.5.4))(eslint@9.18.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -5904,7 +5898,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.18.0(jiti@2.4.2)):
+ eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@9.18.0(jiti@2.4.2))(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.18.0(jiti@2.4.2)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -7255,8 +7249,6 @@ snapshots:
system-architecture@0.1.0: {}
- tailwind-merge@2.6.0: {}
-
tailwindcss@3.4.14:
dependencies:
'@alloc/quick-lru': 5.2.0
From 27ddcd77f4d703d0488a81013e64727db730185c Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Wed, 29 Jan 2025 17:09:05 +0100
Subject: [PATCH 3/4] Add intergation test
---
integrations/vite/ignored-packages.test.ts | 81 ++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 integrations/vite/ignored-packages.test.ts
diff --git a/integrations/vite/ignored-packages.test.ts b/integrations/vite/ignored-packages.test.ts
new file mode 100644
index 000000000000..f7f85ff14975
--- /dev/null
+++ b/integrations/vite/ignored-packages.test.ts
@@ -0,0 +1,81 @@
+import { candidate, css, fetchStyles, html, js, retryAssertion, test, ts, txt } from '../utils'
+
+const WORKSPACE = {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "tailwind-merge": "^2",
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "vite": "^6"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
+
+ export default defineConfig({
+ build: { cssMinify: false },
+ plugins: [tailwindcss()],
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+ `,
+ 'src/index.js': js`
+ import { twMerge } from 'tailwind-merge'
+
+ twMerge('underline')
+
+ console.log('underline')
+ `,
+ 'src/index.css': css`@import 'tailwindcss/utilities' layer(utilities);`,
+ },
+}
+
+test(
+ 'does not scan tailwind-merge in production builds',
+ WORKSPACE,
+ async ({ fs, exec, expect }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+ let [, content] = files[0]
+
+ expect(content).toMatchInlineSnapshot(`
+ "@layer utilities {
+ .underline {
+ text-decoration-line: underline;
+ }
+ }
+ "
+ `)
+ },
+)
+
+test('does not scan tailwind-merge in dev builds', WORKSPACE, async ({ spawn, expect }) => {
+ let process = await spawn('pnpm vite dev')
+ await process.onStdout((m) => m.includes('ready in'))
+
+ let url = ''
+ await process.onStdout((m) => {
+ let match = /Local:\s*(http.*)\//.exec(m)
+ if (match) url = match[1]
+ return Boolean(url)
+ })
+
+ await retryAssertion(async () => {
+ let styles = await fetchStyles(url, '/index.html')
+
+ expect(styles).not.toContain(candidate`flex`)
+ })
+})
From 487d98f49ccf95782629f6739d7058dad02d1e55 Mon Sep 17 00:00:00 2001
From: Philipp Spiess
Date: Wed, 29 Jan 2025 17:10:47 +0100
Subject: [PATCH 4/4] Add change log
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ef0202af29c..b5d7b37c3b62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Find utilities when using the Angular class shorthand syntax ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974))
- Find utilities when using functions inside arrays ([#15974](https://github.com/tailwindlabs/tailwindcss/pull/15974))
- Ensure that `@tailwindcss/browser` does not pollute the global namespace ([#15978](https://github.com/tailwindlabs/tailwindcss/pull/15978))
+- Ensure that `tailwind-merge` is not scanned for potential class names when using the Vite plugin ([#16005](https://github.com/tailwindlabs/tailwindcss/pull/16005))
- _Upgrade_: Ensure JavaScript config files on different drives are correctly migrated ([#15927](https://github.com/tailwindlabs/tailwindcss/pull/15927))
## [4.0.0] - 2025-01-21