From 4a0701a1cd99799658441a354dac41d5d140ef04 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 31 Oct 2024 14:33:22 -0400
Subject: [PATCH 1/6] Add helper for writing binary files in integration tests
---
integrations/utils.ts | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/integrations/utils.ts b/integrations/utils.ts
index 70b97c111d06..cee50c6593df 100644
--- a/integrations/utils.ts
+++ b/integrations/utils.ts
@@ -29,7 +29,7 @@ interface ExecOptions {
interface TestConfig {
fs: {
- [filePath: string]: string
+ [filePath: string]: string | Uint8Array
}
}
interface TestContext {
@@ -279,8 +279,14 @@ export function test(
})
},
fs: {
- async write(filename: string, content: string): Promise {
+ async write(filename: string, content: string | Uint8Array): Promise {
let full = path.join(root, filename)
+ let dir = path.dirname(full)
+ await fs.mkdir(dir, { recursive: true })
+
+ if (typeof content !== 'string') {
+ return await fs.writeFile(full, content)
+ }
if (filename.endsWith('package.json')) {
content = await overwriteVersionsInPackageJson(content)
@@ -291,8 +297,6 @@ export function test(
content = content.replace(/\n/g, '\r\n')
}
- let dir = path.dirname(full)
- await fs.mkdir(dir, { recursive: true })
await fs.writeFile(full, content)
},
@@ -494,6 +498,12 @@ export let json = dedent
export let yaml = dedent
export let txt = dedent
+export function binary(str: string | TemplateStringsArray, ...values: unknown[]): Uint8Array {
+ let base64 = typeof str === 'string' ? str : String.raw(str, ...values)
+
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0))
+}
+
export function candidate(strings: TemplateStringsArray, ...values: any[]) {
let output: string[] = []
for (let i = 0; i < strings.length; i++) {
From 20685a3fd24ca80b0c0f427fb1db1fdde6286e6b Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 31 Oct 2024 15:30:17 -0400
Subject: [PATCH 2/6] Add failing test for Vite URL rewriting
---
integrations/vite/url-rewriting.test.ts | 68 +++++++++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 integrations/vite/url-rewriting.test.ts
diff --git a/integrations/vite/url-rewriting.test.ts b/integrations/vite/url-rewriting.test.ts
new file mode 100644
index 000000000000..36439551506f
--- /dev/null
+++ b/integrations/vite/url-rewriting.test.ts
@@ -0,0 +1,68 @@
+import { expect } from 'vitest'
+import { binary, css, html, json, test, ts } from '../utils'
+
+const SIMPLE_IMAGE = `iVBORw0KGgoAAAANSUhEUgAAADAAAAAlAQAAAAAsYlcCAAAACklEQVR4AWMYBQABAwABRUEDtQAAAABJRU5ErkJggg==`
+
+test(
+ 'can rewrite urls in production builds',
+ {
+ fs: {
+ 'package.json': json`
+ {
+ "type": "module",
+ "dependencies": {
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "vite": "^5.3.5"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import { defineConfig } from 'vite'
+ import tailwindcss from '@tailwindcss/vite'
+
+ export default defineConfig({
+ plugins: [tailwindcss()],
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+
+
+
+
+
+
+ `,
+ 'src/main.ts': ts``,
+ 'src/app.css': css`
+ @import './dir-1/bar.css';
+ @import './dir-1/dir-2/baz.css';
+ `,
+ 'src/dir-1/bar.css': css`
+ .bar {
+ background-image: url('../../resources/image.png');
+ }
+ `,
+ 'src/dir-1/dir-2/baz.css': css`
+ .baz {
+ background-image: url('../../../resources/image.png');
+ }
+ `,
+ 'resources/image.png': binary(SIMPLE_IMAGE),
+ },
+ },
+ async ({ fs, exec }) => {
+ await exec('pnpm vite build')
+
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
+
+ await fs.expectFileToContain(files[0][0], [SIMPLE_IMAGE])
+ },
+)
From 35eb86dbf0bf14d8e37f4dc8c618ae71868ea187 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 31 Oct 2024 15:37:39 -0400
Subject: [PATCH 3/6] =?UTF-8?q?Add=20test=20for=20`:deep(=E2=80=A6)`=20in?=
=?UTF-8?q?=20Vue=20scoped=20style=20blocks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
integrations/vite/vue.test.ts | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/integrations/vite/vue.test.ts b/integrations/vite/vue.test.ts
index 4f582a99fcdc..35ed5de77ba6 100644
--- a/integrations/vite/vue.test.ts
+++ b/integrations/vite/vue.test.ts
@@ -51,9 +51,15 @@ test(
@apply text-red-500;
}
-
+
- Hello Vue!
+ Hello Vue!
`,
},
@@ -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)'])
},
)
From 53049e989105389ad7b3878383a13e6543b34c2a Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 31 Oct 2024 15:39:25 -0400
Subject: [PATCH 4/6] Generate CSS after Vite handles imports during prod
builds
---
packages/@tailwindcss-vite/src/index.ts | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 57eaf42affb5..bcb829fbc559 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -209,7 +209,22 @@ export default function tailwindcss(): Plugin[] {
// Step 2 (full build): Generate CSS
name: '@tailwindcss/vite:generate:build',
apply: 'build',
- enforce: 'pre',
+
+ // 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 (!isPotentialCssRootFile(id)) return
From eb8ae5279b446c2a654a242ffde05317c4fc17c7 Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Thu, 31 Oct 2024 16:15:11 -0400
Subject: [PATCH 5/6] Disable fix when using Lightning CSS
---
packages/@tailwindcss-vite/src/index.ts | 63 +++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index bcb829fbc559..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
@@ -227,6 +231,7 @@ export default function tailwindcss(): Plugin[] {
// 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)
@@ -246,6 +251,64 @@ 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,
+ // 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)
+
+ // 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,
From 45da06c8f143e789156f662c1c4c74302a0d97da Mon Sep 17 00:00:00 2001
From: Jordan Pittman
Date: Fri, 1 Nov 2024 10:27:38 -0400
Subject: [PATCH 6/6] Add stub test for lightningcss
This fails so its marked as a `todo` test.
---
integrations/utils.ts | 5 +
integrations/vite/url-rewriting.test.ts | 132 +++++++++++++-----------
2 files changed, 75 insertions(+), 62 deletions(-)
diff --git a/integrations/utils.ts b/integrations/utils.ts
index cee50c6593df..c52271bdb83b 100644
--- a/integrations/utils.ts
+++ b/integrations/utils.ts
@@ -28,6 +28,7 @@ interface ExecOptions {
}
interface TestConfig {
+ todo?: boolean
fs: {
[filePath: string]: string | Uint8Array
}
@@ -74,6 +75,10 @@ export function test(
testCallback: TestCallback,
{ only = false, debug = false }: TestFlags = {},
) {
+ if (config.todo) {
+ return defaultTest.todo(name)
+ }
+
return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)(
name,
{ timeout: TEST_TIMEOUT, retry: process.env.CI ? 2 : 0 },
diff --git a/integrations/vite/url-rewriting.test.ts b/integrations/vite/url-rewriting.test.ts
index 36439551506f..77a131f80287 100644
--- a/integrations/vite/url-rewriting.test.ts
+++ b/integrations/vite/url-rewriting.test.ts
@@ -1,68 +1,76 @@
-import { expect } from 'vitest'
-import { binary, css, html, json, test, ts } from '../utils'
+import { describe, expect } from 'vitest'
+import { binary, css, html, test, ts, txt } from '../utils'
const SIMPLE_IMAGE = `iVBORw0KGgoAAAANSUhEUgAAADAAAAAlAQAAAAAsYlcCAAAACklEQVR4AWMYBQABAwABRUEDtQAAAABJRU5ErkJggg==`
-test(
- 'can rewrite urls in production builds',
- {
- fs: {
- 'package.json': json`
- {
- "type": "module",
- "dependencies": {
- "tailwindcss": "workspace:^"
- },
- "devDependencies": {
- "@tailwindcss/vite": "workspace:^",
- "vite": "^5.3.5"
- }
- }
- `,
- 'vite.config.ts': ts`
- import { defineConfig } from 'vite'
- import tailwindcss from '@tailwindcss/vite'
+for (let transformer of ['postcss', 'lightningcss']) {
+ describe(transformer, () => {
+ test(
+ 'can rewrite urls in production builds',
+ {
+ todo: transformer === 'lightningcss',
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''}
+ "@tailwindcss/vite": "workspace:^",
+ "vite": "^5.3.5"
+ }
+ }
+ `,
+ 'vite.config.ts': ts`
+ import tailwindcss from '@tailwindcss/vite'
+ import { defineConfig } from 'vite'
- export default defineConfig({
- plugins: [tailwindcss()],
- })
- `,
- 'index.html': html`
-
-
-
-
-
-
-
-
-
-
- `,
- 'src/main.ts': ts``,
- 'src/app.css': css`
- @import './dir-1/bar.css';
- @import './dir-1/dir-2/baz.css';
- `,
- 'src/dir-1/bar.css': css`
- .bar {
- background-image: url('../../resources/image.png');
- }
- `,
- 'src/dir-1/dir-2/baz.css': css`
- .baz {
- background-image: url('../../../resources/image.png');
- }
- `,
- 'resources/image.png': binary(SIMPLE_IMAGE),
- },
- },
- async ({ fs, exec }) => {
- await exec('pnpm vite build')
+ export default defineConfig({
+ plugins: [tailwindcss()],
+ build: { cssMinify: false },
+ css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"},
+ })
+ `,
+ 'index.html': html`
+
+
+
+
+
+
+
+
+
+
+ `,
+ 'src/main.ts': ts``,
+ 'src/app.css': css`
+ @import './dir-1/bar.css';
+ @import './dir-1/dir-2/baz.css';
+ `,
+ 'src/dir-1/bar.css': css`
+ .bar {
+ background-image: url('../../resources/image.png');
+ }
+ `,
+ 'src/dir-1/dir-2/baz.css': css`
+ .baz {
+ background-image: url('../../../resources/image.png');
+ }
+ `,
+ 'resources/image.png': binary(SIMPLE_IMAGE),
+ },
+ },
+ async ({ fs, exec }) => {
+ await exec('pnpm vite build')
- let files = await fs.glob('dist/**/*.css')
- expect(files).toHaveLength(1)
+ let files = await fs.glob('dist/**/*.css')
+ expect(files).toHaveLength(1)
- await fs.expectFileToContain(files[0][0], [SIMPLE_IMAGE])
- },
-)
+ await fs.expectFileToContain(files[0][0], [SIMPLE_IMAGE])
+ },
+ )
+ })
+}