Skip to content

Commit

Permalink
Vite: Don't track candidate changes for Svelte <style> tags (#14981)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
philipp-spiess authored Nov 13, 2024
1 parent 13f05e2 commit dda181b
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 68 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980))
- Support specifying the color interpolation method for gradients via modifier ([#14984](https://github.com/tailwindlabs/tailwindcss/pull/14984))

### Fixed

- 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))

## [4.0.0-alpha.33] - 2024-11-11

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ interface TestContext {
dumpFiles(pattern: string): Promise<string>
expectFileToContain(
filePath: string,
contents: string | string[] | RegExp | RegExp[],
contents: string | RegExp | (string | RegExp)[],
): Promise<void>
expectFileNotToContain(filePath: string, contents: string | string[]): Promise<void>
}
Expand Down
127 changes: 102 additions & 25 deletions integrations/vite/svelte.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,50 @@ test(
target: document.body,
})
`,
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@import 'tailwindcss/utilities';
`,
'src/App.svelte': html`
<script>
import './index.css'
let name = 'world'
</script>
<h1 class="foo underline">Hello {name}!</h1>
<h1 class="global local underline">Hello {name}!</h1>
<style global>
@import 'tailwindcss/utilities';
<style>
@import 'tailwindcss/theme' theme(reference);
@import './components.css';
@import './other.css';
</style>
`,
'src/components.css': css`
.foo {
'src/other.css': css`
.local {
@apply text-red-500;
animation: 2s ease-in-out 0s infinite localKeyframes;
}
:global(.global) {
@apply text-green-500;
animation: 2s ease-in-out 0s infinite globalKeyframes;
}
@keyframes -global-globalKeyframes {
0% {
opacity: 0;
}
100% {
opacity: 100%;
}
}
@keyframes localKeyframes {
0% {
opacity: 0;
}
100% {
opacity: 100%;
}
}
`,
},
Expand All @@ -74,7 +102,13 @@ test(
let files = await fs.glob('dist/**/*.css')
expect(files).toHaveLength(1)

await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`foo`])
await fs.expectFileToContain(files[0][0], [
candidate`underline`,
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
/@keyframes globalKeyframes\{/,
/@keyframes svelte-.*-localKeyframes\{/,
])
},
)

Expand Down Expand Up @@ -127,51 +161,94 @@ test(
`,
'src/App.svelte': html`
<script>
import './index.css'
let name = 'world'
</script>
<h1 class="foo underline">Hello {name}!</h1>
<h1 class="local global underline">Hello {name}!</h1>
<style global>
@import 'tailwindcss/utilities';
<style>
@import 'tailwindcss/theme' theme(reference);
@import './components.css';
@import './other.css';
</style>
`,
'src/components.css': css`
.foo {
'src/index.css': css`
@import 'tailwindcss/theme' theme(reference);
@import 'tailwindcss/utilities';
`,
'src/other.css': css`
.local {
@apply text-red-500;
animation: 2s ease-in-out 0s infinite localKeyframes;
}
:global(.global) {
@apply text-green-500;
animation: 2s ease-in-out 0s infinite globalKeyframes;
}
@keyframes -global-globalKeyframes {
0% {
opacity: 0;
}
100% {
opacity: 100%;
}
}
@keyframes localKeyframes {
0% {
opacity: 0;
}
100% {
opacity: 100%;
}
}
`,
},
},
async ({ fs, spawn }) => {
await spawn(`pnpm vite build --watch`)

let filename = ''
await retryAssertion(async () => {
let files = await fs.glob('dist/**/*.css')
expect(files).toHaveLength(1)
filename = files[0][0]
let [, css] = files[0]
expect(css).toContain(candidate`underline`)
expect(css).toContain(
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
)
expect(css).toMatch(
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
)
expect(css).toMatch(/@keyframes globalKeyframes\{/)
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
})

await fs.expectFileToContain(filename, [candidate`foo`, candidate`underline`])
await fs.write(
'src/App.svelte',
(await fs.read('src/App.svelte')).replace('underline', 'font-bold bar'),
)

await fs.write(
'src/components.css',
css`
.bar {
@apply text-green-500;
}
`,
'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`underline`)
expect(css).toContain(candidate`bar`)
expect(css).not.toContain(candidate`foo`)
expect(css).toContain(candidate`font-bold`)
expect(css).toContain(
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
)
expect(css).toMatch(
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
)
expect(css).toMatch(/@keyframes globalKeyframes\{/)
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
expect(css).toMatch(/\.bar.svelte-.*\{color:var\(--color-pink-500\)\}/)
})
},
)
Loading

0 comments on commit dda181b

Please sign in to comment.