mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Fixes #16725 When using `@reference "tailwindcss";` inside a separate CSS root (e.g. Svelte `<style>` components, CSS modules, etc.), we have no guarantee that the CSS variables will be defined in the main stylesheet (or if there even is one). To work around potential issues with this we decided in #16676 that we would emit all used CSS variables from the `@theme` inside the `@reference` block. However, this is not only a bit surprising but also unexpected in CSS modules and Next.js that **requires CSS module files to only create scope-able declarations**. To fix this issue, we decided to not emit CSS variables but instead ensure all `var(…)` calls we create for theme values in reference mode will simply have their fallback value added. This ensures styles work as-expected even if the root Tailwind file does not pick up the variable as being used or _if you don't add a root at all_. Furthermore we do not duplicate any variable declarations across your stylesheets and you still have the ability to change variables at runtime. ## Test plan - Updated snapshots everywhere (see diff) - New Next.js CSS modules integration test
254 lines
6.8 KiB
TypeScript
254 lines
6.8 KiB
TypeScript
import { candidate, css, html, json, retryAssertion, test, ts } from '../utils'
|
|
|
|
test(
|
|
'production build',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"svelte": "^5",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"@sveltejs/vite-plugin-svelte": "^5",
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"vite": "^6"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import { defineConfig } from 'vite'
|
|
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
svelte({
|
|
preprocess: [vitePreprocess()],
|
|
}),
|
|
tailwindcss(),
|
|
],
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<!doctype html>
|
|
<html>
|
|
<body>
|
|
<div id="app"></div>
|
|
<script type="module" src="./src/main.ts"></script>
|
|
</body>
|
|
</html>
|
|
`,
|
|
'src/main.ts': ts`
|
|
import App from './App.svelte'
|
|
const app = new App({
|
|
target: document.body,
|
|
})
|
|
`,
|
|
'src/index.css': css`@import 'tailwindcss';`,
|
|
'src/App.svelte': html`
|
|
<script>
|
|
import './index.css'
|
|
let name = 'world'
|
|
</script>
|
|
|
|
<h1 class="global local underline">Hello {name}!</h1>
|
|
|
|
<style>
|
|
@import './other.css';
|
|
@reference 'tailwindcss';
|
|
</style>
|
|
`,
|
|
'src/other.css': css`
|
|
.local {
|
|
@apply text-red-500;
|
|
animation: 2s ease-in-out infinite localKeyframes;
|
|
}
|
|
|
|
:global(.global) {
|
|
@apply text-green-500;
|
|
animation: 2s ease-in-out infinite globalKeyframes;
|
|
}
|
|
|
|
@keyframes -global-globalKeyframes {
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 100%;
|
|
}
|
|
}
|
|
|
|
@keyframes localKeyframes {
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 100%;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ exec, fs, expect }) => {
|
|
let output = await exec('pnpm vite build')
|
|
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
|
|
await fs.expectFileToContain(files[0][0], [
|
|
candidate`underline`,
|
|
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
|
|
/\.local.svelte-.*\{color:var\(--color-red-500\,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
|
|
/@keyframes globalKeyframes\{/,
|
|
/@keyframes svelte-.*-localKeyframes\{/,
|
|
])
|
|
|
|
// Should not print any warnings
|
|
expect(output).not.toContain('vite-plugin-svelte')
|
|
},
|
|
)
|
|
|
|
test(
|
|
'watch mode',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"svelte": "^5",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"@sveltejs/vite-plugin-svelte": "^5",
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"vite": "^6"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import { defineConfig } from 'vite'
|
|
import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
svelte({
|
|
preprocess: [vitePreprocess()],
|
|
}),
|
|
tailwindcss(),
|
|
],
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<!doctype html>
|
|
<html>
|
|
<body>
|
|
<div id="app"></div>
|
|
<script type="module" src="./src/main.ts"></script>
|
|
</body>
|
|
</html>
|
|
`,
|
|
'src/main.ts': ts`
|
|
import App from './App.svelte'
|
|
const app = new App({
|
|
target: document.body,
|
|
})
|
|
`,
|
|
'src/App.svelte': html`
|
|
<script>
|
|
import './index.css'
|
|
let name = 'world'
|
|
</script>
|
|
|
|
<h1 class="local global underline">Hello {name}!</h1>
|
|
|
|
<style>
|
|
@import './other.css';
|
|
@reference 'tailwindcss';
|
|
</style>
|
|
`,
|
|
'src/index.css': css` @import 'tailwindcss'; `,
|
|
'src/other.css': css`
|
|
.local {
|
|
@apply text-red-500;
|
|
animation: 2s ease-in-out infinite localKeyframes;
|
|
}
|
|
|
|
:global(.global) {
|
|
@apply text-green-500;
|
|
animation: 2s ease-in-out infinite globalKeyframes;
|
|
}
|
|
|
|
@keyframes -global-globalKeyframes {
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 100%;
|
|
}
|
|
}
|
|
|
|
@keyframes localKeyframes {
|
|
0% {
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
opacity: 100%;
|
|
}
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, spawn, expect }) => {
|
|
let process = await spawn(`pnpm vite build --watch`)
|
|
await process.onStdout((m) => m.includes('built in'))
|
|
|
|
await retryAssertion(async () => {
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [, css] = files[0]
|
|
expect(css).toContain(candidate`underline`)
|
|
expect(css).toContain(
|
|
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
|
|
)
|
|
expect(css).toMatch(
|
|
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
|
|
)
|
|
expect(css).toMatch(/@keyframes globalKeyframes\{/)
|
|
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
|
|
})
|
|
|
|
await fs.write(
|
|
'src/App.svelte',
|
|
(await fs.read('src/App.svelte')).replace('underline', 'font-bold bar'),
|
|
)
|
|
|
|
await fs.write(
|
|
'src/other.css',
|
|
`${await fs.read('src/other.css')}\n.bar { @apply text-pink-500; }`,
|
|
)
|
|
|
|
await retryAssertion(async () => {
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [, css] = files[0]
|
|
expect(css).toContain(candidate`font-bold`)
|
|
expect(css).toContain(
|
|
'.global{color:var(--color-green-500,oklch(.723 .219 149.579));animation:2s ease-in-out infinite globalKeyframes}',
|
|
)
|
|
expect(css).toMatch(
|
|
/\.local.svelte-.*\{color:var\(--color-red-500,oklch\(\.637 \.237 25\.331\)\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
|
|
)
|
|
expect(css).toMatch(/@keyframes globalKeyframes\{/)
|
|
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
|
|
expect(css).toMatch(
|
|
/\.bar.svelte-.*\{color:var\(--color-pink-500,oklch\(\.656 \.241 354\.308\)\)\}/,
|
|
)
|
|
})
|
|
},
|
|
)
|