Skip to content

Commit dda181b

Browse files
Vite: Don't track candidate changes for Svelte <style> tags (#14981)
Closes #14965 This PR changes the way we register Tailwind CSS as a Svelte preprocessor when using the Vite plugin. The idea is to reduce the bookkeeping for interacting with CSS inside `<style>` tags so that we have a more consistent behavior and make sure the Svelte-specific post-processing (e.g. local class mangling) works as expected. Prior to this change, we were running Tailwind CSS as a Svelte preprocessor and then we would transform the file again when necessary inside the Vite `transform` hook. This is necessary to have the right list of candidates when we build the final CSS, but it did cause some situation to not apply the Svelte post-processors anymore. The repro for this seemed to indicate a timing specific issue and I did notice that specifically the code where we invalidate modules in Vite would cause unexpected processing orders. We do, however, not officially support rendering utilities (`@tailwind utilities;`) inside `<style>` tag. This is because the `<style>` block is scoped by default and emitting utilities will always include utilities for all classes in your whole project. For this case, we highly recommend creating as separate `.css` file and importing it explicitly. With this limitation in place, the additional bookkeeping where we need to invalidate modules because the candidate list has changed is no longer necessary and removing it allows us to reduce the complexity of the Svelte integration. ## Test Plan https://github.com/user-attachments/assets/32c8e91f-ab21-48c6-aeaf-2582273b9bac Not seen in the test plan above I also tested the `pnpm build --watch` step of the Vite project. This does require the `pnpm preview` server to restart but the build artifact are updated as expected.
1 parent 13f05e2 commit dda181b

File tree

4 files changed

+190
-68
lines changed

4 files changed

