mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
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: https://github.com/sveltejs/svelte/discussions/14668#discussioncomment-11620743
This commit is contained in:
parent
a11c80d6c6
commit
c766d7e274
@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
|
||||
- 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))
|
||||
- Use Vite post-processor APIs for processing Svelte `<style>` blocks ([#15436](https://github.com/tailwindlabs/tailwindcss/pull/15436))
|
||||
|
||||
## [4.0.0-beta.8] - 2024-12-17
|
||||
|
||||
|
||||
@ -57,19 +57,19 @@ test(
|
||||
<h1 class="global local underline">Hello {name}!</h1>
|
||||
|
||||
<style>
|
||||
@import 'tailwindcss' reference;
|
||||
@import './other.css';
|
||||
@reference 'tailwindcss';
|
||||
</style>
|
||||
`,
|
||||
'src/other.css': css`
|
||||
.local {
|
||||
@apply text-red-500;
|
||||
animation: 2s ease-in-out 0s infinite localKeyframes;
|
||||
animation: 2s ease-in-out infinite localKeyframes;
|
||||
}
|
||||
|
||||
:global(.global) {
|
||||
@apply text-green-500;
|
||||
animation: 2s ease-in-out 0s infinite globalKeyframes;
|
||||
animation: 2s ease-in-out infinite globalKeyframes;
|
||||
}
|
||||
|
||||
@keyframes -global-globalKeyframes {
|
||||
@ -93,18 +93,21 @@ test(
|
||||
},
|
||||
},
|
||||
async ({ exec, fs, expect }) => {
|
||||
await exec('pnpm vite build')
|
||||
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);animation:2s ease-in-out 0s infinite globalKeyframes}',
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
|
||||
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);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')
|
||||
},
|
||||
)
|
||||
|
||||
@ -164,20 +167,20 @@ test(
|
||||
<h1 class="local global underline">Hello {name}!</h1>
|
||||
|
||||
<style>
|
||||
@import 'tailwindcss' reference;
|
||||
@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 0s infinite localKeyframes;
|
||||
animation: 2s ease-in-out infinite localKeyframes;
|
||||
}
|
||||
|
||||
:global(.global) {
|
||||
@apply text-green-500;
|
||||
animation: 2s ease-in-out 0s infinite globalKeyframes;
|
||||
animation: 2s ease-in-out infinite globalKeyframes;
|
||||
}
|
||||
|
||||
@keyframes -global-globalKeyframes {
|
||||
@ -210,10 +213,10 @@ test(
|
||||
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}',
|
||||
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
|
||||
)
|
||||
expect(css).toMatch(
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
|
||||
)
|
||||
expect(css).toMatch(/@keyframes globalKeyframes\{/)
|
||||
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
|
||||
@ -235,10 +238,10 @@ test(
|
||||
let [, css] = files[0]
|
||||
expect(css).toContain(candidate`font-bold`)
|
||||
expect(css).toContain(
|
||||
'.global{color:var(--color-green-500);animation:2s ease-in-out 0s infinite globalKeyframes}',
|
||||
'.global{color:var(--color-green-500);animation:2s ease-in-out infinite globalKeyframes}',
|
||||
)
|
||||
expect(css).toMatch(
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out 0s infinite svelte-.*-localKeyframes\}/,
|
||||
/\.local.svelte-.*\{color:var\(--color-red-500\);animation:2s ease-in-out infinite svelte-.*-localKeyframes\}/,
|
||||
)
|
||||
expect(css).toMatch(/@keyframes globalKeyframes\{/)
|
||||
expect(css).toMatch(/@keyframes svelte-.*-localKeyframes\{/)
|
||||
|
||||
@ -79,11 +79,6 @@ export default function tailwindcss(): Plugin[] {
|
||||
for (let [id, root] of roots.entries()) {
|
||||
let module = server.moduleGraph.getModuleById(id)
|
||||
if (!module) {
|
||||
// The module for this root might not exist yet
|
||||
if (root.builtBeforeTransform) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Note: Removing this during SSR is not safe and will produce
|
||||
// inconsistent results based on the timing of the removal and
|
||||
// the order / timing of transforms.
|
||||
@ -184,7 +179,6 @@ export default function tailwindcss(): Plugin[] {
|
||||
}
|
||||
|
||||
return [
|
||||
svelteProcessor(roots),
|
||||
{
|
||||
// Step 1: Scan source files for candidates
|
||||
name: '@tailwindcss/vite:scan',
|
||||
@ -225,19 +219,6 @@ export default function tailwindcss(): Plugin[] {
|
||||
|
||||
let root = roots.get(id)
|
||||
|
||||
// If the root was built outside of the transform hook (e.g. in the
|
||||
// Svelte preprocessor), we still want to mark all dependencies of the
|
||||
// root as watched files.
|
||||
if (root.builtBeforeTransform) {
|
||||
root.builtBeforeTransform.forEach((file) => this.addWatchFile(file))
|
||||
root.builtBeforeTransform = undefined
|
||||
}
|
||||
|
||||
// We only process Svelte `<style>` tags in the `sveltePreprocessor`
|
||||
if (isSvelteStyle(id)) {
|
||||
return src
|
||||
}
|
||||
|
||||
if (!options?.ssr) {
|
||||
// Wait until all other files have been processed, so we can extract
|
||||
// all candidates before generating CSS. This must not be called
|
||||
@ -272,19 +253,6 @@ export default function tailwindcss(): Plugin[] {
|
||||
|
||||
let root = roots.get(id)
|
||||
|
||||
// If the root was built outside of the transform hook (e.g. in the
|
||||
// Svelte preprocessor), we still want to mark all dependencies of the
|
||||
// root as watched files.
|
||||
if (root.builtBeforeTransform) {
|
||||
root.builtBeforeTransform.forEach((file) => this.addWatchFile(file))
|
||||
root.builtBeforeTransform = undefined
|
||||
}
|
||||
|
||||
// We only process Svelte `<style>` tags in the `sveltePreprocessor`
|
||||
if (isSvelteStyle(id)) {
|
||||
return src
|
||||
}
|
||||
|
||||
// We do a first pass to generate valid CSS for the downstream plugins.
|
||||
// However, since not all candidates are guaranteed to be extracted by
|
||||
// this time, we have to re-run a transform for the root later.
|
||||
@ -304,9 +272,6 @@ export default function tailwindcss(): Plugin[] {
|
||||
I.start('[@tailwindcss/vite] (render start)')
|
||||
|
||||
for (let [id, root] of roots.entries()) {
|
||||
// Do not do a second render pass on Svelte `<style>` tags.
|
||||
if (isSvelteStyle(id)) continue
|
||||
|
||||
let generated = await regenerateOptimizedCss(
|
||||
root,
|
||||
// During the renderStart phase, we can not add watch files since
|
||||
@ -341,23 +306,13 @@ function isPotentialCssRootFile(id: string) {
|
||||
if (id.includes('/.vite/')) return
|
||||
let extension = getExtension(id)
|
||||
let isCssFile =
|
||||
(extension === 'css' ||
|
||||
(extension === 'vue' && id.includes('&lang.css')) ||
|
||||
(extension === 'astro' && id.includes('&lang.css')) ||
|
||||
// We want to process Svelte `<style>` tags to properly add dependency
|
||||
// tracking for imported files.
|
||||
isSvelteStyle(id)) &&
|
||||
(extension === 'css' || id.includes('&lang.css')) &&
|
||||
// Don't intercept special static asset resources
|
||||
!SPECIAL_QUERY_RE.test(id)
|
||||
|
||||
return isCssFile
|
||||
}
|
||||
|
||||
function isSvelteStyle(id: string) {
|
||||
let extension = getExtension(id)
|
||||
return extension === 'svelte' && id.includes('&lang.css')
|
||||
}
|
||||
|
||||
function optimizeCss(
|
||||
input: string,
|
||||
{ file = 'input.css', minify = false }: { file?: string; minify?: boolean } = {},
|
||||
@ -425,14 +380,6 @@ class Root {
|
||||
// `renderStart` hook.
|
||||
public lastContent: string = ''
|
||||
|
||||
// When set, indicates that the root was built before the Vite transform hook
|
||||
// was being called. This can happen in scenarios like when preprocessing
|
||||
// `<style>` tags for Svelte components.
|
||||
//
|
||||
// It can be set to a list of dependencies that will be added whenever the
|
||||
// next `transform` hook is being called.
|
||||
public builtBeforeTransform: string[] | undefined
|
||||
|
||||
// The lazily-initialized Tailwind compiler components. These are persisted
|
||||
// throughout rebuilds but will be re-initialized if the rebuild strategy is
|
||||
// set to `full`.
|
||||
@ -626,75 +573,3 @@ class Root {
|
||||
return shared
|
||||
}
|
||||
}
|
||||
|
||||
// Register a plugin that can hook into the Svelte preprocessor if Svelte is
|
||||
// configured. This allows us to transform CSS in `<style>` tags and create a
|
||||
// stricter version of CSS that passes the Svelte compiler.
|
||||
//
|
||||
// Note that these files will not undergo a second pass through the vite
|
||||
// transpiler later. This means that `@tailwind utilities;` will not be up to
|
||||
// date.
|
||||
//
|
||||
// In practice, it is discouraged to use `@tailwind utilities;` inside Svelte
|
||||
// components, as the styles it create would be scoped anyways. Use an external
|
||||
// `.css` file instead.
|
||||
function svelteProcessor(roots: DefaultMap<string, Root>): Plugin {
|
||||
return {
|
||||
name: '@tailwindcss/svelte',
|
||||
api: {
|
||||
sveltePreprocess: {
|
||||
async style({
|
||||
content,
|
||||
filename,
|
||||
markup,
|
||||
}: {
|
||||
content: string
|
||||
filename?: string
|
||||
markup: string
|
||||
}) {
|
||||
if (!filename) return
|
||||
using I = new Instrumentation()
|
||||
DEBUG && I.start('[@tailwindcss/vite] Preprocess svelte')
|
||||
|
||||
// Create the ID used by Vite to identify the `<style>` contents. This
|
||||
// way, the Vite `transform` hook can find the right root and thus
|
||||
// track the right dependencies.
|
||||
let id = filename + '?svelte&type=style&lang.css'
|
||||
|
||||
let root = roots.get(id)
|
||||
|
||||
// Since a Svelte pre-processor call means that the CSS has changed,
|
||||
// we need to trigger a rebuild.
|
||||
root.requiresRebuild = true
|
||||
|
||||
// Mark this root as being built before the Vite transform hook is
|
||||
// called. We capture all eventually added dependencies so that we can
|
||||
// connect them to the vite module graph later, when the transform
|
||||
// hook is called.
|
||||
root.builtBeforeTransform = []
|
||||
|
||||
// We only want to consider candidates from the current template file,
|
||||
// this ensures that no one can depend on this having the full candidate
|
||||
// list in some builds (as this is undefined behavior).
|
||||
let scanner = new Scanner({})
|
||||
root.overwriteCandidates = scanner.scanFiles([
|
||||
{ content: markup, file: filename, extension: 'svelte' },
|
||||
])
|
||||
|
||||
let generated = await root.generate(
|
||||
content,
|
||||
(file) => root.builtBeforeTransform?.push(file),
|
||||
I,
|
||||
)
|
||||
|
||||
if (!generated) {
|
||||
roots.delete(id)
|
||||
return
|
||||
}
|
||||
|
||||
return { code: generated }
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user