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