Skip to content

Commit c766d7e

Browse files
Vite: Process <style> blocks inside Svelte files as a post-processor (#15436)
This PR changes the Svelte integration to be a post-processor similar to what we're doing for `<style>` blocks in Astro and Vue files. More details can be found in the GitHub discussion: sveltejs/svelte#14668 (reply in thread)
1 parent a11c80d commit c766d7e

File tree

3 files changed

+18
-139
lines changed

3 files changed

+18
-139
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
### Changed
3232

3333
- Removed `--container-prose` in favor of a deprecated `--max-width-prose` theme variable so that `*-prose` is only available for max-width utilities and only for backward compatibility ([#15439](https://github.com/tailwindlabs/tailwindcss/pull/15439))
34+
- Use Vite post-processor APIs for processing Svelte `<style>` blocks ([#15436](https://github.com/tailwindlabs/tailwindcss/pull/15436))
3435

3536
## [4.0.0-beta.8] - 2024-12-17
3637

integrations/vite/svelte.test.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,19 @@ test(
5757
<h1 class="global local underline">Hello {name}!</h1>
5858
5959
<style>
60-
@import 'tailwindcss' reference;
6160
@import './other.css';
61+
@reference 'tailwindcss';
6262
</style>
6363
`,
6464
'src/other.css': css`
6565
.local {
6666
@apply text-red-500;
67-
animation: 2s ease-in-out 0s infinite localKeyframes;
67+
animation: 2s ease-in-out infinite localKeyframes;
6868
}
6969
7070
:global(.global) {
7171
@apply text-green-500;
72-
animation: 2s ease-in-out 0s infinite globalKeyframes;
72+
animation: 2s ease-in-out infinite globalKeyframes;
7373
}
7474
7575
@keyframes -global-globalKeyframes {
@@ -93,18 +93,21 @@ test(
9393
},
9494
},
9595
async ({ exec, fs, expect }) => {
96-
await exec('pnpm vite build')
96+
let output = await exec('pnpm vite build')
9797

9898
let files = await fs.glob('dist/**/*.css')
9999
expect(files).toHaveLength(1)
100100

101101
await fs.expectFileToContain(files[0][0], [
102102
candidate`underline`,
103-
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
104-
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
103+
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
104+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
105105
/@keyframes globalKeyframes\{/,
106106
/@keyframes svelte-.*-localKeyframes\{/,
107107
])
108+
109+
// Should not print any warnings
110+
expect(output).not.toContain('vite-plugin-svelte')
108111
},
109112
)
110113

@@ -164,20 +167,20 @@ test(
164167
<h1 class="local global underline">Hello {name}!</h1>
165168
166169
<style>
167-
@import 'tailwindcss' reference;
168170
@import './other.css';
171+
@reference 'tailwindcss';
169172
</style>
170173
`,
171174
'src/index.css': css` @import 'tailwindcss'; `,
172175
'src/other.css': css`
173176
.local {
174177
@apply text-red-500;
175-
animation: 2s ease-in-out 0s infinite localKeyframes;
178+
animation: 2s ease-in-out infinite localKeyframes;
176179
}
177180
178181
:global(.global) {
179182
@apply text-green-500;
180-
animation: 2s ease-in-out 0s infinite globalKeyframes;
183+
animation: 2s ease-in-out infinite globalKeyframes;
181184
}
182185
183186
@keyframes -global-globalKeyframes {
@@ -210,10 +213,10 @@ test(
210213
let [, css] = files[0]
211214
expect(css).toContain(candidate`underline`)
212215
expect(css).toContain(
213-
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
216+
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
214217
)
215218
expect(css).toMatch(
216-
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
219+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
217220
)
218221
expect(css).toMatch(/@keyframes globalKeyframes\{/)
219222
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
@@ -235,10 +238,10 @@ test(
235238
let [, css] = files[0]
236239
expect(css).toContain(candidate`font-bold`)
237240
expect(css).toContain(
238-
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
241+
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
239242
)
240243
expect(css).toMatch(
241-
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
244+
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
242245
)
243246
expect(css).toMatch(/@keyframes globalKeyframes\{/)
244247
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)

packages/@tailwindcss-vite/src/index.ts

Lines changed: 1 addition & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,6 @@ export default function tailwindcss(): Plugin[] {
7979
for (let [id, root] of roots.entries()) {
8080
let module = server.moduleGraph.getModuleById(id)
8181
if (!module) {
82-
// The module for this root might not exist yet
83-
if (root.builtBeforeTransform) {
84-
continue
85-
}
86-
8782
// Note: Removing this during SSR is not safe and will produce
8883
// inconsistent results based on the timing of the removal and
8984
// the order / timing of transforms.
@@ -184,7 +179,6 @@ export default function tailwindcss(): Plugin[] {
184179
}
185180

186181
return [
187-
svelteProcessor(roots),
188182
{
189183
// Step 1: Scan source files for candidates
190184
name: '@tailwindcss/vite:scan',
@@ -225,19 +219,6 @@ export default function tailwindcss(): Plugin[] {
225219

226220
let root = roots.get(id)
227221

228-
// If the root was built outside of the transform hook (e.g. in the
229-
// Svelte preprocessor), we still want to mark all dependencies of the
230-
// root as watched files.
231-
if (root.builtBeforeTransform) {
232-
root.builtBeforeTransform.forEach((file) => this.addWatchFile(file))
233-
root.builtBeforeTransform = undefined
234-
}
235-
236-
// We only process Svelte `<style>` tags in the `sveltePreprocessor`
237-
if (isSvelteStyle(id)) {
238-
return src
239-
}
240-
241222
if (!options?.ssr) {
242223
// Wait until all other files have been processed, so we can extract
243224
// all candidates before generating CSS. This must not be called
@@ -272,19 +253,6 @@ export default function tailwindcss(): Plugin[] {
272253

273254
let root = roots.get(id)
274255

275-
// If the root was built outside of the transform hook (e.g. in the
276-
// Svelte preprocessor), we still want to mark all dependencies of the
277-
// root as watched files.
278-
if (root.builtBeforeTransform) {
279-
root.builtBeforeTransform.forEach((file) => this.addWatchFile(file))
280-
root.builtBeforeTransform = undefined
281-
}
282-
283-
// We only process Svelte `<style>` tags in the `sveltePreprocessor`
284-
if (isSvelteStyle(id)) {
285-
return src
286-
}
287-
288256
// We do a first pass to generate valid CSS for the downstream plugins.
289257
// However, since not all candidates are guaranteed to be extracted by
290258
// this time, we have to re-run a transform for the root later.
@@ -304,9 +272,6 @@ export default function tailwindcss(): Plugin[] {
304272
I.start('[@tailwindcss/vite] (render start)')
305273

306274
for (let [id, root] of roots.entries()) {
307-
// Do not do a second render pass on Svelte `<style>` tags.
308-
if (isSvelteStyle(id)) continue
309-
310275
let generated = await regenerateOptimizedCss(
311276
root,
312277
// During the renderStart phase, we can not add watch files since
@@ -341,23 +306,13 @@ function isPotentialCssRootFile(id: string) {
341306
if (id.includes('/.vite/')) return
342307
let extension = getExtension(id)
343308
let isCssFile =
344-
(extension === 'css' ||
345-
(extension === 'vue' && id.includes('&lang.css')) ||
346-
(extension === 'astro' && id.includes('&lang.css')) ||
347-
// We want to process Svelte `<style>` tags to properly add dependency
348-
// tracking for imported files.
349-
isSvelteStyle(id)) &&
309+
(extension === 'css' || id.includes('&lang.css')) &&
350310
// Don't intercept special static asset resources
351311
!SPECIAL_QUERY_RE.test(id)
352312

353313
return isCssFile
354314
}
355315

356-
function isSvelteStyle(id: string) {
357-
let extension = getExtension(id)
358-
return extension === 'svelte' && id.includes('&lang.css')
359-
}
360-
361316
function optimizeCss(
362317
input: string,
363318
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
@@ -425,14 +380,6 @@ class Root {
425380
// `renderStart` hook.
426381
public lastContent: string = ''
427382

428-
// When set, indicates that the root was built before the Vite transform hook
429-
// was being called. This can happen in scenarios like when preprocessing
430-
// `<style>` tags for Svelte components.
431-
//
432-
// It can be set to a list of dependencies that will be added whenever the
433-
// next `transform` hook is being called.
434-
public builtBeforeTransform: string[] | undefined
435-
436383
// The lazily-initialized Tailwind compiler components. These are persisted
437384
// throughout rebuilds but will be re-initialized if the rebuild strategy is
438385
// set to `full`.
@@ -626,75 +573,3 @@ class Root {
626573
return shared
627574
}
628575
}
629-
630-
// Register a plugin that can hook into the Svelte preprocessor if Svelte is
631-
// configured. This allows us to transform CSS in `<style>` tags and create a
632-
// stricter version of CSS that passes the Svelte compiler.
633-
//
634-
// Note that these files will not undergo a second pass through the vite
635-
// transpiler later. This means that `@tailwind utilities;` will not be up to
636-
// date.
637-
//
638-
// In practice, it is discouraged to use `@tailwind utilities;` inside Svelte
639-
// components, as the styles it create would be scoped anyways. Use an external
640-
// `.css` file instead.
641-
function svelteProcessor(roots: DefaultMap<string, Root>): Plugin {
642-
return {
643-
name: '@tailwindcss/svelte',
644-
api: {
645-
sveltePreprocess: {
646-
async style({
647-
content,
648-
filename,
649-
markup,
650-
}: {
651-
content: string
652-
filename?: string
653-
markup: string
654-
}) {
655-
if (!filename) return
656-
using I = new Instrumentation()
657-
DEBUG && I.start('[@tailwindcss/vite] Preprocess svelte')
658-
659-
// Create the ID used by Vite to identify the `<style>` contents. This
660-
// way, the Vite `transform` hook can find the right root and thus
661-
// track the right dependencies.
662-
let id = filename + '?svelte&type=style&lang.css'
663-
664-
let root = roots.get(id)
665-
666-
// Since a Svelte pre-processor call means that the CSS has changed,
667-
// we need to trigger a rebuild.
668-
root.requiresRebuild = true
669-
670-
// Mark this root as being built before the Vite transform hook is
671-
// called. We capture all eventually added dependencies so that we can
672-
// connect them to the vite module graph later, when the transform
673-
// hook is called.
674-
root.builtBeforeTransform = []
675-
676-
// We only want to consider candidates from the current template file,
677-
// this ensures that no one can depend on this having the full candidate
678-
// list in some builds (as this is undefined behavior).
679-
let scanner = new Scanner({})
680-
root.overwriteCandidates = scanner.scanFiles([
681-
{ content: markup, file: filename, extension: 'svelte' },
682-
])
683-
684-
let generated = await root.generate(
685-
content,
686-
(file) => root.builtBeforeTransform?.push(file),
687-
I,
688-
)
689-
690-
if (!generated) {
691-
roots.delete(id)
692-
return
693-
}
694-
695-
return { code: generated }
696-
},
697-
},
698-
},
699-
}
700-
}

0 commit comments

Comments
 (0)