`,
},
@@ -65,5 +71,7 @@ test(
expect(files).toHaveLength(1)
await fs.expectFileToContain(files[0][0], [candidate`underline`, candidate`foo`])
+
+ await fs.expectFileNotToContain(files[0][0], [':deep(.bar)'])
},
)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 57eaf42affb5..014755d60a4d 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -133,6 +133,10 @@ export default function tailwindcss(): Plugin[] {
return css
}
+ function isUsingLightningCSS() {
+ return config?.css.transformer === 'lightningcss'
+ }
+
return [
{
// Step 1: Scan source files for candidates
@@ -206,12 +210,84 @@ export default function tailwindcss(): Plugin[] {
},
{
+ // Step 2 (full build): Generate CSS
+ name: '@tailwindcss/vite:generate:build',
+ apply: 'build',
+
+ // NOTE:
+ // We used to use `enforce: 'pre'` here because Tailwind CSS can handle
+ // imports itself. However, this caused two problems:
+ //
+ // - Relative asset URL rewriting for was not happening so things like
+ // `background-image: url(../image.png)` could break if done in an
+ // imported CSS file.
+ //
+ // - Processing of Vue scoped style blocks didn't happen at the right time
+ // which caused `:deep(…)` to end up in the generated CSS rather than
+ // appropriately handled by Vue.
+ //
+ // This does mean that Tailwind is no longer handling the imports itself
+ // which is not ideal but it's a reasonable tradeoff until we can resolve
+ // both of these issues with Tailwind's own import handling.
+
+ async transform(src, id) {
+ if (isUsingLightningCSS()) return
+ if (!isPotentialCssRootFile(id)) return
+
+ let root = roots.get(id)
+
+ // 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.
+ let generated = await root.generate(src, (file) => this.addWatchFile(file))
+ if (!generated) {
+ roots.delete(id)
+ return src
+ }
+ return { code: generated }
+ },
+
+ // `renderStart` runs in the bundle generation stage after all transforms.
+ // We must run before `enforce: post` so the updated chunks are picked up
+ // by vite:css-post.
+ async renderStart() {
+ if (isUsingLightningCSS()) return
+
+ for (let [id, root] of roots.entries()) {
+ let generated = await regenerateOptimizedCss(
+ root,
+ // During the renderStart phase, we can not add watch files since
+ // those would not be causing a refresh of the right CSS file. This
+ // should not be an issue since we did already process the CSS file
+ // before and the dependencies should not be changed (only the
+ // candidate list might have)
+ () => {},
+ )
+ if (!generated) {
+ roots.delete(id)
+ continue
+ }
+
+ // These plugins have side effects which, during build, results in CSS
+ // being written to the output dir. We need to run them here to ensure
+ // the CSS is written before the bundle is generated.
+ await transformWithPlugins(this, id, generated)
+ }
+ },
+ },
+
+ {
+ // NOTE: This is an exact copy of the above plugin but with `enforce: pre`
+ // when using Lightning CSS.
+
// Step 2 (full build): Generate CSS
name: '@tailwindcss/vite:generate:build',
apply: 'build',
enforce: 'pre',
async transform(src, id) {
+ if (!isUsingLightningCSS()) return
+
if (!isPotentialCssRootFile(id)) return
let root = roots.get(id)
@@ -231,6 +307,8 @@ export default function tailwindcss(): Plugin[] {
// We must run before `enforce: post` so the updated chunks are picked up
// by vite:css-post.
async renderStart() {
+ if (!isUsingLightningCSS()) return
+
for (let [id, root] of roots.entries()) {
let generated = await regenerateOptimizedCss(
root,