+190
-68
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980))
1313
- Support specifying the color interpolation method for gradients via modifier ([#14984](https://github.com/tailwindlabs/tailwindcss/pull/14984))
1414

15+
### Fixed
16+
17+
- Ensure that CSS inside Svelte `<style>` blocks always run the expected Svelte processors when using the Vite extension ([#14981](https://github.com/tailwindlabs/tailwindcss/pull/14981))
18+
1519
## [4.0.0-alpha.33] - 2024-11-11
1620

1721
### Fixed

integrations/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface TestContext {
4646
dumpFiles(pattern: string): Promise<string>
4747
expectFileToContain(
4848
filePath: string,
49-
contents: string | string[] | RegExp | RegExp[],
49+
contents: string | RegExp | (string | RegExp)[],
5050
): Promise<void>
5151
expectFileNotToContain(filePath: string, contents: string | string[]): Promise<void>
5252
}

integrations/vite/svelte.test.ts

Lines changed: 102 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,50 @@ test(
4848
target: document.body,
4949
})
5050
`,
51+
'src/index.css': css`
52+
@import 'tailwindcss/theme' theme(reference);
53+
@import 'tailwindcss/utilities';
54+
`,
5155
'src/App.svelte': html`
5256
<script>
57+
import './index.css'
5358
let name = 'world'
5459
</script>
5560
56-
<h1 class="foo underline">Hello {name}!</h1>
61+
<h1 class="global local underline">Hello {name}!</h1>
5762
58-
<style global>
59-
@import 'tailwindcss/utilities';
63+
<style>
6064
@import 'tailwindcss/theme' theme(reference);
61-
@import './components.css';
65+
@import './other.css';
6266
</style>
6367
`,
64-
'src/components.css': css`
65-
.foo {
68+
'src/other.css': css`
69+
.local {
6670
@apply text-red-500;
71+
animation: 2s ease-in-out 0s infinite localKeyframes;
72+
}
73+
74+
:global(.global) {
75+
@apply text-green-500;
76+
animation: 2s ease-in-out 0s infinite globalKeyframes;
77+
}
78+
79+
@keyframes -global-globalKeyframes {
80+
0% {
81+
opacity: 0;
82+
}
83+
100% {
84+
opacity: 100%;
85+
}
86+
}
87+
88+
@keyframes localKeyframes {
89+
0% {
90+
opacity: 0;
91+
}
92+
100% {
93+
opacity: 100%;
94+
}
6795
}
6896
`,
6997
},
@@ -74,7 +102,13 @@ test(
74102
let files = await fs.glob('dist/**/*.css')
75103
expect(files).toHaveLength(1)
76104

77-
await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`foo`])
105+
await fs.expectFileToContain(files[0][0], [
106+
candidate`underline`,
107+
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
108+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
109+
/@keyframes globalKeyframes\{/,
110+
/@keyframes svelte-.*-localKeyframes\{/,
111+
])
78112
},
79113
)
80114

@@ -127,51 +161,94 @@ test(
127161
`,
128162
'src/App.svelte': html`
129163
<script>
164+
import './index.css'
130165
let name = 'world'
131166
</script>
132167
133-
<h1 class="foo underline">Hello {name}!</h1>
168+
<h1 class="local global underline">Hello {name}!</h1>
134169
135-
<style global>
136-
@import 'tailwindcss/utilities';
170+
<style>
137171
@import 'tailwindcss/theme' theme(reference);
138-
@import './components.css';
172+
@import './other.css';
139173
</style>
140174
`,
141-
'src/components.css': css`
142-
.foo {
175+
'src/index.css': css`
176+
@import 'tailwindcss/theme' theme(reference);
177+
@import 'tailwindcss/utilities';
178+
`,
179+
'src/other.css': css`
180+
.local {
143181
@apply text-red-500;
182+
animation: 2s ease-in-out 0s infinite localKeyframes;
183+
}
184+
185+
:global(.global) {
186+
@apply text-green-500;
187+
animation: 2s ease-in-out 0s infinite globalKeyframes;
188+
}
189+
190+
@keyframes -global-globalKeyframes {
191+
0% {
192+
opacity: 0;
193+
}
194+
100% {
195+
opacity: 100%;
196+
}
197+
}
198+
199+
@keyframes localKeyframes {
200+
0% {
201+
opacity: 0;
202+
}
203+
100% {
204+
opacity: 100%;
205+
}
144206
}
145207
`,
146208
},
147209
},
148210
async ({ fs, spawn }) => {
149211
await spawn(`pnpm vite build --watch`)
150212

151-
let filename = ''
152213
await retryAssertion(async () => {
153214
let files = await fs.glob('dist/**/*.css')
154215
expect(files).toHaveLength(1)
155-
filename = files[0][0]
216+
let [, css] = files[0]
217+
expect(css).toContain(candidate`underline`)
218+
expect(css).toContain(
219+
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
220+
)
221+
expect(css).toMatch(
222+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
223+
)
224+
expect(css).toMatch(/@keyframes globalKeyframes\{/)
225+
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
156226
})
157227

158-
await fs.expectFileToContain(filename, [candidate`foo`, candidate`underline`])
228+
await fs.write(
229+
'src/App.svelte',
230+
(await fs.read('src/App.svelte')).replace('underline', 'font-bold bar'),
231+
)
159232

160233
await fs.write(
161-
'src/components.css',
162-
css`
163-
.bar {
164-
@apply text-green-500;
165-
}
166-
`,
234+
'src/other.css',
235+
`${await fs.read('src/other.css')}\n.bar { @apply text-pink-500; }`,
167236
)
237+
168238
await retryAssertion(async () => {
169239
let files = await fs.glob('dist/**/*.css')
170240
expect(files).toHaveLength(1)
171241
let [, css] = files[0]
172-
expect(css).toContain(candidate`underline`)
173-
expect(css).toContain(candidate`bar`)
174-
expect(css).not.toContain(candidate`foo`)
242+
expect(css).toContain(candidate`font-bold`)
243+
expect(css).toContain(
244+
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
245+
)
246+
expect(css).toMatch(
247+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
248+
)
249+
expect(css).toMatch(/@keyframes globalKeyframes\{/)
250+
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
251+
expect(css).toMatch(/\.bar.svelte-.*\{color:var\(--color-pink-500\)\}/)
175252
})
176253
},
177254
)

0 commit comments

Comments
 (0)