From e9426d01a64c2cbf9c8f5a0d46893ef1e7ffedb1 Mon Sep 17 00:00:00 2001 From: Adam Wathan Date: Mon, 2 Dec 2024 09:34:13 -0500 Subject: [PATCH 01/35] Use correct values for `float-start/end` and `clear-start/end` (#15261) This PR fixes the `float-start/end` and `clear-start/end` utilities to use `inline-start` and `inline-end` instead of `start` and `end`, which aren't valid values. Fixes #15255. Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> --- packages/tailwindcss/src/utilities.test.ts | 36 +++++++++++----------- packages/tailwindcss/src/utilities.ts | 8 ++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 3f1b25333112..d8cd24c026f5 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -1360,26 +1360,26 @@ test('row-end', async () => { test('float', async () => { expect(await run(['float-start', 'float-end', 'float-right', 'float-left', 'float-none'])) .toMatchInlineSnapshot(` - ".float-end { - float: end; - } + ".float-end { + float: inline-end; + } - .float-left { - float: left; - } + .float-left { + float: left; + } - .float-none { - float: none; - } + .float-none { + float: none; + } - .float-right { - float: right; - } + .float-right { + float: right; + } - .float-start { - float: start; - }" - `) + .float-start { + float: inline-start; + }" + `) expect( await run([ 'float', @@ -1413,7 +1413,7 @@ test('clear', async () => { } .clear-end { - clear: end; + clear: inline-end; } .clear-left { @@ -1429,7 +1429,7 @@ test('clear', async () => { } .clear-start { - clear: start; + clear: inline-start; }" `) expect( diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index a544403025d3..287604f146a9 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -719,8 +719,8 @@ export function createUtilities(theme: Theme) { /** * @css `float` */ - staticUtility('float-start', [['float', 'start']]) - staticUtility('float-end', [['float', 'end']]) + staticUtility('float-start', [['float', 'inline-start']]) + staticUtility('float-end', [['float', 'inline-end']]) staticUtility('float-right', [['float', 'right']]) staticUtility('float-left', [['float', 'left']]) staticUtility('float-none', [['float', 'none']]) @@ -728,8 +728,8 @@ export function createUtilities(theme: Theme) { /** * @css `clear` */ - staticUtility('clear-start', [['clear', 'start']]) - staticUtility('clear-end', [['clear', 'end']]) + staticUtility('clear-start', [['clear', 'inline-start']]) + staticUtility('clear-end', [['clear', 'inline-end']]) staticUtility('clear-right', [['clear', 'right']]) staticUtility('clear-left', [['clear', 'left']]) staticUtility('clear-both', [['clear', 'both']]) From 6af483547e27b0df4df3271c4ecdefa62e875d8f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 2 Dec 2024 20:03:10 +0100 Subject: [PATCH 02/35] Improve performance of scanning source files (#15270) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR improves scanning files by scanning chunks of the files in parallel. Each chunk is separated by new lines since we can't use whitespace in classes anyway. This also means that we can use the power of your CPU to scan files faster. The extractor itself also has less state to worry about on these smaller chunks. On a dedicated benchmark machine: Mac Mini, M1, 16 GB RAM ```shellsession ❯ hyperfine --warmup 15 --runs 50 \ -n NEW 'bun --bun /Users/ben/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' \ -n CURRENT 'bun --bun /Users/ben/github.com/tailwindlabs/tailwindcss--next/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' Benchmark 1: NEW Time (mean ± σ): 337.2 ms ± 2.9 ms [User: 1376.6 ms, System: 80.9 ms] Range (min … max): 331.0 ms … 345.3 ms 50 runs Benchmark 2: CURRENT Time (mean ± σ): 730.3 ms ± 3.8 ms [User: 978.9 ms, System: 78.7 ms] Range (min … max): 722.0 ms … 741.8 ms 50 runs Summary NEW ran 2.17 ± 0.02 times faster than CURRENT ``` On a more powerful machine, MacBook Pro M1 Max, 64 GB RAM, the results look even more promising: ```shellsession ❯ hyperfine --warmup 15 --runs 50 \ -n NEW 'bun --bun /Users/robin/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' \ -n CURRENT 'bun --bun /Users/robin/github.com/tailwindlabs/tailwindcss--next/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' Benchmark 1: NEW Time (mean ± σ): 307.8 ms ± 24.5 ms [User: 1124.8 ms, System: 187.9 ms] Range (min … max): 291.7 ms … 397.9 ms 50 runs Benchmark 2: CURRENT Time (mean ± σ): 754.7 ms ± 27.2 ms [User: 934.9 ms, System: 217.6 ms] Range (min … max): 735.5 ms … 845.6 ms 50 runs Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options. Summary NEW ran 2.45 ± 0.21 times faster than CURRENT ``` > Note: This last benchmark is running on my main machine which is more "busy" compared to my benchmark machine. Because of this I had to increase the `--runs` to get statistically better results. There is still a warning present, but the overall numbers are still very promising. --- These benchmarks are running on our Tailwind UI project where we have >1000 files, and >750 000 lines of code in those files. | Before | After | | --- | --- | | image | image | --- I am sure there is more we can do here, because reading all of these 1000 files only takes ~10ms, whereas parsing all these files takes ~180ms. But I'm still happy with these results as an incremental improvement. For good measure, I also wanted to make sure that we didn't regress on smaller projects. Running this on Catalyst, we only have to deal with ~100 files and ~18 000 lines of code. In this case reading all the files takes ~890µs and parsing takes about ~4ms. | Before | After | | --- | --- | | image | image | Not a huge difference, still better and definitely no regressions which sounds like a win to me. --- **Edit:** after talking to @thecrypticace, instead of splitting on any whitespace we just split on newlines. This makes the chunks a bit larger, but it reduces the overhead of the extractor itself. This now results in a 2.45x speedup in Tailwind UI compared to 1.94x speedup. --------- Co-authored-by: Jordan Pittman Co-authored-by: Adam Wathan --- CHANGELOG.md | 4 +++- crates/oxide/src/lib.rs | 24 +++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14cc77dbfb59..7be15ceb683d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Added + +- Parallelize parsing of individual source files ([#15270](https://github.com/tailwindlabs/tailwindcss/pull/15270)) ## [4.0.0-beta.4] - 2024-11-29 diff --git a/crates/oxide/src/lib.rs b/crates/oxide/src/lib.rs index a9567c5c4019..4c7621e7a1c6 100644 --- a/crates/oxide/src/lib.rs +++ b/crates/oxide/src/lib.rs @@ -102,13 +102,11 @@ impl Scanner { pub fn scan(&mut self) -> Vec { init_tracing(); self.prepare(); - self.check_for_new_files(); self.compute_candidates(); - let mut candidates: Vec = self.candidates.clone().into_iter().collect(); - - candidates.sort(); + let mut candidates: Vec = self.candidates.clone().into_par_iter().collect(); + candidates.par_sort(); candidates } @@ -140,7 +138,7 @@ impl Scanner { let extractor = Extractor::with_positions(&content[..], Default::default()); let candidates: Vec<(String, usize)> = extractor - .into_iter() + .into_par_iter() .map(|(s, i)| { // SAFETY: When we parsed the candidates, we already guaranteed that the byte slices // are valid, therefore we don't have to re-check here when we want to convert it back @@ -156,7 +154,7 @@ impl Scanner { self.prepare(); self.files - .iter() + .par_iter() .filter_map(|x| Path::from(x.clone()).canonicalize().ok()) .map(|x| x.to_string()) .collect() @@ -201,7 +199,7 @@ impl Scanner { if !changed_content.is_empty() { let candidates = parse_all_blobs(read_all_files(changed_content)); - self.candidates.extend(candidates); + self.candidates.par_extend(candidates); } } @@ -209,6 +207,7 @@ impl Scanner { // content for candidates. fn prepare(&mut self) { if self.ready { + self.check_for_new_files(); return; } @@ -455,12 +454,10 @@ fn read_all_files(changed_content: Vec) -> Vec> { #[tracing::instrument(skip_all)] fn parse_all_blobs(blobs: Vec>) -> Vec { - let input: Vec<_> = blobs.iter().map(|blob| &blob[..]).collect(); - let input = &input[..]; - - let mut result: Vec = input + let mut result: Vec<_> = blobs .par_iter() - .map(|input| Extractor::unique(input, Default::default())) + .flat_map(|blob| blob.par_split(|x| matches!(x, b'\n'))) + .map(|blob| Extractor::unique(blob, Default::default())) .reduce(Default::default, |mut a, b| { a.extend(b); a @@ -473,6 +470,7 @@ fn parse_all_blobs(blobs: Vec>) -> Vec { unsafe { String::from_utf8_unchecked(s.to_vec()) } }) .collect(); - result.sort(); + + result.par_sort(); result } From 667af255b3b48d74f6d782e8256d62c2d05f60e4 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 2 Dec 2024 20:23:38 +0100 Subject: [PATCH 03/35] Vite: Don't rebase absolute `url()`s (#15275) Closes #15269 This PR fixes an issue where our Vite extension was rebasing absolute urls inside `@import`-ed files. We forgot to cover this when we implemented the URL rebasing. ## Test Plan We validated that this fixes the repro in #15269: Screenshot 2024-12-02 at 18 07 35 Also added a unit test for this. Co-authored-by: Jordan Pittman --- CHANGELOG.md | 4 ++++ packages/@tailwindcss-node/src/urls.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be15ceb683d..6755c89c5688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Ensure absolute `url()`s inside imported CSS files are not rebased when using `@tailwindcss/vite` + ### Added - Parallelize parsing of individual source files ([#15270](https://github.com/tailwindlabs/tailwindcss/pull/15270)) diff --git a/packages/@tailwindcss-node/src/urls.ts b/packages/@tailwindcss-node/src/urls.ts index c6009a927105..c4d56deb7e60 100644 --- a/packages/@tailwindcss-node/src/urls.ts +++ b/packages/@tailwindcss-node/src/urls.ts @@ -51,6 +51,8 @@ export async function rewriteUrls({ let promises: Promise[] = [] function replacerForDeclaration(url: string) { + if (url[0] === '/') return url + let absoluteUrl = path.posix.join(normalizePath(base), url) let relativeUrl = path.posix.relative(normalizePath(root), absoluteUrl) From 89f291c0074331f8e6c5d177170b19b409d6ce18 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 2 Dec 2024 20:48:00 +0100 Subject: [PATCH 04/35] Vite: Simplify preprocessor to make it work with Svelte 5 and Vite 6 (#15274) Closes #15250 This PR simplifies our Vite integration even more. It turns out that in some projects (see #15250 for the exact repro), the way we invoke `svelte-preprocess` was actually causing issues in Vite since with Vite, it's expected to use the `sveltePreprocess` version exported by `sveltejs/vite-plugin-svelte`. While trying to change this we noticed that there are different versions of `sveltejs/vite-plugin-svelte` for Vite 5 and Vite 6 which caused us to investigate even more and we noticed that we do not even need to recursively call into the `sveltePreprocess()` as every plugin is run after each other anyways. This allows us to drop the dependency on `svelte-preprocess` and simplify the code a bit more, registering only a `(string) => string` style transformer. ## Test Plan This was tsted on the repro repo from #15250 as well as the SvelteKit setup from [my playgrounds](https://github.com/philipp-spiess/tailwindcss-playgrounds). Furthermore we tested various combinations of `svelte`, `@sveltejs/vite-plugin-svelte` and `vite` in our integration test to ensure everything works as expected. --------- Co-authored-by: Jordan Pittman --- CHANGELOG.md | 2 + integrations/vite/svelte.test.ts | 8 +- packages/@tailwindcss-vite/package.json | 3 +- packages/@tailwindcss-vite/src/index.ts | 21 ++-- pnpm-lock.yaml | 150 +----------------------- 5 files changed, 20 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6755c89c5688..8b1c70ddf920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Ensure absolute `url()`s inside imported CSS files are not rebased when using `@tailwindcss/vite` +- Fix issues with dev servers using Svelte 5 with the Vite plugin ([#15274](https://github.com/tailwindlabs/tailwindcss/issues/15274)) ### Added - Parallelize parsing of individual source files ([#15270](https://github.com/tailwindlabs/tailwindcss/pull/15270)) +- Support Vite 6 in the Vite plugin ([#15274](https://github.com/tailwindlabs/tailwindcss/issues/15274)) ## [4.0.0-beta.4] - 2024-11-29 diff --git a/integrations/vite/svelte.test.ts b/integrations/vite/svelte.test.ts index 12acd63a579d..b0950f9f9cd2 100644 --- a/integrations/vite/svelte.test.ts +++ b/integrations/vite/svelte.test.ts @@ -9,11 +9,11 @@ test( { "type": "module", "dependencies": { - "svelte": "^4.2.18", + "svelte": "^5", "tailwindcss": "workspace:^" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.1", + "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", "vite": "^6" } @@ -120,11 +120,11 @@ test( { "type": "module", "dependencies": { - "svelte": "^4.2.18", + "svelte": "^5", "tailwindcss": "workspace:^" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.1.1", + "@sveltejs/vite-plugin-svelte": "^5", "@tailwindcss/vite": "workspace:^", "vite": "^6" } diff --git a/packages/@tailwindcss-vite/package.json b/packages/@tailwindcss-vite/package.json index 546f047bc13c..00a020e9336f 100644 --- a/packages/@tailwindcss-vite/package.json +++ b/packages/@tailwindcss-vite/package.json @@ -31,7 +31,6 @@ "@tailwindcss/node": "workspace:^", "@tailwindcss/oxide": "workspace:^", "lightningcss": "catalog:", - "svelte-preprocess": "^6.0.2", "tailwindcss": "workspace:*" }, "devDependencies": { @@ -39,6 +38,6 @@ "vite": "catalog:" }, "peerDependencies": { - "vite": "^5.2.0" + "vite": "^5.2.0 || ^6" } } diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index cc89775eae32..9c92699faf89 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -4,7 +4,6 @@ import { Scanner } from '@tailwindcss/oxide' import { Features as LightningCssFeatures, transform } from 'lightningcss' import fs from 'node:fs/promises' import path from 'node:path' -import { sveltePreprocess } from 'svelte-preprocess' import type { Plugin, ResolvedConfig, Rollup, Update, ViteDevServer } from 'vite' const SPECIAL_QUERY_RE = /[?&](raw|url)\b/ @@ -609,8 +608,8 @@ class Root { } } -// Register a plugin that can hook into the Svelte preprocessor if svelte is -// enabled. This allows us to transform CSS in ` `, @@ -165,14 +165,11 @@ test(

Hello {name}!

`, - 'src/index.css': css` - @import 'tailwindcss/theme' theme(reference); - @import 'tailwindcss/utilities'; - `, + 'src/index.css': css` @import 'tailwindcss'; `, 'src/other.css': css` .local { @apply text-red-500; diff --git a/integrations/vite/vue.test.ts b/integrations/vite/vue.test.ts index dec61d808350..626733705be8 100644 --- a/integrations/vite/vue.test.ts +++ b/integrations/vite/vue.test.ts @@ -45,7 +45,7 @@ test( `, 'src/App.vue': html`