sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.75k stars 4.23k forks source link

[Svelte 5] Invalid CSS generated from Tailwind class names including `[&` and `url(` #14082

Open saadeghi opened 4 days ago

saadeghi commented 4 days ago

Describe the bug

@tailwindcss/vite@alpha gives Vite error if [&_div]:flex class name and url() from a Tailwind plugin exists in app together.

Note: I first opened an issue about this on Tailwind repo,
https://github.com/tailwindlabs/tailwindcss/issues/14809
but apparently the issue is from how Svelte 5 compiles the class names, not from Tailwind.

✅ Having a Tailwind plugin like this works as expected

export default ({ addComponents }) => {
  addComponents({
    ".circlemask": {
      "mask-image": "url(\"data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e\")"
    }
  })
}

✅ An arbitrary class name like [&_div]:flex works as expected.

❌ But having both in the same project using @tailwindcss/vite@4.0.0-alpha.30 causes Vite error:

[plugin:vite:css] [postcss] /src/tailwind.css:615:5: Unknown word

✅ This wasn't an issue in Tailwind CSS 3 or in Svelte 4 ✅ The issue doesn't happen with @tailwindcss/cli@4.0.0-alpha.30
It was really hard to debug for me because Vite just says Unknown word. No more details. After days I found that arbitrary class names with [] and & cause the issue if there's a plugin including url() with encoded image in it.


additional info from @thecrypticace

I did a bit of debugging here and _it appears that Svelte has compiled the .svelte file before Tailwind is able to scan it. This causes [&_div]:flex to show up in the HTML as [&_div]:flex which produces invalid CSS_ and causes it to throw under certain situations.
In this case I think the presence of a potentially re-writable url(…) in the CSS causes additional validation to happen and is why the error is conditional on the inclusion of circlemask.
We don't have a workaround for this at the moment but wanted to drop in some details about what appears to be happening.

Reproduction

https://github.com/saadeghi/tw4alpha-vite-url-bug

it's an empty Vite/Svelte project.

npm i
npm run dev

There's [&_div]:flex class in src/routes/+page.svelte There's a Tailwind plugin in src/myplugin.js

Logs

11:17:52 PM [vite] Pre-transform error: [postcss]/tw4alpha-vite-url-bug/src/tailwind.css:606:5: Unknown word
11:17:52 PM [vite] Error when evaluating SSR module /src/routes/+page.svelte:
|- CssSyntaxError: [postcss]/tw4alpha-vite-url-bug/src/tailwind.css:606:5: Unknown word
    at Input.error /tw4alpha-vite-url-bug/node_modules/postcss/lib/input.js:106:16)
    at Parser.unknownWord /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:593:22)
    at Parser.other /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:435:12)
    at Parser.parse /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:470:16)
    at parse /tw4alpha-vite-url-bug/node_modules/postcss/lib/parse.js:11:12)
    at new LazyResult /tw4alpha-vite-url-bug/node_modules/postcss/lib/lazy-result.js:133:16)
    at Processor.process /tw4alpha-vite-url-bug/node_modules/postcss/lib/processor.js:53:14)
    at compileCSS (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:36897:59)
    at async TransformPluginContext.transform (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:36170:11)
    at async PluginContainer.transform (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:49096:18)

CssSyntaxError: [postcss]/tw4alpha-vite-url-bug/src/tailwind.css:606:5: Unknown word
    at Input.error /tw4alpha-vite-url-bug/node_modules/postcss/lib/input.js:106:16)
    at Parser.unknownWord /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:593:22)
    at Parser.other /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:435:12)
    at Parser.parse /tw4alpha-vite-url-bug/node_modules/postcss/lib/parser.js:470:16)
    at parse /tw4alpha-vite-url-bug/node_modules/postcss/lib/parse.js:11:12)
    at new LazyResult /tw4alpha-vite-url-bug/node_modules/postcss/lib/lazy-result.js:133:16)
    at Processor.process /tw4alpha-vite-url-bug/node_modules/postcss/lib/processor.js:53:14)
    at compileCSS (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:36897:59)
    at async TransformPluginContext.transform (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:36170:11)
    at async PluginContainer.transform (file://tw4alpha-vite-url-bug/node_modules/vite/dist/node/chunks/dep-BWSbWtLw.js:49096:18) {
  reason: 'Unknown word',
  file: /tw4alpha-vite-url-bug/src/tailwind.css',

### System Info

```shell
Binaries:
    Node: 22.10.0 - ~/.nvm/versions/node/v22.10.0/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v21.1.0/bin/yarn
    npm: 10.9.0 - ~/.nvm/versions/node/v22.10.0/bin/npm
    pnpm: 9.12.3 - ~/.nvm/versions/node/v21.1.0/bin/pnpm
    bun: 1.1.31 - ~/.bun/bin/bun
  Browsers:
    Chrome: 130.0.6723.71
    Firefox Nightly: 96.0a1
    Safari: 18.1
  npmPackages:
    svelte: ^5.1.9 => 5.1.9

Severity

blocking all usage of svelte

dummdidumm commented 4 days ago

This comes from our attribute escaping, because & means the beginning of HTML entities. I'm not exactly sure if HTML is smart enough to detect if it's actually a real entity, and leaves it alone otherwise.

Rich-Harris commented 4 days ago

It is:

document.body.innerHTML = `<div class="&"></div>`;
document.querySelector('div').className; // '&'

document.body.innerHTML = `<div class="&excl;"></div>`;
document.querySelector('div').className; // '!'

So — reluctantly — our escaping logic needs to get smart enough to handle this edge case.

paoloricciuti commented 4 days ago

I think you should move your tailwind vite plugin before sveltekit in your vite config

dummdidumm commented 4 days ago

If I put the tailwind plugin before the sveltekit plugin, as suggested by @paoloricciuti , this works. Is there any reason you can't do that?

saadeghi commented 16 hours ago

@paoloricciuti @dummdidumm Changing the order like plugins: [tailwindcss(), sveltekit()], does not fix the issue.

dummdidumm commented 14 hours ago

Please provide an updated reproduction then - the given one causes no such error when changing the order.