mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-02-01 17:26:34 +00:00
76 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ed3cecd39d
|
Support Leptos class: attributes (#18093)
<!-- 👋 Hey, thanks for your interest in contributing to Tailwind! **Please ask first before starting work on any significant new features.** It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create a discussion to first discuss any significant new features. For more info, check out the contributing guide: https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md --> Fixes #18092 ## Summary Using the Svelte preprocessor for Rust files we can support Leptos `class:` attributes syntax. ## Test plan ``` pnpm t ``` --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com> |
||
|
|
bf591febf3
|
Fix missing extracted classes containing . in Clojure (#18038)
This PR fixes an issue in the Clojure pre-processor where candidates including `.` characters were not extracted correctly. The solution here is to only replace the `.` with a ` ` when the `.` is not surrounded by numbers. This means that: ``` :.foo.bar ``` Becomes ``` : foo bar ``` But ``` :.gap-1.5.flex ``` Becomes ``` : gap-1.5 flex ``` This way the `gap-1.5` is correctly extracted. ## Test plan 1. Added a test for this case 2. Tested this in the extractor tool as well. Notice how the `gap-1.5` is correctly extracted here. <img width="1247" alt="image" src="https://github.com/user-attachments/assets/f5dd2600-5c5e-4ad8-88af-4e5be44340f5" /> Fixes: 17760 |
||
|
|
737994b7aa
|
Allow _ before numbers during candidate extraction (#17961)
This PR fixes a bug where a class like `header_1` wasn't properly extracted because we didn't allow an `_` before a number. This PR fixes that by allowing an `_` before a number. Fixes: #17960 ## Test plan 1. Added a test to verify this works 2. Existing tests work Used the visualizer tool for this to verify that the `header_1` class is being extracted: <img width="1816" alt="image" src="https://github.com/user-attachments/assets/fdc21602-0e2b-4e4e-92a1-19c4f4f5393f" /> |
||
|
|
2f6679abfe
|
Print sources when DEBUG=* is set (#17952)
This PR improves the debug logging by also adding the provided `@source`
to the log file. It will also print the optimized sources (the ones we
expand and mark as `Auto` or `Pattern`).
In the logs, this will look like this:
```
2025-05-09T11:03:32.906478Z INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:03:32.906544Z INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:03:32.906589Z INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:03:32.906595Z INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
```
Or if you have more sources:
```
2025-05-09T11:06:54.767546Z INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:06:54.767660Z INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:06:54.767987Z INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./routes/*.{jsx,tsx}", negated: false }
2025-05-09T11:06:54.767992Z INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./utils/*.ts", negated: false }
2025-05-09T11:06:54.768450Z INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:06:54.768455Z INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
2025-05-09T11:06:54.768459Z INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.jsx" }
2025-05-09T11:06:54.768462Z INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.tsx" }
2025-05-09T11:06:54.768466Z INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils", pattern: "*.ts" }
```
|
||
|
|
179e5ddd7c
|
Add more folders to the list of ignored content dirs (#17892)
Closes #15452 This PR adds more directories to the list of ignored content dirs. These are now handled the same as `node_modules`: - Contents of this directory are ignored by default to avoid scanning dependency trees - Explicit `@import`s inside these folders are now treated as _external_, bypassing any `.gitignore` files. The new extensions are: - Version control systems: `.hg`, `.svn` - Bundler caches: `.venv`, `venv`, `.yarn` - Framework caches: `.next`, `.turbo`, `.parcel-cache`, `__pycache__`, `.svelte-kit` ## Test plan Verified with the repro of #15452 by renaming the ignored directory to `.venv` and installing a local tarball: <img width="1283" alt="Screenshot 2025-05-06 at 13 14 55" src="https://github.com/user-attachments/assets/3265acbb-e121-47b3-ac6c-e174770f8234" /> |
||
|
|
d8c4df8001
|
Write to log file when using DEBUG=* (#17906)
This PR improves the debugability of the scanner when using `DEBUG=*` by
writing to a `tailwindcss-{pid}.log` file in the current working
directory.
It will include all the tracing information from the scanner. This PR
also introduces `Discovering {path}` and `Reading {path}` logs.
- `Discovering {path}` — this is logged when we are traversing the file
system looking for files. We use the `ignore` crate, and log this
information in the `filter_entry` callback. If a file was already
ignored by `.gitignore` files, this won't show up, but it also means
that we will not read it.
- `Reading {path}` — this is when we are actually reading the file so we
can start extracting potential Tailwind CSS classes.
These will give you some insights in what paths are being scanned, and
if we get stuck, where we get stuck.
Also, we are appending to the file. In the log below, you can already
see that a `tailwindcss-<number>.log` file exists already even though it
didn't exist before running the command. This should make it easier to
debug if we get stuck on a specific file/folder because the file will be
populated with information.
There are a few reasons for appending to a file:
1. There is a lot of output, so spamming the stdout/stderr is not ideal
2. If you run the same command again, after changing your `@source`
directives, you could diff the outputs. (although, the timestamps will
be different)
3. When using `DEBUG=*`, a lot of other tools also output debug
information, so writing to a file should make this better.
<details>
<summary>Example log</summary>
```log
2025-05-06T23:13:45.912292Z INFO scan_sources: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.912697Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.912716Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.912748Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app"
2025-05-06T23:13:45.912786Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test"
2025-05-06T23:13:45.912814Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.912851Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils"
2025-05-06T23:13:45.912873Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.912884Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.912893Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.912904Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.912914Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.912936Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain"
2025-05-06T23:13:45.912962Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.912972Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.912995Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature"
2025-05-06T23:13:45.913019Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.913029Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.913039Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.913048Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.913058Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.913067Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.913077Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.913086Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.913095Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.913105Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.913121Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.913505Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.913514Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.913523Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.913531Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.913554Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions"
2025-05-06T23:13:45.913583Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.913592Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.913601Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.913613Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.913622Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.913631Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.913640Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.913648Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.913656Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.913665Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.913673Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.913681Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.913689Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.913697Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.913705Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.913713Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.913720Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.913728Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.913741Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.913749Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.913757Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.913783Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/__snapshots__"
2025-05-06T23:13:45.913817Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.913826Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.913833Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.913841Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.913849Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.913857Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.913879Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes"
2025-05-06T23:13:45.913896Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.914172Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.914197Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public"
2025-05-06T23:13:45.914228Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public/fonts"
2025-05-06T23:13:45.914268Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.914289Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts"
2025-05-06T23:13:45.914310Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.914332Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github"
2025-05-06T23:13:45.914383Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows"
2025-05-06T23:13:45.914410Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.914420Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.914455Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.914486Z INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.914512Z INFO scan_sources: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.914515Z INFO extract_candidates: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914518Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914524Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 58 file(s)
2025-05-06T23:13:45.914808Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.914990Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.915138Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.915140Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.915145Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.915163Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.915153Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.915226Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.915229Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.915372Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.915578Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.915599Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.915637Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.915647Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.915657Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.915680Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.915681Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.915701Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.915706Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.915691Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.915701Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.915730Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.915753Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.915731Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.915787Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.915822Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.915826Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.915885Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.915885Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.915886Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.915990Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.915995Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.915998Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.916000Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.916024Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.916056Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.916058Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.916070Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.916075Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.916063Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.916119Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.916120Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.916148Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.916152Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.916193Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.916219Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.916228Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.916245Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.916256Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.916253Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.916267Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.916287Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.916286Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.916317Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.916323Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.916354Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.916562Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.916609Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.916676Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.916682Z INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.916688Z INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918271Z INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918282Z INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918286Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918288Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 2 file(s)
2025-05-06T23:13:45.918315Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.918504Z INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.918512Z INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918519Z INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918522Z INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918635Z INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918640Z INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.919059Z INFO extract_candidates: tailwindcss_oxide::scanner: exit
```
</details>
We also output where we are writing the file to. This looks like this
when using the CLI:
<img width="1462" alt="image"
src="https://github.com/user-attachments/assets/79c2cc95-adea-4bbd-a4f1-101de45726f5"
/>
Last but not least, this also ignores `.log` files by default
## Test plan
Ran the CLI (but you can use any tool real, since this is implemented in
Oxide) with the `DEBUG=*` flag.
The file generated, looks like the example I shared above.
|
||
|
|
5c5ae04db6
|
Fix Windows CI build (#17878)
This PR fixes a Windows CI issue due to the recently merged #17846 There might be better ways to solve this, but essentially we want to make sure we are always dealing with `\n` and not `\r\n`. This PR fixes that by replacing all `\r\n` with `\n` in the tests. |
||
|
|
d38554d110
|
Fix HAML extraction with embedded Ruby (#17846)
This PR fixes and improves the HAML extractor by ensuring that whenever we detect lines or blocks of Ruby code (`-`, `=` and `~`) characters at the beginning of the line, we treat them as Ruby code. Fixes: #17813 ## Test Plan 1. Existing tests pass 2. Changed 1 existing test which embedded Ruby syntax 3. Added a dedicated test to ensure the HAML file in the linked issue is parsed correctly Running this in the internal extractor tool you can see that the `w-[12px]`, `w-[16px]`, `h-[12px]`, and `h-[16px]` are properly extracted. Note: the `mr-12px` is also extracted, but not highlighted because this is not a valid Tailwind CSS class. <img width="1816" alt="image" src="https://github.com/user-attachments/assets/fc5929ca-bc71-47d2-b21b-7abeec86f54d" /> |
||
|
|
25ec6a3b7d
|
Ignore .db files by default (#17711)
This PR ignores `.db` files by default. We already ignored `.sqlite` and `.sqlite3` files but we didn't ignore `.db` files which is a common extension for database files as well. Due to the binary nature of `.db` files, scanning these could result in hard to debug errors such as:  |
||
|
|
8feb6a758a
|
Ignore .geojson files by default (#17700)
Resolves https://github.com/tailwindlabs/tailwindcss/issues/17699 GeoJSON files are giant JSON files for geographic data structures and will never contain Tailwind classes, but because they are often huge they will slow down the build a lot if scanned. --------- Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com> |
||
|
|
bbd916aaa0
|
Ignore binary extensions, except in folder names (#17595)
We generate a glob to ignore binary extensions that looks something like
this:
```
*.{mp4,pages,exe,…}
```
However, if you have a folder that happens to end in `.pages` for
example, then this folder will be ignored in its entirety.
To solve this, we added a new flag to the `Gitignore` struct so we can
register a bunch of ignore rules that _only_ apply to paths and not
folders.
Fixes: #17569
## Test plan
- Added a unit test
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
|
||
|
|
7d317251f2
|
Handle Ruby %w syntax in Slim pre processing (#17557)
This PR fixes an issue where the Ruby `%w[…]` syntax causes utilities to not be properly extracted. This PR will now ensure that it does get extracted correctly. Given this input: ```slim div[ class=%w[bg-blue-500 w-10 h-10] ] div[ class=%w[w-10 bg-green-500 h-10] ] ``` Before this PR, we extracted everything but the `bg-blue-500`. The `w-10` was extracted but not because of the second div, but because of the first one. Fixes: #17542 ## Test plan 1. Added a test to ensure it's working correctly. Looking at the extractor tool, you can see that it now gets extracted correctly. Top is before, bottom is with this change. <img width="1199" alt="image" src="https://github.com/user-attachments/assets/028d9abd-8917-438c-a423-88ba887b7f97" /> |
||
|
|
4200a1ecc4
|
Fix slow incremental builds (especially on Windows) (#17511)
This PR fixes slow rebuilds on Windows where rebuilds go from ~2s to ~20ms. Fixes: #16911 Fixes: #17522 ## Test plan 1. Tested it on a reproduction with the following results: Before: https://github.com/user-attachments/assets/10c5e9e0-3c41-4e1d-95f6-ee8d856577ef After: https://github.com/user-attachments/assets/2c7597e9-3fff-4922-a2da-a8d06eab9047 Zooming in on the times, it looks like this: <img width="674" alt="image" src="https://github.com/user-attachments/assets/85eee69c-bbf6-4c28-8ce3-6dcdad74be9c" /> But with these changes: <img width="719" alt="image" src="https://github.com/user-attachments/assets/d89cefda-0711-4f84-bfaf-2bea11977bf7" /> We also tested this on Windows with the following results: Before: <img width="961" alt="image" src="https://github.com/user-attachments/assets/3a42f822-f103-4598-9a91-e659ae09800c" /> After: <img width="956" alt="image" src="https://github.com/user-attachments/assets/05b6b6bc-d107-40d1-a207-3638aba3fc3a" /> [ci-all] --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com> |
||
|
|
53801091a0
|
Watch CSS module files for changes (#17467)
This PR is a follow-up PR for: https://github.com/tailwindlabs/tailwindcss/pull/17433 In the other PR we allow scanning CSS files for extracting usages of CSS variables. This is important for `.module.css` files that reference these variables but aren't in the same big AST of the main CSS file. This PR also makes sure to watch for changes in those registered CSS files and re-extract the variables when they change. This PR took a bit longer than expected because I was trying to make sure that writing to `./dist/out.css` works without infinite-looping (e.g.: we had issues with this in Tailwind CSS v3 with webpack). But I couldn't reproduce the issue at all. I did had some code that tried to detect if the CSS file contained license headers and skip in (because then it's very likely an output CSS file) but even without it the tests were fine. I setup integration tests with `@tailwindcss/cli` itself, and with tools that use webpack. Added a test for Next.js, and a dedicated webpack test as well. Even without tests, locally, I couldn't reproduce an infinite loop due to changes in an output CSS file... Eventually dropped the code that tries to detect output CSS files. One thing to keep in mind is that if you change any of your "main" CSS files, then we will trigger a full rebuild anyway, so this change is only required for unrelated CSS files (like CSS module files) that use CSS variables. ## Test plan 1. Added integration tests for the CLI and Next.js 2. Added new dedicated test for webpack |
||
|
|
2af7c57983
|
Extract used CSS variables from .css files (#17433)
This PR fixes an issue where CSS variables could be used in CSS modules, but where never emitted in your final CSS. Some backstory, when Tailwind CSS v4 came out, we _always_ emitted all CSS variables whether they were used or not. Later, we added an optimization where we only emit the CSS variables that were actually used. The definition of "used" in this case is: 1. Used in your CSS file(s) — (we check the final CSS AST for this) 2. Used _somewhere_ in any of your source files (e.g.: a JavaScript file accessing a variable) The issue this PR tries to solve is with the very first point. If you are using CSS modules, then every CSS file is processed separately. This is not a choice Tailwind CSS made, but how other build tooling works (like Vite for example). To prevent emitting all of Tailwind's Preflight reset and all utilities per CSS file, you can use the `@reference` directive instead of repeating `@import "tailwindcss";`. This is explained here: https://tailwindcss.com/docs/compatibility#explicit-context-sharing But now we are just _referencing_ them, not emitting them. And since the CSS module is not connected in any way to the main `index.css` file that contains the `@import "tailwindcss";` directive, we don't even see the CSS variables while processing the `index.css` file. (or wherever your main CSS file is) This is where point 2 from above comes in. This is a situation where we rely on the extractor to find the used CSS variables so we can internally mark them as used. To finally get to the point of this PR, the extractor only scans `.html`, `.js`, ... files but not `.css` files. So all the CSS variables used inside of CSS modules will not be generated. This PR changes that behavior to also scan `.css` files. But _only_ for CSS variables (not any other type of class candidate). This is important, otherwise all your custom `@utility foo {}` definitions would always mark `foo` as a used class and include it in the CSS which is not always the case. On top extracting CSS variables, we will also make sure that the CSS variables we find are in usage positions (e.g.: `var(--color-red-500)`) and not in definition positions (e.g.: `--color-red-500: #ff0000;`). This is important because we only want to emit the variables that are actually used in the final CSS output. One future improvement not implemented here, is that technically we will also extract CSS variables that might not be used if defined in a `@utility`. ```css @utility never-used { color: var(--color-red-500); /* --color-red-500 will be emitted, even if it might not be used */ } ``` Fixes: #16904 Fixes: #17429 # Test plan 1. Added a test where CSS variables are defined in `.css` files (and ignored) 2. Added a test where CSS variables are used in `.css` files (and included) Testing on the reproduction defined in #16904, the `.module.css` file contains a reference to `var(--color-hot-pink)`, but generating a build shows that the variable definition is not available: <img width="1630" alt="image" src="https://github.com/user-attachments/assets/a0d5c37e-6813-4cd5-a677-6c356b5a73d4" /> When you run the build again with the changes from this PR, then we _do_ see the definition of the `--color-hot-pink` in the root CSS file: <img width="2876" alt="image" src="https://github.com/user-attachments/assets/beab7c11-a31b-4ea4-8235-4849a8e92859" /> |
||
|
|
601f369374
|
Extract special @("@")md:… syntax in Razor files (#17427)
This PR fixes an extraction issue in Razor files where `@@md:bg-red-500`
can't always be extracted properly. We already convert `@@md:bg-red-500`
to ` @md:bg-red-500` but in certain situations Razor will emit the
double `@@` to the DOM.
A workaround in Razor land would be to write `@("@")md:bg-red-500`
instead. See: https://github.com/dotnet/aspnetcore/issues/38595 But then
we don't extract the `@md:bg-red-500` properly anymore.
This is where this PR comes in, essentially we will pre process the
Razor contents and apply the following replacement internally:
```diff
- @("@")md:bg-red-500
+ @md:bg-red-500
```
Notice that the `)` looks like it's replaced with `@`. This will have a
small side effect later when we get to the testing part.
But this way we properly see the `@md:bg-red-500` class during class
extraction.
> [!WARNING]
> There is technically a bug here because of the replacement with `@`,
because if you now run the `npx @tailwindcss/upgrade@latest` tool, then
we would replace `)md:bg-red-500` if changes are required, not the
`@("@")md:bg-red-500` part. We can try to fix that in this PR but it
seems unlikely that we will actually run into this issue realistically
speaking. I think fixing the actual extraction here is much more
important than the upgrade tooling that could fail _if_ we ever have to
migrate `@md:…` to something else.
Fixes: #17424
## Test plan
1. Added a test to verify the fix
2. Existing tests pass
3. Verified this in the extractor tool, but it looks a tiny bit funky.
Typically we remove characters by replacing it with a space ` `. But
this time, we replace it with some spaces _and_ an `@` character that
didn't exist at that specific position. If you look at the diff above,
you will notice that `)` was replaced with `@`.
That's why in the extractor tool it is highlighted that it could extract
it, but it's just funny looking because it highlights `)md:bg-red-500`
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/57e6a3ac-bfd5-4cad-a1ce-0039b4d7d9b5"
/>
|
||
|
|
eecb6f7476
|
A bit of cleanup (#17394)
Was working on another issue and noticed that I wanted these but they aren't related to any of the issues. So opening a separate PR to do some internal cleanup. |
||
|
|
b0c48c3de9
|
Fix symlink issues when resolving @source directives (#17391)
This PR fixes some issues related to symlinks when using them in the `@source` directive. Fixes: #16765 Fixes: #16038 ## Test plan 1. Added tests to prove this works - Added a recursive symlink test as well to make sure we don't hang 2. Existing tests still pass [ci-all] |
||
|
|
3dcd615b02
|
Fix Ruby files causing the CLI to hang (#17383)
Fixes #17379 The preprocessor we added to detect embedded languages uses a back reference and given a long enough file with certain byte / character patterns it'll cause what appears to be an indefinite hang (might just be catastrophically exponential backtracking but not sure) This replaces the one regex w/ back references with two, anchored, multi-line regexes Now we search for all the starting & ending delimiters in the file. We then loop over all the starting delimiters, find the paired ending one, and preprocess the content inside --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com> |
||
|
|
1ef97759e3
|
Add @source not support (#17255)
This PR adds a new source detection feature: `@source not "…"`. It can
be used to exclude files specifically from your source configuration
without having to think about creating a rule that matches all but the
requested file:
```css
@import "tailwindcss";
@source not "../src/my-tailwind-js-plugin.js";
```
While working on this feature, we noticed that there are multiple places
with different heuristics we used to scan the file system. These are:
- Auto source detection (so the default configuration or an `@source
"./my-dir"`)
- Custom sources ( e.g. `@source "./**/*.bin"` — these contain file
extensions)
- The code to detect updates on the file system
Because of the different heuristics, we were able to construct failing
cases (e.g. when you create a new file into `my-dir` that would be
thrown out by auto-source detection, it'd would actually be scanned). We
were also leaving a lot of performance on the table as the file system
is traversed multiple times for certain problems.
To resolve these issues, we're now unifying all of these systems into
one `ignore` crate walker setup. We also implemented features like
auto-source-detection and the `not` flag as additional _gitignore_ rules
only, avoid the need for a lot of custom code needed to make decisions.
High level, this is what happens after the now:
- We collect all non-negative `@source` rules into a list of _roots_
(that is the source directory for this rule) and optional _globs_ (that
is the actual rules for files in this file). For custom sources (i.e
with a custom `glob`), we add an allowlist rule to the gitignore setup,
so that we can be sure these files are always included.
- For every negative `@source` rule, we create respective ignore rules.
- Furthermore we have a custom filter that ensures files are only read
if they have been changed since the last time they were read.
So, consider the following setup:
```css
/* packages/web/src/index.css */
@import "tailwindcss";
@source "../../lib/ui/**/*.bin";
@source not "../../lib/ui/expensive.bin";
```
This creates a git ignore file that (simplified) looks like this:
```gitignore
# Auto-source rules
*.{exe,node,bin,…}
*.{css,scss,sass,…}
{node_modules,git}/
# Custom sources can overwrite auto-source rules
!lib/ui/**/*.bin
# Negative rules
lib/ui/expensive.bin
```
We then use this information _on top of your existing `.gitignore`
setup_ to resolve files (i.e so if your `.gitignore` contains rules e.g.
`dist/` this line is going to be added _before_ any of the rules lined
out in the example above. This allows negative rules to allow-list your
`.gitignore` rules.
To implement this, we're rely on the `ignore` crate but we had to make
various changes, very specific, to it so we decided to fork the crate.
All changes are prefixed with a `// CHANGED:` block but here are the
most-important ones:
- We added a way to add custom ignore rules that _extend_ (rather than
overwrite) your existing `.gitignore` rules
- We updated the order in which files are resolved and made it so that
more-specific files can allow-list more generic ignore rules.
- We resolved various issues related to adding more than one base path
to the traversal and ensured it works consistent for Linux, macOS, and
Windows.
## Behavioral changes
1. Any custom glob defined via `@source` now wins over your `.gitignore`
file and the auto-content rules.
- Resolves #16920
3. The `node_modules` and `.git` folders as well as the `.gitignore`
file are now ignored by default (but can be overridden by an explicit
`@source` rule).
- Resolves #17318
- Resolves #15882
4. Source paths into ignored-by-default folders (like `node_modules`)
now also win over your `.gitignore` configuration and auto-content
rules.
- Resolves #16669
5. Introduced `@source not "…"` to negate any previous rules.
- Resolves #17058
6. Negative `content` rules in your legacy JavaScript configuration
(e.g. `content: ['!./src']`) now work with v4.
- Resolves #15943
7. The order of `@source` definitions matter now, because you can
technically include or negate previous rules. This is similar to your
`.gitingore` file.
9. Rebuilds in watch mode now take the `@source` configuration into
account
- Resolves #15684
## Combining with other features
Note that the `not` flag is also already compatible with [`@source
inline(…)`](https://github.com/tailwindlabs/tailwindcss/pull/17147)
added in an earlier commit:
```css
@import "tailwindcss";
@source not inline("container");
```
## Test plan
- We added a bunch of oxide unit tests to ensure that the right files
are scanned
- We updated the existing integration tests with new `@source not "…"`
specific examples and updated the existing tests to match the subtle
behavior changes
- We also added a new special tag `[ci-all]` that, when added to the
description of a PR, causes the PR to run unit and integration tests on
all operating systems.
[ci-all]
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
|
||
|
|
711b9cd0db
|
Pre process Slim templates embedded in Ruby files (#17336)
This PR fixes an issue where embedded Slim templates inside of Ruby
files are not pre processed because we pre process based on a file
extension.
This PR also handles embedded SLIM templates using the following syntax:
```rb
slim_template <<~SLIM
.flex
.flex-1
h1 Hello World
.flex-1
p This is a test
SLIM
```
~~As far as I can tell, this is only a Slim template thing and not a
Haml template thing but I could be wrong here. See:
https://viewcomponent.org/guide/templates.html#interpolations~~
The ViewComponent package handles anything that looks like
`{lang}_template`, so the lang here will be used as the pre processing
language for now.
Fixes: #17334
# Test plan
1. Added test for this
2. Existing tests pass
3. Made sure that the snippet from the issue works as expected:
Added an example where we have a `slim_template` and a `svelte_template`
to prove that it embeds based on the language. I also added a
`html_template` with Svelte syntax to really make sure that that
_doesn't_ work.
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/35564a32-9c46-4b51-bb1f-e02f4ffe8b01"
/>
|
||
|
|
5426baf358
|
Fix class extraction followed by ( in Pug (#17320)
This PR fixes an issue where a class shorthand in Pug followed by a `(` is not properly extracted. ```html <template lang="pug"> .text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute. </template> ``` The `text-sky-600` is extracted, but the `bg-neutral-900` is not. Fixes: #17313 # Test plan 1. Added test to cover this case 2. Existing tests pass (after a few small adjustments due to _more_ extracted candidates, but definitely not _less_) 3. Verified against the original issue (top is before, bottom is this PR) <img width="1307" alt="image" src="https://github.com/user-attachments/assets/68a0529f-63ad-477d-a342-e3f91c5a1690" /> We had this exact same bug in Slim (https://github.com/tailwindlabs/tailwindcss/pull/17278). Since Pug, Slim and Haml are the only pre processors we have right now with this dot-separated class notation I also double checked the Haml pre-processor if this is an issue or not (and it's already covered there). <img width="1263" alt="image" src="https://github.com/user-attachments/assets/c658168b-d124-46c9-9ec0-9697151a57bf" /> |
||
|
|
f369e22172
|
Fix class extraction followed by ( in Slim (#17278)
This PR fixes an issue where using the class shorthand in Slim
templates, followed by an `(` results in the last class being ignored.
E.g.:
```slim
body.border-t-4.p-8(class="#{body_classes}" data-hotwire-native="#{hotwire_native_app?}" data-controller="update-time-zone")
```
This is because we will eventually extract `p-8` but it's followed by an
invalid boundary character `(`.
To solve this, we make sure to replace the `(` with a space. We already
do a similar thing when the classes are followed by an `[`.
One caveat, we _can_ have `(` in our classes, like `bg-(--my-color)`.
But in my testing this is not something that can be used in the
shorthand version.
E.g.:
```slim
div.bg-(--my-color)
```
Compiles to:
```html
<div --my-color="" class="bg-"></div>
```
So I didn't add any special handling for this. Even when trying to
escape the `(`, `-` and `)` characters, it still doesn't work. E.g.:
```slim
div.bg-\(--my-color\)
```
Compiles to:
```html
<div class="bg-">\(--my-color\)</div>
```
# Test plan
1. Added test for the issue
2. Existing tests pass
3. Verified via the extractor tool:
| Before | After |
| --- | --- |
| <img width="958" alt="image"
src="https://github.com/user-attachments/assets/f72c420e-5429-424f-a01d-12f724062bf2"
/> | <img width="958" alt="image"
src="https://github.com/user-attachments/assets/b0cc8f2f-97a8-4fca-8813-3bb1da8d99a8"
/> |
---
Fixes: #17277
|
||
|
|
d7c81164da
|
Pre process <template lang="…"> in Vue files (#17252)
This PR fixes an issue where `<template lang="…">…</template>` in Vue files should be handled as-if it's the language specified in the `lang` attribute. To do this, we added a new Vue pre processor and run the content through the same pre processor logic as we do for other languages. Fixes: #17211 # Test plan 1. Added a test to verify this works 2. Existing tests still work Visually verified against the reproduction in the issue: | Before | After | | --- | --- | | <img width="1273" alt="image" src="https://github.com/user-attachments/assets/d1accdeb-97cf-48ef-83fb-978832b3e599" /> | <img width="1273" alt="image" src="https://github.com/user-attachments/assets/ab7ec19c-b6c4-43be-8845-096ff4e58808" /> | --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com> |
||
|
|
221855b195
|
Ensure candidate extraction works as expected in Clojure/ClojureScript (#17087)
This PR adds a Clojure/ClojureScript pre processor to make sure that candidate extraction works as expected. | Before | After | | --- | --- | | <img width="908" alt="image" src="https://github.com/user-attachments/assets/98aba8b6-0c44-47c6-b87c-ecf955e5e007" /> | <img width="908" alt="image" src="https://github.com/user-attachments/assets/7a5ec3eb-1630-4b60-80bd-c07bc2381d3b" /> | You can see that the classes preceded by `:` are not properly extracted in the before case, but they are in the after case. We do extract a few more cases now like `:class` and `:className` itself, but at least we also retrieve all the `flex-*` classes. We could also always ignore `:class` and `:className` literals: <img width="908" alt="image" src="https://github.com/user-attachments/assets/f5a67cae-25d6-4811-b777-f72fdb5ef450" /> |
||
|
|
cedd54fecf
|
Fix variants with <digit>.</digit> are extracted correctly (#17153)
This PR fixes an issue where if you use a number with a decimal in a
variant then it wasn't picked up correctly.
E.g.:
```
<div class="2xl:flex 1.5xl:flex"></div>
^^^^^^^^ Picked up
^^^^^^^^^^ Not picket up
```
This PR fixes that behavior by applying the same rules for utilities
where a `.` is valid if it is surrounded by numbers.
# Test plan
1. Added test to ensure this is picked up
2. Existing tests pass
3. Ran the extractor on a real example with the following results:
| Before | After |
| --- | --- |
| <img width="821" alt="image"
src="https://github.com/user-attachments/assets/a77ed5e4-6848-4fe3-8cbf-cf61ff8db41d"
/> | <img width="821" alt="image"
src="https://github.com/user-attachments/assets/61aca66a-e38d-4b61-bf86-e6286a89a3d9"
/> |
They are crossed out just because it's not a default value we know in
the system, but you can see that the `1.` part is also extracted now.
Fixes: #17148
|
||
|
|
ca408d0612
|
Do not extract candidates containing JS string interpolation pattern ${ (#17142)
This PR fixes an issue where often people run into issues where they try
to use string interpolation and it doesn't work. Even worse, it could
result in crashes because we will actually generate CSS. This fix only
filters out candidates with a pattern like `${`. If this occurs in a
string position it is fine.
Another solution would be to add a pre processor for JS/TS (and all
thousand file extension combinations) but the problem is that you can
also write JS in HTML files so we would have to pre process HTML as well
which would not be ideal.
# Test plan
1. Added tests to prove this works in arbitrary values, arbitrary
variables in both utilities and variants.
2. Existing tests pass.
3. Some screenshots with before / after situations:
Given this input:
```ts
let color = '#0088cc';
let opacity = 0.8;
let name = 'variable-name';
let classes = [
// Arbitrary Properties
`[color:${color}]`
`[${property}:value]`,
`[--img:url('https://example.com?q=${name}')]`, // WONT WORK BUT VALID CSS
// Arbitrary Values
`bg-[${color}]`,
// Arbitrary Variables
`bg-(--my-${color})`,
`bg-(--my-color,${color})`,
// Arbitrary Modifier
`bg-red-500/[${opacity}]`,
`bg-red-500/(--my-${name})`,
`bg-red-500/(--my-opacity,${opacity})`,
// Arbitrary Variant
`data-[state=${name}]:flex`,
`supports-(--my-${name}):flex`,
`[@media(width>=${value})]:flex`,
];
```
This is the result:
| Before | After |
| --- | --- |
| <img width="908" alt="image"
src="https://github.com/user-attachments/assets/c64d1b16-d39d-48a6-a098-bc4477cb4b0a"
/> | <img width="908" alt="image"
src="https://github.com/user-attachments/assets/d71aaf62-5e13-4174-82bb-690eb81aaeaf"
/> |
Fixes: #17054
Fixes: #15853
|
||
|
|
37062928f9
|
Improve candidate extraction when candidates contain . characters (#17113)
This PR fixes an issue where some classes weren't properly extracted due
to some incorrect assumptions in the pre processors.
Templating languages such as Haml, Slim and Pug allow you to write
classes in a shorter way that are not properly contained inside of
strings. E.g.:
```slim
p.flex.px-2
```
These candidates are not properly extracted because there are no
bounding characters like quotes. To solve this, we pre-process it and
replace `.` with ` ` characters. This results in something like:
```
p flex px-2
```
However, this has some challenges on its own. Candidates like `px-2.5`
cannot be written in this shorthand form, instead they need to be in
strings. Now we _cannot_ replace the `.` because otherwise we would
change `px-2.5` to `px-2 5` which is wrong.
The next problem is that we need to know when they are in a "string".
This has another set of problems because these templating languages
allow you to write normal text that will eventually be the contents of
the HTML tags.
```haml
.text-red-500.text-3xl
| This text can't should be red
^ Wait, is this the start of a string now???
```
In this example, if we consider the `'` the start of a string, when it's
clearly not, how would we know it's for _sure_ not a string?
This ended up as a bit of a rabbit hole, but we came up with another
approach entirely if we think about the original problem we want to
solve which is when do we change `.` to ` ` characters.
One of the rules in our current extractor is that a `.` has to be
between 2 numbers. Which works great in a scenario like: `px-2.5`.
However, if you look at Haml or Slim syntax, this is also allowed:
```slim
p.bg-red-500.2xl:flex
^^^ Uh oh...
```
In this scenario, a `.` is surrounded by numbers so we shouldn't replace
it with a space. But as you can see, we clearly do... so we need another
heuristic in this case.
Luckily, one of the rules in Tailwind CSS is that a utility cannot start
with a number, but a variant _can_. This means that if we see a scenario
like `<digit>.<digit>` then we can just check if the value after the `.`
is a valid variant or not.
In this case it is a valid variant so we _do_ want to replace the `.`
with a ` ` even though we do have the `<digit>.<digit>` format.
🥴
# Test plan
1. Added additional tests.
2. Existing tests still pass
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
|
||
|
|
9d7f25316e
|
Don't extract links as arbitrary properties (#17129)
Closes #17128 This PR prevents extraction of links inside square brackets as valid candidate: ``` [https://example/] ``` We do this by throwing out arbitrary properties when the value starts with a slash (`/`). ## Test plan - Added unit test --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com> |
||
|
|
9ddeb091d7
|
Add JSON pre-processor (#17125)
This PR adds a small JSON pre processor to improve parsing JSON files. Due to the sheer amount of potential `[` and `]` brackets, it could be that parsing JSON files are way slower than they need to be. We saw this while debugging this issue: https://github.com/tailwindlabs/tailwindcss/issues/17092 # Test plan 1. Added test to verify the pre processing works 2. Existing tests still pass |
||
|
|
8bc633bb17
|
Add .node and .wasm to known binary file list (#17123)
Closes #17092 After a lot of spelunking we found one specific reason for the very slow builds in the repro from #17092: Turns our we are needlessly scanning the binary `.node` extension for class names 😬. This PR adds `.wasm` and `.node` to the list of known binary extensions. ## Test plan - Check out the repro from `#17092` - Delete the `.gitignore` file - Observe that builds are very slow (`527.79s`) - Add a _pnpm override_ to load local versions of Oxide - `pnpm build` now completes in ~50s |
||
|
|
de145c5b06
|
Refactor: use compile time type state pattern (#17083)
This PR implements the state machines using the type state pattern at
compile time (via generic types) instead of a runtime state variable.
There is no runtime check to see what state we are in, instead we
transition to the new state when it's necessary.
This has some nice performance improvements for some of the state
machines, e.g.:
```diff
- ArbitraryVariableMachine: Throughput: 744.92 MB/s
+ ArbitraryVariableMachine: Throughput: 1.21 GB/s
```
We also don't have to store the current state because each machine runs
to completion. It's during execution that we can move to a new state if
necessary.
Unfortunately the diff is a tiny bit annoying to read, but essentially
this is what happened:
### The `enum` is split up in it's individual states as structs:
```rs
enum State {
A,
B,
C,
}
```
Becomes:
```rs
struct A;
struct B;
struct C;
```
### Generics
The current machine will receive a generic `State` that we can default
to the `IdleState`. Then we use `PhantomData` to "use" the type because
the generic type is otherwise not used as a concrete value, it's just a
marker.
```rs
struct MyMachine {}
```
Becomes:
```rs
struct MyMachine<State = Idle> {
_state: std::marker::PhantomData<State>
}
```
### Split
Next, the `next` function used to match on the current state, but now
each match arm is moved to a dedicated implementation instead:
```rs
impl Machine for MyMachine {
fn next(&mut self) -> MachineState {
match self.state {
State::A => { /* … */ },
State::B => { /* … */ },
State::C => { /* … */ },
}
}
}
```
Becomes:
```rs
impl Machine for MyMachine<A> {
fn next(&mut self) -> MachineState {
/* … */
}
}
impl Machine for MyMachine<B> {
fn next(&mut self) -> MachineState {
/* … */
}
}
impl Machine for MyMachine<C> {
fn next(&mut self) -> MachineState {
/* … */
}
}
```
It's a bit more verbose, but now each state is implemented in its own
block. This also removes 2 levels of nesting which is a nice benefit.
|
||
|
|
cc3e852791
|
Treat starting single quote as verbatim text in Slim (#17085)
This PR fixes an issue in Slim templates where a single quote `'` at the start of the line (excluding white space) is considered a line indicator for verbatim text. It is not considered a string in this scenario. So something like this: ```slim div 'Foo' ``` Will compile to: ```html <div>Foo'</div> ``` Fixes: #17081 |
||
|
|
bc5a8c3683
|
Ensure classes between > and < are properly extracted (#17094)
This PR fixes an issue where candidates inside `>…<` were not always
correctly extracted. This happens in XML-like languages where the
classes are inside of these boundaries.
E.g.:
```html
<!-- Fluid template language -->
<f:variable name="bgStyle">
<f:switch expression="{data.layout}">
<f:case value="0">from-blue-900 to-cyan-200</f:case>
<!-- ^^^^^^^^^^^^^^^^^^^^^^^^^ -->
<f:case value="1">from-cyan-600 to-teal-200</f:case>
<f:defaultCase>from-blue-300 to-cyan-100</f:defaultCase>
</f:switch>
</f:variable>
```
Fixes: https://github.com/tailwindlabs/tailwindcss/issues/17088
# Test plan
1. Added a new test
2. Existing tests pass
|
||
|
|
7005ad7e00
|
Add Haml pre processor (#17051)
This PR ensures we extract candidates from Haml files. Fixes: #17050 |
||
|
|
f498e4a97c
|
Ensure extracting candidates from JS embedded in a PHP string works as expected (#17031)
This PR fixes an issue where candidates are not properly extractor when
they end in `\`. This can happen if you embed a programming language
like JS inside another language like PHP where you need to escape some
strings.
Here is an example of Livewire flux:
```blade
@php
if ($sidebarIsStashable) {
$attributes = $attributes->merge([
'x-init' => '$el.classList.add(\'-translate-x-full\'); $el.classList.add(\'transition-transform\')',
// ^ ^
]);
}
@endphp
<div x-data {{ $attributes->class('border-r w-64 p-4 min-h-dvh max-h-dvh top-0 fixed left-0') }}>
{{ $slot }}
</div>
```
Where the `\'` is causing some issues.
Another solution might be to add a custom pre processor for blade files
where we drop the escaped characters, but that felt overkill for now
because some escapes are still valid.
Fixes: #17023
# Test plan
1. Added a test to cover this case.
2. Existing tests still pass
|
||
|
|
d18fed1dca
|
Add razor/cshtml pre processing (#17027)
This PR fixes an issue in Razor template files where `@sm:flex` doesn't work and `@@sm:flex` is required. In Tailwind CSS v3, some people used a custom transform to replace `@@` with just `@`. But in Tailwind CSS v4 we don't have this. However, we can add a pre processor for `.cshtml` and `.razor` files. Fixes: #17022 |
||
|
|
d0a97467f4
|
Improve boundary classification (#17005)
This PR cleans up the boundary character checking by using similar
classification techniques as we used for other classification problems.
For starters, this moves the boundary related items to its own file,
next we setup the classification enum.
Last but not least, we removed `}` as an _after_ boundary character, and
instead handle that situation in the Ruby pre processor where we need
it. This means the `%w{flex}` will still work in Ruby files.
---
This PR is a followup for
https://github.com/tailwindlabs/tailwindcss/pull/17001, the main goal is
to clean up some of the boundary character checking code. The other big
improvement is performance. Changing the boundary character checking to
use a classification instead results in:
Took the best score of 10 runs each:
```diff
- CandidateMachine: Throughput: 311.96 MB/s
+ CandidateMachine: Throughput: 333.52 MB/s
```
So a ~20MB/s improvement.
# Test plan
1. Existing tests should pass. Due to the removal of `}` as an after
boundary character, some tests are updated.
2. Added new tests to ensure the Ruby pre processor still works as
expected.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
|
||
|
|
57e91a671a
|
Ensure } and { are valid boundary characters (#17001)
|
||
|
|
85c6e04f44
|
Ensure strings in Pug and Slim templates are handled correctly (#17000)
This PR fixes an issue where strings in the Pug and Slim pre-processor
were handled using the `string_machine`. However, the `string_machine`
is not for strings inside of Tailwind CSS classes which means that
whitespace is invalid.
This means that parts of the code that _are_ inside strings will not be
inside strings and parts of the code that are not inside strings will be
part of a potential string. This is a bit confusing to wrap your head
around, but here is a visual representation of the problem:
```
.join(' ')
^ 3. start of new string, which means that the `)` _could_ be part of a string if a new `'` occurs later.
^ 2. whitespace is not allowed, stop string
^ 1. start of string
```
Fixes: #16998
# Test plan
1. Added new test
2. Existing tests still pass
3. Added a simple test helper to make sure that we can extract the
correct candidates _after_ pre-processing
|
||
|
|
bff387bf6b
|
Ensure arbitrary variables with data types are extracted correctly (#16986)
Closes #16983 This PR fixes an issue where the arbitrary variable machine did not extract data type correctly. ## Test plan - Added regression test |
||
|
|
af132fbedb
|
Fix Slim attributes (#16985)
This PR fixes an issue in Slim templates where the start of attributes causes some candidates to be missing. ```slim .text-xl.text-red-600[ data-foo="bar" ] | This line should be red ``` Because of the `[` attached to the `text-red-600`, the `text-red-600` was not extracted because `[` is not a valid boundary character. To solve this, we copied the Pug pre processor and created a dedicated Slim pre processor. Next, we ensure that we replace `[` with ` ` in this scenario (by also making sure that we don't replace `[` where it's important). Additionally, we noticed that `.` was also replaced inside of arbitrary values such as URLs. This has been fixed for both Pug and Slim. Fixes: #16975 # Test plan 1. Added failing tests 2. Existing tests still pass |
||
|
|
617b7abb81
|
Oxide: Extract arbitrary container queries (#16984)
Closes #16982 Handle the case of variants looking like this: `@[32rem]:flex`. ## Test plan Added regression tests Co-authored-by: Robin Malfait <malfait.robin@gmail.com> |
||
|
|
4a0236471e
|
Ensure classes containing number followed by dash or underscore are extracted correctly (#16980)
Fixes #16978, and also added support for dash. Classes like `text-theme1-primary` or `text-theme1_primary` should be treated as valid. If this approach is not aligned with the project’s direction or there are any concerns, please feel free to close or edit this PR 😃 <br/> ### As is Classes conatining number followed by dash or underscore (e.g. `bg-theme1-primary`, `text-title1_strong`) are ignored, and utility classes are not generated. ### To be Classes conatining number followed by dash or underscore (e.g. `bg-theme1-primary`, `text-title1_strong`) are treated as valid tailwindcss classes --------- Co-authored-by: Philipp Spiess <hello@philippspiess.com> |
||
|
|
9c59b07fb3
|
Ensure -- is allowed inside candidates (#16972)
This PR fixes an issue where named utilities that contain double dashes `--` are not extracted correctly. Some people use `--` in the middle of the utility to create some form of namespaced utility. Given this input: ```js let x = 'foo--bar' ``` The extracted candidates before this change: ```js [ "let", "x", "--bar" ] ``` The extracted candidates after this change: ```js [ "let", "x", "foo--bar", "--bar" ] ``` The reason `--bar` is still extracted in both cases is because of the CSS variable machine. We could improve its extraction by checking its boundary characters but that's a different issue. For now, the important thing is that `foo--bar` was extracted. # Test plan 1. Added new test 2. Existing tests pass |
||
|
|
4c110014f1
|
Improve internal DX around byte classification [1] (#16864)
This PR improves the internal DX when working with `u8` classification
into a smaller enum. This is done by implementing a `ClassifyBytes` proc
derive macro. The benefit of this is that the DX is much better and
everything you will see here is done at compile time.
Before:
```rs
#[derive(Debug, Clone, Copy, PartialEq)]
enum Class {
ValidStart,
ValidInside,
OpenBracket,
OpenParen,
Slash,
Other,
}
const CLASS_TABLE: [Class; 256] = {
let mut table = [Class::Other; 256];
macro_rules! set {
($class:expr, $($byte:expr),+ $(,)?) => {
$(table[$byte as usize] = $class;)+
};
}
macro_rules! set_range {
($class:expr, $start:literal ..= $end:literal) => {
let mut i = $start;
while i <= $end {
table[i as usize] = $class;
i += 1;
}
};
}
set_range!(Class::ValidStart, b'a'..=b'z');
set_range!(Class::ValidStart, b'A'..=b'Z');
set_range!(Class::ValidStart, b'0'..=b'9');
set!(Class::OpenBracket, b'[');
set!(Class::OpenParen, b'(');
set!(Class::Slash, b'/');
set!(Class::ValidInside, b'-', b'_', b'.');
table
};
```
After:
```rs
#[derive(Debug, Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z', b'A'..=b'Z', b'0'..=b'9')]
ValidStart,
#[bytes(b'-', b'_', b'.')]
ValidInside,
#[bytes(b'[')]
OpenBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b'/')]
Slash,
#[fallback]
Other,
}
```
Before we were generating a `CLASS_TABLE` that we could access directly,
but now it will be part of the `Class`. This means that the usage has to
change:
```diff
- CLASS_TABLE[cursor.curr as usize]
+ Class::TABLE[cursor.curr as usize]
```
This is slightly worse UX, and this is where another change comes in. We
implemented the `From<u8> for #enum_name` trait inside of the
`ClassifyBytes` derive macro. This allows us to use `.into()` on any
`u8` as long as we are comparing it to a `Class` instance. In our
scenario:
```diff
- Class::TABLE[cursor.curr as usize]
+ cursor.curr.into()
```
Usage wise, this looks something like this:
```diff
while cursor.pos < len {
- match Class::TABLE[cursor.curr as usize] {
+ match cursor.curr.into() {
- Class::Escape => match Class::Table[cursor.next as usize] {
+ Class::Escape => match cursor.next.into() {
// An escaped whitespace character is not allowed
Class::Whitespace => return MachineState::Idle,
// An escaped character, skip ahead to the next character
_ => cursor.advance(),
},
// End of the string
Class::Quote if cursor.curr == end_char => return self.done(start_pos, cursor),
// Any kind of whitespace is not allowed
Class::Whitespace => return MachineState::Idle,
// Everything else is valid
_ => {}
};
cursor.advance()
}
MachineState::Idle
}
}
```
If you manually look at the `Class::TABLE` in your editor for example,
you can see that it is properly generated at compile time.
Given this input:
```rs
#[derive(Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z')]
AlphaLower,
#[bytes_range(b'A'..=b'Z')]
AlphaUpper,
#[bytes(b'@')]
At,
#[bytes(b':')]
Colon,
#[bytes(b'-')]
Dash,
#[bytes(b'.')]
Dot,
#[bytes(b'\0')]
End,
#[bytes(b'!')]
Exclamation,
#[bytes_range(b'0'..=b'9')]
Number,
#[bytes(b'[')]
OpenBracket,
#[bytes(b']')]
CloseBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b'%')]
Percent,
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b'/')]
Slash,
#[bytes(b'_')]
Underscore,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[fallback]
Other,
}
```
This is the result:
<img width="1244" alt="image"
src="https://github.com/user-attachments/assets/6ffd6ad3-0b2f-4381-a24c-593e4c72080e"
/>
|
||
|
|
b3c25563b7
|
Improve Oxide candidate extractor [0] (#16306)
This PR adds a new candidate[^candidate] extractor with 2 major goals in
mind:
1. It must be way easier to reason about and maintain.
2. It must have on-par performance or better than the current candidate
extractor.
### Problem
Candidate extraction is a bit of a wild west in Tailwind CSS and it's a
very critical step to make sure that all your classes are picked up
correctly to ensure that your website/app looks good.
One issue we run into is that Tailwind CSS is used in many different
"host" languages and frameworks with their own syntax. It's not only
used in HTML but also in JSX/TSX, Vue, Svelte, Angular, Pug, Rust, PHP,
Rails, Clojure, .NET, … the list goes on and all of these have different
syntaxes. Introducing dedicated parsers for each of these languages
would be a huge maintenance burden because there will be new languages
and frameworks coming up all the time. The best thing we can do is make
assumptions and so far we've done a pretty good job at that.
The only certainty we have is that there is at least _some_ structure to
the possible Tailwind classes used in a file. E.g.: `abc#def` is
definitely not a valid class, `hover:flex` definitely is. In a perfect
world we limit the characters that can be used and defined a formal
grammar that each candidate must follow, but that's not really an option
right now (maybe this is something we can implement in future major
versions).
The current candidate extractor we have has grown organically over time
and required patching things here and there to make it work in various
scenarios (and edge cases due to the different languages Tailwind is
used in).
While there is definitely some structure, we essentially work in 2
phases:
1. Try to extract `0..n` candidates. (This is the hard part)
2. Validate each candidate to make sure they are valid looking classes
(by validating against the few rules we have)
Another reason the current extractor is hard to reason about is that we
need it to be fast and that comes with some trade-offs to readability
and maintainability.
Unfortunately there will always be a lot of false positives, but if we
extract more classes than necessary then that's fine. It's only when we
pass the candidates to the core engine that we will know for sure if
they are valid or not. (we have some ideas to limit the amount of false
positives but that's for another time)
### Solution
Since the introduction of Tailwind CSS v4, we re-worked the internals
quite a bit and we have a dedicated internal AST structure for
candidates. For example, if you take a look at this:
```html
<div class="[@media(pointer:fine)]:data-[state=pending]:hover:text-red-500/(--my-opacity)"></div>
```
<details>
<summary>This will be parsed into the following AST:</summary>
```json
[
{
"kind": "functional",
"root": "text",
"value": {
"kind": "named",
"value": "red-500",
"fraction": null
},
"modifier": {
"kind": "arbitrary",
"value": "var(--my-opacity)"
},
"variants": [
{
"kind": "static",
"root": "hover"
},
{
"kind": "functional",
"root": "data",
"value": {
"kind": "arbitrary",
"value": "state=pending"
},
"modifier": null
},
{
"kind": "arbitrary",
"selector": "@media(pointer:fine)",
"relative": false
}
],
"important": false,
"raw": "[@media(pointer:fine)]:data-[state=pending]:hover:text-red-500/(--my-opacity)"
}
]
```
</details>
We have a lot of information here and we gave these patterns a name
internally. You'll see names like `functional`, `static`, `arbitrary`,
`modifier`, `variant`, `compound`, ...
Some of these patterns will be important for the new candidate extractor
as well:
| Name | Example | Description |
| -------------------------- | ----------------- |
---------------------------------------------------------------------------------------------------
|
| Static utility (named) | `flex` | A simple utility with no inputs
whatsoever |
| Functional utility (named) | `bg-red-500` | A utility `bg` with an
input that is named `red-500` |
| Arbitrary value | `bg-[#0088cc]` | A utility `bg` with an input that
is arbitrary, denoted by `[…]` |
| Arbitrary variable | `bg-(--my-color)` | A utility `bg` with an input
that is arbitrary and has a CSS variable shorthand, denoted by `(--…)` |
| Arbitrary property | `[color:red]` | A utility that sets a property to
a value on the fly |
A similar structure exist for modifiers, where each modifier must start
with `/`:
| Name | Example | Description |
| ------------------ | --------------------------- |
---------------------------------------- |
| Named modifier | bg-red-500`/20` | A named modifier |
| Arbitrary value | bg-red-500`/[20%]` | An arbitrary value, denoted by
`/[…]` |
| Arbitrary variable | bg-red-500`/(--my-opacity)` | An arbitrary
variable, denoted by `/(…)` |
Last but not least, we have variants. They have a very similar pattern
but they _must_ end in a `:`.
| Name | Example | Description |
| ------------------ | --------------------------- |
------------------------------------------------------------------------
|
| Named variant | `hover:` | A named variant |
| Arbitrary value | `data-[state=pending]:` | An arbitrary value,
denoted by `[…]` |
| Arbitrary variable | `supports-(--my-variable):` | An arbitrary
variable, denoted by `(…)` |
| Arbitrary variant | `[@media(pointer:fine)]:` | Similar to arbitrary
properties, this will generate a variant on the fly |
The goal with the new extractor is to encode these separate patterns in
dedicated pieces of code (we called them "machines" because they are
mostly state machine based and because I've been watching Person of
Interest but I digress).
This will allow us to focus on each pattern separately, so if there is a
bug or some new syntax we want to support we can add it to those
machines.
One nice benefit of this is that we can encode the rules and handle
validation as we go. The moment we know that some pattern is invalid, we
can bail out early.
At the time of writing this, there are a bunch of machines:
<details>
<summary>Overview of the machines</summary>
- `ArbitraryPropertyMachine`
Extracts candidates such as `[color:red]`. Some of the rules are:
1. There must be a property name
2. There must be a `:`
3. There must ba a value
There cannot be any spaces, the brackets are included, if the property
is a CSS variable, it must be a valid CSS variable (uses the
`CssVariableMachine`).
```
[color:red]
^^^^^^^^^^^
[--my-color:red]
^^^^^^^^^^^^^^^^
```
Depends on the `StringMachine` and `CssVariableMachine`.
- `ArbitraryValueMachine`
Extracts arbitrary values for utilities and modifiers including the
brackets:
```
bg-[#0088cc]
^^^^^^^^^
bg-red-500/[20%]
^^^^^
```
Depends on the `StringMachine`.
- `ArbitraryVariableMachine`
Extracts arbitrary variables including the parentheses. The first
argument must be a valid CSS variable, the other arguments are optional
fallback arguments.
```
(--my-value)
^^^^^^^^^^^^
bg-red-500/(--my-opacity)
^^^^^^^^^^^^^^
```
Depends on the `StringMachine` and `CssVariableMachine`.
- `CandidateMachine`
Uses the variant machine and utility machine. It will make sure that 0
or more variants are directly touching and followed by a utility.
```
hover:focus:flex
^^^^^^^^^^^^^^^^
aria-invalid:bg-red-500/(--my-opacity)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
Depends on the `VariantMachine` and `UtilityMachine`.
- `CssVariableMachine`
Extracts CSS variables, they must start with `--` and must contain at
least one alphanumeric character or, `-`, `_` and can contain any
escaped character (except for whitespace).
```
bg-(--my-color)
^^^^^^^^^^
bg-red-500/(--my-opacity)
^^^^^^^^^^^^
bg-(--my-color)/(--my-opacity)
^^^^^^^^^^ ^^^^^^^^^^^^
```
- `ModifierMachine`
Extracts modifiers including the `/`
- `/[` will delegate to the `ArbitraryValueMachine`
- `/(` will delegate to the `ArbitraryVariableMachine`
```
bg-red-500/20
^^^
bg-red-500/[20%]
^^^^^^
bg-red-500/(--my-opacity)
^^^^^^^^^^^^^^^
```
Depends on the `ArbitraryValueMachine` and `ArbitraryVariableMachine`.
- `NamedUtilityMachine`
Extracts named utilities regardless of whether they are functional or
static.
```
flex
^^^^
px-2.5
^^^^^^
```
This includes rules like: A `.` must be surrounded by digits.
Depends on the `ArbitraryValueMachine` and `ArbitraryVariableMachine`.
- `NamedVariantMachine`
Extracts named variants regardless of whether they are functional or
static. This is very similar to the `NamedUtilityMachine` but with
different rules. We could combine them, but splitting things up makes it
easier to reason about.
Another rule is that the `:` must be included.
```
hover:flex
^^^^^^
data-[state=pending]:flex
^^^^^^^^^^^^^^^^^^^^^
supports-(--my-variable):flex
^^^^^^^^^^^^^^^^^^^^^^^^^
```
Depends on the `ArbitraryVariableMachine`, `ArbitraryValueMachine`, and
`ModifierMachine`.
- `StringMachine`
This is a low-level machine that is used by various other machines. The
only job this has is to extract strings that start with double quotes,
single quotes or backticks.
We have this because once you are in a string, we don't have to make
sure that brackets, parens and curlies are properly balanced. We have to
make sure that balancing brackets are properly handled in other
machines.
```
content-["Hello_World!"]
^^^^^^^^^^^^^^
bg-[url("https://example.com")]
^^^^^^^^^^^^^^^^^^^^^
```
- `UtilityMachine`
Extracts utilities, it will use the lower level `NamedUtilityMachine`,
`ArbitraryPropertyMachine` and `ModifierMachine` to extract the utility.
It will also handle important markers (including the legacy important
marker).
```
flex
^^^^
bg-red-500/20
^^^^^^^^^^^^^
!bg-red-500/20 Legacy important marker
^^^^^^^^^^^^^^
bg-red-500/20! New important marker
^^^^^^^^^^^^^^
!bg-red-500/20! Both, but this is considered invalid
^^^^^^^^^^^^^^^
```
Depends on the `ArbitraryPropertyMachine`, `NamedUtilityMachine`, and
`ModifierMachine`.
- `VariantMachine`
Extracts variants, it will use the lower level `NamedVariantMachine` and
`ArbitraryValueMachine` to extract the variant.
```
hover:focus:flex
^^^^^^
^^^^^^
```
Depends on the `NamedVariantMachine` and `ArbitraryValueMachine`.
</details>
One important thing to know here is that each machine runs to
completion. They all implement a `Machine` trait that has a
`next(cursor)` method and returns a `MachineState`.
The `MachineState` looks like this:
```rs
enum MachineState {
Idle,
Done(Span)
}
```
Where a `Span` is just the location in the input where the candidate was
found.
```rs
struct Span {
pub start: usize,
pub end: usize,
}
```
#### Complexities
**Boundary characters:**
When running these machines to completion, they don't typically check
for boundary characters, the wrapping `CandidateMachine` will check for
boundary characters.
A boundary character is where we know that even though the character is
touching the candidate it will not be part of the candidate.
```html
<div class="flex"></div>
<!-- ^ ^ -->
```
The quotes are touching the candidate `flex`, but they will not be part
of the candidate itself, so this is considered a valid candidate.
**What to pick?**
Let's imagine you are parsing this input:
```html
<div class="hover:flex"></div>
```
The `UtilityMachine` will find `hover` and `flex`. The `VariantMachine`
will find `hover:`. This means that at a certain point in the
`CandidateMachine` you will see something like this:
```rs
let variant_machine_state = variant_machine.next(cursor);
// MachineState::Done(Span { start: 12, end: 17 }) // `hover:`
let utility_machine_state = utility_machine.next(cursor);
// MachineState::Done(Span { start: 12, end: 16 }) // `hover`
```
They are both done, but which one do we pick? In this scenario we will
always pick the variant because its range will always be 1 character
longer than the utility.
Of course there is an exception to this rule and it has to do with the
fact that Tailwind CSS can be used in different languages and
frameworks. A lot of people use `clsx` for dynamically applying classes
to their React components. E.g.:
```tsx
<div
class={clsx({
underline: someCondition(),
})}
></div>
```
In this scenario, we will see `underline:` as a variant, and `underline`
as a utility. We will pick the utility in this scenario because the next
character is whitespace so this will never be a valid candidate
otherwise (variants and utilities must be touching). Another reason this
is valid, is because there wasn't a variant present prior to this
candidate.
E.g.:
```tsx
<div
class={clsx({
hover:underline: someCondition(),
})}
></div>
```
This will be considered invalid, if you do want this, you should use
quotes.
E.g.:
```tsx
<div
class={clsx({
'hover:underline': someCondition(),
})}
></div>
```
**Overlapping/covered spans:**
Another complexity is that the extracted spans for candidates can and
will overlap. Let's take a look at this C# example:
```csharp
public enum StackSpacing
{
[CssClass("gap-y-4")]
Small,
[CssClass("gap-y-6")]
Medium,
[CssClass("gap-y-8")]
Large
}
```
In this scenario, `[CssClass("gap-y-4")]` starts with a `[` so we have a
few options here:
1. It is an arbitrary property, e.g.: `[color:red]`
2. It is an arbitrary variant, e.g.: `[@media(pointer:fine)]:`
When running the parsers, both the `VariantMachine` and the
`UtilityMachine` will run to completion but end up in a
`MachineState::Idle` state.
- This is because it is not a valid variant because it didn't end with a
`:`.
- It's also not a valid arbitrary property, because it didn't include a
`:` to separate the property from the value.
Looking at the code as a human it's very clear what this is supposed to
be, but not from the individual machines perspective.
Obviously we want to extract the `gap-y-*` classes here.
To solve this problem, we will run over an additional slice of the
input, starting at the position before the machines started parsing
until the position where the machines stopped parsing.
That slice will be this one: `[CssClass("gap-y-6")]` (we already skipped
over the whitespace). Now, for every `[` character we see, will start a
new `CandidateMachine` right after the `[`'s position and run the
machines over that slice. This will now eventually extract the `gap-y-6`
class.
The next question is, what if there was a `:` (e.g.:
`[CssClass("gap-y-6")]:`), then the `VariantMachine` would complete, but
the `UtilityMachine` will not because not exists after it. We will apply
the same idea in this case.
Another issue is if we _do_ have actual overlapping ranges. E.g.: `let
classes = ['[color:red]'];`. This will extract both the `[color:red]`
and `color:red` classes. You have to use your imagination, but the last
one has the exact same structure as `hover:flex` (variant + utility).
In this case we will make sure to drop spans that are covered by other
spans.
The extracted `Span`s will be valid candidates therefore if the outer
most candidate is valid, we can throw away the inner candidate.
```
Position: 11112222222
67890123456
↓↓↓↓↓↓↓↓↓↓↓
Span { start: 17, end: 25 } // color:red
Span { start: 16, end: 26 } // [color:red]
```
#### Exceptions
**JavaScript keys as candidates:**
We already talked about the `clsx` scenario, but there are a few more
exceptions and that has to do with different syntaxes.
**CSS class shorthand in certain templating languages:**
In Pug and Slim, you can have a syntax like this:
```pug
.flex.underline
div Hello World
```
<details>
<summary>Generated HTML</summary>
```html
<div class="flex underline">
<div>Hello World</div>
</div>
```
</details>
We have to make sure that in these scenarios the `.` is a valid boundary
character. For this, we introduce a pre-processing step to massage the
input a little bit to improve the extraction of the data. We have to
make sure we don't make the input smaller or longer otherwise the
positions might be off.
In this scenario, we could simply replace the `.` with a space. But of
course, there are scenarios in these languages where it's not safe to do
that.
If you want to use `px-2.5` with this syntax, then you'd write:
```pug
.flex.px-2.5
div Hello World
```
But that's invalid because that technically means `flex`, `px-2`, and
`5` as classes.
You can use this syntax to get around that:
```pug
div(class="px-2.5")
div Hello World
```
<details>
<summary>Generated HTML</summary>
```html
<div class="px-2.5">
<div>Hello World</div>
</div>
```
</details>
Which means that we can't simply replace `.` with a space, but have to
parse the input. Luckily we only care about strings (and we have a
`StringMachine` for that) and ignore replacing `.` inside of strings.
**Ruby's weird string syntax:**
```ruby
%w[flex underline]
```
This is valid syntax and is shorthand for:
```ruby
["flex", "underline"]
```
Luckily this problem is solved by the running the sub-machines after
each `[` character.
### Performance
**Testing:**
Each machine has a `test_…_performance` test (that is ignored by
default) that allows you to test the throughput of that machine. If you
want to run them, you can use the following command:
```sh
cargo test test_variant_machine_performance --release -- --ignored
```
This will run the test in release mode and allows you to run the ignored
test.
> [!CAUTION]
> This test **_will_** fail, but it will print some output. E.g.:
```
tailwindcss_oxide::extractor::variant_machine::VariantMachine: Throughput: 737.75 MB/s over 0.02s
tailwindcss_oxide::extractor::variant_machine::VariantMachine: Duration: 500ns
```
**Readability:**
One thing to note when looking at the code is that it's not always
written in the cleanest way but we had to make some sacrifices for
performance reasons.
The `input` is of type `&[u8]`, so we are already dealing with bytes.
Luckily, Rust has some nice ergonomics to easily write `b'['` instead of
`0x5b`.
A concrete example where we had to sacrifice readability is the state
machines where we check the `previous`, `current` and `next` character
to make decisions. For a named utility one of the rules is that a `.`
must be preceded by and followed by a digit. This can be written as:
```rs
match (cursor.prev, cursor.curr, cursor.next) {
(b'0'..=b'9', b'.', b'0'..=b'9') => { /* … */ }
_ => { /* … */ }
}
```
But this is not very fast because Rust can't optimize the match
statement very well, especially because we are dealing with tuples
containing 3 values and each value is a `u8`.
To solve this we use some nesting, once we reach `b'.'` only then will
we check for the previous and next characters. We will also early return
in most places. If the previous character is not a digit, there is no
need to check the next character.
**Classification and jump tables:**
Another optimization we did is to classify the characters into a much
smaller `enum` such that Rust _can_ optimize all `match` arms and create
some jump tables behind the scenes.
E.g.:
```rs
#[derive(Debug, Clone, Copy, PartialEq)]
enum Class {
/// ', ", or `
Quote,
/// \
Escape,
/// Whitespace characters
Whitespace,
Other,
}
const CLASS_TABLE: [Class; 256] = {
let mut table = [Class::Other; 256];
macro_rules! set {
($class:expr, $($byte:expr),+ $(,)?) => {
$(table[$byte as usize] = $class;)+
};
}
set!(Class::Quote, b'"', b'\'', b'`');
set!(Class::Escape, b'\\');
set!(Class::Whitespace, b' ', b'\t', b'\n', b'\r', b'\x0C');
table
};
```
There are only 4 values in this enum, so Rust can optimize this very
well. The `CLASS_TABLE` is generated at compile time and must be exactly
256 elements long to fit all `u8` values.
**Inlining**:
Last but not least, sometimes we use functions to abstract some logic.
Luckily Rust will optimize and inline most of the functions
automatically. In some scenarios, explicitly adding a
`#[inline(always)]` improves performance, sometimes it doesn't improve
it at all.
You might notice that in some functions the annotation is added and in
some it's not. Every state machine was tested on its own and whenever
the performance was better with the annotation, it was added.
### Test Plan
1. Each machine has a dedicated set of tests to try and extract the
relevant part for that machine. Most machines don't even check boundary
characters or try to extract nested candidates. So keep that in mind
when adding new tests. Extracting inside of nested `[…]` is only handled
by the outer most `extractor/mod.rs`.
2. The main `extractor/mod.rs` has dedicated tests for recent bug
reports related to missing candidates.
3. You can test each machine's performance if you want to.
There is a chance that this new parser is missing candidates even though
a lot of tests are added and existing tests have been ported.
To double check, we ran the new extractor on our own projects to make
sure we didn't miss anything obvious.
#### Tailwind UI
On Tailwind UI the diff looks like this:
<details>
<summary>diff</summary>
```diff
diff --git a/./main.css b/./pr.css
index d83b0a506..b3dd94a1d 100644
--- a/./main.css
+++ b/./pr.css
@@ -5576,9 +5576,6 @@ @layer utilities {
--tw-saturate: saturate(0%);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
- .\!filter {
- filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) !important;
- }
.filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
```
</details>
The reason `!filter` is gone, is because it was used like this:
```js
getProducts.js
23: if (!filter) return true
```
And right now `(` and `)` are not considered valid boundary characters
for a candidate.
#### Catalyst
On Catalyst, the diff looks like this:
<details>
<summary>diff</summary>
```diff
diff --git a/./main.css b/./pr.css
index 9f8ed129..4aec992e 100644
--- a/./main.css
+++ b/./pr.css
@@ -2105,9 +2105,6 @@
.outline-transparent {
outline-color: transparent;
}
- .filter {
- filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
- }
.backdrop-blur-\[6px\] {
--tw-backdrop-blur: blur(6px);
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -7141,46 +7138,6 @@
inherits: false;
initial-value: solid;
}
-@property --tw-blur {
- syntax: "*";
- inherits: false;
-}
-@property --tw-brightness {
- syntax: "*";
- inherits: false;
-}
-@property --tw-contrast {
- syntax: "*";
- inherits: false;
-}
-@property --tw-grayscale {
- syntax: "*";
- inherits: false;
-}
-@property --tw-hue-rotate {
- syntax: "*";
- inherits: false;
-}
-@property --tw-invert {
- syntax: "*";
- inherits: false;
-}
-@property --tw-opacity {
- syntax: "*";
- inherits: false;
-}
-@property --tw-saturate {
- syntax: "*";
- inherits: false;
-}
-@property --tw-sepia {
- syntax: "*";
- inherits: false;
-}
-@property --tw-drop-shadow {
- syntax: "*";
- inherits: false;
-}
@property --tw-backdrop-blur {
syntax: "*";
inherits: false;
```
</details>
The reason for this is that `filter` was only used as a function call:
```tsx
src/app/docs/Code.tsx
31: .filter((x) => x !== null)
```
This was tested on all templates and they all remove a very small amount
of classes that aren't used.
The script to test this looks like this:
```sh
bun --bun ~/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.t -- -i ./src/styles/tailwind.css -o pr.css
bun --bun ~/github.com/tailwindlabs/tailwindcss--main/packages/@tailwindcss-cli/src/index.t -- -i ./src/styles/tailwind.css -o main.css
git diff --no-index --patch ./{main,pr}.css
```
This is using git worktrees, so the `pr` branch lives in a `tailwindcss`
folder, and the `main` branch lives in a `tailwindcss--main` folder.
---
### Fixes:
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15616
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16750
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16790
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16801
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16880 (due
to validating the arbitrary property)
---
### Ideas for in the future
1. Right now each machine takes in a `Cursor` object. One potential
improvement we can make is to rely on the `input` on its own instead of
going via the wrapping `Cursor` object.
2. If you take a look at the AST, you'll notice that utilities and
variants have a "root", these are basically prefixes of each available
utility and/or variant. We can use this information to filter out
candidates and bail out early if we know that a certain candidate will
never produce a valid class.
3. Passthrough the `prefix` information. Everything that doesn't start
with `tw:` can be skipped.
### Design decisions that didn't make it
Once you reach this part, you can stop reading if you want to, but this
is more like a brain dump of the things we tried and didn't work out.
Wanted to include them as a reference in case we want to look back at
this issue and know _why_ certain things are implemented the way they
are.
#### One character at a time
In an earlier implementation, the state machines were pure state
machines where the `next()` function was called on every single
character of the input. This had a lot of overhead because for every
character we had to:
1. Ask the `CandidateMachine` which state it was in.
2. Check the `cursor.curr` (and potentially the `cursor.prev` and
`cursor.next`) character.
3. If we were in a state where a nested state machine was running, we
had to check its current state as well and so on.
4. Once we did all of that we could go to the next character.
In this approach, the `MachineState` looked like this instead:
```rs
enum MachineState {
Idle,
Parsing,
Done(Span)
}
```
This had its own set of problems because now it's very hard to know
whether we are done or not.
```html
<div class="hover:flex"></div>
<!-- ^ -->
```
Let's look at the current position in the example above. At this point,
it's both a valid variant and valid utility, so there was a lot of
additional state we had to track to know whether we were done or not.
#### `Span` stitching
Another approach we tried was to just collect all valid variants and
utilities and throw them in a big `Vec<Span>`. This reduced the amount
of additional state to track and we could track a span the moment we saw
a `MachineState::Done(span)`.
The next thing we had to do was to make sure that:
1. Covered spans were removed. We still do this part in the current
implementation.
2. Combine all touching variant spans (where `span_a.end + 1 ==
span_b.start`).
3. For every combined variant span, find a corresponding utility span.
- If there is no utility span, the candidate is invalid.
- If there are multiple candidate spans (this is in theory not possible
because we dropped covered spans)
- If there is a candidate _but_ it is attached to another set of spans,
then the candidate is invalid. E.g.: `flex!block`
4. All left-over utility spans are candidates without variants.
This approach was slow, and still a bit hard to reason about.
#### Matching on tuples
While matching against the `prev`, `curr` and `next` characters was very
readable and easy to reason about. It was not very fast. Unfortunately
had to abandon this approach in favor of a more optimized approach.
In a perfect world, we would still write it this way, but have some
compile time macro that would optimize this for us.
#### Matching against `b'…'` instead of classification and jump tables
Similar to the previous point, while this is better for readability,
it's not fast enough. The jump tables are much faster.
Luckily for us, each machine has it's own set of rules and context, so
it's much easier to reason about a single problem and optimize a single
machine.
[^candidate]: A candidate is what a potential Tailwind CSS class _could_
be. It's a candidate because at this stage we don't know if it will
actually produce something but it looks like it could be a valid class.
E.g.: `hover:bg-red-500` is a candidate, but it will only produce
something if `--color-red-500` is defined in your theme.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
|
||
|
|
08972f294f
|
Scan Next.js dynamic route segments with manual @source rules (#16457)
Part of #16287 ## Test plan Added unit and integration tests --------- Co-authored-by: Robin Malfait <malfait.robin@gmail.com> |
||
|
|
d684733d80
|
Only expose used CSS variables (#16211)
This PR only exposes used CSS variables.
My initial approach was to track the used variables, this was a bit
messy because it meant that we had to walk part of the AST(s) in
multiple places. We also had to be careful because sometimes if a
variable exists in an AST, that doesn't mean that it's actually used.
E.g.:
```css
h1 {
color: var(--color-red-500); /* Definitely used, so let's keep it */
}
@utility foo {
color: var(--color-blue-500); /* Hmm, used? */
}
```
In this last case, the `--color-blue-500` is part of the CSS AST, but as
long as `foo` the utility is not used, it won't end up in your actual
CSS file, therefore the variable is **not** used.
Alternatively, if the `foo` utility is used with an invalid variant
(e.g.: `group-[>.foo]:foo`, then the `@utility foo` code will still run
internally because variants are applied on top of the utility. This
means that it looks like `var(--color-blue-500)` is being used.
Another annoying side effect was that because variables are
conditionally generated, that the `@theme` -> `:root, :host` conversion
had to happen for every build, instead of once in the `compile(…)` step.
---
To prevent all the messy rules and additional booking while walking of
ASTs I thought about a different approach. We are only interested in
variables that are actually used. The only way we know for sure, is
right before the `toCss(…)` step. Any step before that could still throw
away AST nodes.
However, we do have an `optimizeAst` step right before printing to
simplify and optimize the AST. So the idea was to keep all the CSS
variables in the AST, and only in the `optimizeAst` step we perform a
kind of mark-and-sweep algorithm where we can first check which
variables are _actually_ used (these are the ones that are left in the
AST), and later we removed the ones that weren't part of known used
list.
Moving the logic to this step feels a natural spot for this to happen,
because we are in fact optimizing the AST. We were already walking the
AST, so we can just handle these cases while we are walking without
additional walks. Last but not least, this also means that there is only
a single spot where need to track and remove variables.
Now, there is a different part to this story. If you use a variable in
JS land for example, we also want to make sure that we keep the CSS
variable in the CSS. To do this, we can mark variables as being used in
the internal `Theme`.
The Oxide scanner will also emit used variables that it can find such as
`var(--color-red-500)` and will emit `--color-red-500` as a "candidate".
We can then proactively mark this one as used even though it may not be
used anyway in the actual AST.
---
### Always including all variables
Some users might make heavy use of JavaScript and string interpolation
where they _need_ all the variables to be present. Similar to the
`inline` and `reference` theme options, this also exposes a new `static`
option. This ensures that all the CSS variables will always be generated
regardless of whether it's used or not.
One handy feature is that you have granular control over this:
```css
/* These will always be generated */
@theme static {
--color-primary: red;
--color-secondary: blue;
}
/* Only generated when used */
@theme {
--color-maybe: pink;
}
```
### Performance considerations:
Now that we are tracking which variables are being used, it means that
we will produce a smaller CSS file, but we are also doing more work (the
mark-and-sweep part). That said, ran some benchmarks and the changes
look like this:
Running it on Catalyst:
<img width="1086" alt="image"
src="https://github.com/user-attachments/assets/ec2124f0-2e64-4a11-aa5e-5f7ae6605962"
/>
_(probably within margin of error)_
Running it on Tailwind UI:
<img width="1113" alt="image"
src="https://github.com/user-attachments/assets/6bea2328-d790-4f33-a0ae-72654c688edb"
/>
### Test plan
- Tests have been updated with the removed CSS variables
- Added a dedicated integration test to show that Oxide can find
variables and mark them as used (so they are included)
- Ran the code on Catalyst, and verified that all the removed variables
are in fact not used anywhere in the codebase.
The diff on Catalyst looks like this:
<details>
```diff
diff --git a/templates/catalyst/out.css b/templates/catalyst/out.css
index f2b364ea..240d1d90 100644
--- a/templates/catalyst/out.css
+++ b/templates/catalyst/out.css
@@ -29,218 +29,111 @@
@layer theme {
:root, :host {
--font-sans: Inter, sans-serif;
- --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
- --color-red-50: oklch(0.971 0.013 17.38);
- --color-red-100: oklch(0.936 0.032 17.717);
--color-red-200: oklch(0.885 0.062 18.334);
--color-red-300: oklch(0.808 0.114 19.571);
--color-red-400: oklch(0.704 0.191 22.216);
--color-red-500: oklch(0.637 0.237 25.331);
--color-red-600: oklch(0.577 0.245 27.325);
--color-red-700: oklch(0.505 0.213 27.518);
- --color-red-800: oklch(0.444 0.177 26.899);
--color-red-900: oklch(0.396 0.141 25.723);
- --color-red-950: oklch(0.258 0.092 26.042);
- --color-orange-50: oklch(0.98 0.016 73.684);
- --color-orange-100: oklch(0.954 0.038 75.164);
--color-orange-200: oklch(0.901 0.076 70.697);
--color-orange-300: oklch(0.837 0.128 66.29);
--color-orange-400: oklch(0.75 0.183 55.934);
--color-orange-500: oklch(0.705 0.213 47.604);
--color-orange-600: oklch(0.646 0.222 41.116);
--color-orange-700: oklch(0.553 0.195 38.402);
- --color-orange-800: oklch(0.47 0.157 37.304);
--color-orange-900: oklch(0.408 0.123 38.172);
- --color-orange-950: oklch(0.266 0.079 36.259);
- --color-amber-50: oklch(0.987 0.022 95.277);
- --color-amber-100: oklch(0.962 0.059 95.617);
- --color-amber-200: oklch(0.924 0.12 95.746);
- --color-amber-300: oklch(0.879 0.169 91.605);
--color-amber-400: oklch(0.828 0.189 84.429);
--color-amber-500: oklch(0.769 0.188 70.08);
--color-amber-600: oklch(0.666 0.179 58.318);
--color-amber-700: oklch(0.555 0.163 48.998);
- --color-amber-800: oklch(0.473 0.137 46.201);
- --color-amber-900: oklch(0.414 0.112 45.904);
--color-amber-950: oklch(0.279 0.077 45.635);
- --color-yellow-50: oklch(0.987 0.026 102.212);
- --color-yellow-100: oklch(0.973 0.071 103.193);
- --color-yellow-200: oklch(0.945 0.129 101.54);
--color-yellow-300: oklch(0.905 0.182 98.111);
--color-yellow-400: oklch(0.852 0.199 91.936);
- --color-yellow-500: oklch(0.795 0.184 86.047);
--color-yellow-600: oklch(0.681 0.162 75.834);
--color-yellow-700: oklch(0.554 0.135 66.442);
- --color-yellow-800: oklch(0.476 0.114 61.907);
- --color-yellow-900: oklch(0.421 0.095 57.708);
--color-yellow-950: oklch(0.286 0.066 53.813);
- --color-lime-50: oklch(0.986 0.031 120.757);
- --color-lime-100: oklch(0.967 0.067 122.328);
- --color-lime-200: oklch(0.938 0.127 124.321);
--color-lime-300: oklch(0.897 0.196 126.665);
--color-lime-400: oklch(0.841 0.238 128.85);
- --color-lime-500: oklch(0.768 0.233 130.85);
--color-lime-600: oklch(0.648 0.2 131.684);
--color-lime-700: oklch(0.532 0.157 131.589);
- --color-lime-800: oklch(0.453 0.124 130.933);
- --color-lime-900: oklch(0.405 0.101 131.063);
--color-lime-950: oklch(0.274 0.072 132.109);
- --color-green-50: oklch(0.982 0.018 155.826);
- --color-green-100: oklch(0.962 0.044 156.743);
- --color-green-200: oklch(0.925 0.084 155.995);
- --color-green-300: oklch(0.871 0.15 154.449);
--color-green-400: oklch(0.792 0.209 151.711);
--color-green-500: oklch(0.723 0.219 149.579);
--color-green-600: oklch(0.627 0.194 149.214);
--color-green-700: oklch(0.527 0.154 150.069);
- --color-green-800: oklch(0.448 0.119 151.328);
--color-green-900: oklch(0.393 0.095 152.535);
- --color-green-950: oklch(0.266 0.065 152.934);
- --color-emerald-50: oklch(0.979 0.021 166.113);
- --color-emerald-100: oklch(0.95 0.052 163.051);
- --color-emerald-200: oklch(0.905 0.093 164.15);
- --color-emerald-300: oklch(0.845 0.143 164.978);
--color-emerald-400: oklch(0.765 0.177 163.223);
--color-emerald-500: oklch(0.696 0.17 162.48);
--color-emerald-600: oklch(0.596 0.145 163.225);
--color-emerald-700: oklch(0.508 0.118 165.612);
- --color-emerald-800: oklch(0.432 0.095 166.913);
--color-emerald-900: oklch(0.378 0.077 168.94);
- --color-emerald-950: oklch(0.262 0.051 172.552);
- --color-teal-50: oklch(0.984 0.014 180.72);
- --color-teal-100: oklch(0.953 0.051 180.801);
- --color-teal-200: oklch(0.91 0.096 180.426);
--color-teal-300: oklch(0.855 0.138 181.071);
--color-teal-400: oklch(0.777 0.152 181.912);
--color-teal-500: oklch(0.704 0.14 182.503);
--color-teal-600: oklch(0.6 0.118 184.704);
--color-teal-700: oklch(0.511 0.096 186.391);
- --color-teal-800: oklch(0.437 0.078 188.216);
--color-teal-900: oklch(0.386 0.063 188.416);
- --color-teal-950: oklch(0.277 0.046 192.524);
- --color-cyan-50: oklch(0.984 0.019 200.873);
- --color-cyan-100: oklch(0.956 0.045 203.388);
- --color-cyan-200: oklch(0.917 0.08 205.041);
--color-cyan-300: oklch(0.865 0.127 207.078);
--color-cyan-400: oklch(0.789 0.154 211.53);
--color-cyan-500: oklch(0.715 0.143 215.221);
- --color-cyan-600: oklch(0.609 0.126 221.723);
--color-cyan-700: oklch(0.52 0.105 223.128);
- --color-cyan-800: oklch(0.45 0.085 224.283);
- --color-cyan-900: oklch(0.398 0.07 227.392);
--color-cyan-950: oklch(0.302 0.056 229.695);
- --color-sky-50: oklch(0.977 0.013 236.62);
- --color-sky-100: oklch(0.951 0.026 236.824);
- --color-sky-200: oklch(0.901 0.058 230.902);
--color-sky-300: oklch(0.828 0.111 230.318);
- --color-sky-400: oklch(0.746 0.16 232.661);
--color-sky-500: oklch(0.685 0.169 237.323);
--color-sky-600: oklch(0.588 0.158 241.966);
--color-sky-700: oklch(0.5 0.134 242.749);
- --color-sky-800: oklch(0.443 0.11 240.79);
--color-sky-900: oklch(0.391 0.09 240.876);
- --color-sky-950: oklch(0.293 0.066 243.157);
- --color-blue-50: oklch(0.97 0.014 254.604);
- --color-blue-100: oklch(0.932 0.032 255.585);
- --color-blue-200: oklch(0.882 0.059 254.128);
--color-blue-300: oklch(0.809 0.105 251.813);
--color-blue-400: oklch(0.707 0.165 254.624);
--color-blue-500: oklch(0.623 0.214 259.815);
--color-blue-600: oklch(0.546 0.245 262.881);
--color-blue-700: oklch(0.488 0.243 264.376);
- --color-blue-800: oklch(0.424 0.199 265.638);
--color-blue-900: oklch(0.379 0.146 265.522);
- --color-blue-950: oklch(0.282 0.091 267.935);
- --color-indigo-50: oklch(0.962 0.018 272.314);
- --color-indigo-100: oklch(0.93 0.034 272.788);
--color-indigo-200: oklch(0.87 0.065 274.039);
--color-indigo-300: oklch(0.785 0.115 274.713);
--color-indigo-400: oklch(0.673 0.182 276.935);
--color-indigo-500: oklch(0.585 0.233 277.117);
--color-indigo-600: oklch(0.511 0.262 276.966);
--color-indigo-700: oklch(0.457 0.24 277.023);
- --color-indigo-800: oklch(0.398 0.195 277.366);
--color-indigo-900: oklch(0.359 0.144 278.697);
- --color-indigo-950: oklch(0.257 0.09 281.288);
- --color-violet-50: oklch(0.969 0.016 293.756);
- --color-violet-100: oklch(0.943 0.029 294.588);
--color-violet-200: oklch(0.894 0.057 293.283);
--color-violet-300: oklch(0.811 0.111 293.571);
--color-violet-400: oklch(0.702 0.183 293.541);
--color-violet-500: oklch(0.606 0.25 292.717);
--color-violet-600: oklch(0.541 0.281 293.009);
--color-violet-700: oklch(0.491 0.27 292.581);
- --color-violet-800: oklch(0.432 0.232 292.759);
--color-violet-900: oklch(0.38 0.189 293.745);
- --color-violet-950: oklch(0.283 0.141 291.089);
- --color-purple-50: oklch(0.977 0.014 308.299);
- --color-purple-100: oklch(0.946 0.033 307.174);
--color-purple-200: oklch(0.902 0.063 306.703);
--color-purple-300: oklch(0.827 0.119 306.383);
--color-purple-400: oklch(0.714 0.203 305.504);
--color-purple-500: oklch(0.627 0.265 303.9);
--color-purple-600: oklch(0.558 0.288 302.321);
--color-purple-700: oklch(0.496 0.265 301.924);
- --color-purple-800: oklch(0.438 0.218 303.724);
--color-purple-900: oklch(0.381 0.176 304.987);
- --color-purple-950: oklch(0.291 0.149 302.717);
- --color-fuchsia-50: oklch(0.977 0.017 320.058);
- --color-fuchsia-100: oklch(0.952 0.037 318.852);
--color-fuchsia-200: oklch(0.903 0.076 319.62);
--color-fuchsia-300: oklch(0.833 0.145 321.434);
--color-fuchsia-400: oklch(0.74 0.238 322.16);
--color-fuchsia-500: oklch(0.667 0.295 322.15);
--color-fuchsia-600: oklch(0.591 0.293 322.896);
--color-fuchsia-700: oklch(0.518 0.253 323.949);
- --color-fuchsia-800: oklch(0.452 0.211 324.591);
--color-fuchsia-900: oklch(0.401 0.17 325.612);
- --color-fuchsia-950: oklch(0.293 0.136 325.661);
- --color-pink-50: oklch(0.971 0.014 343.198);
- --color-pink-100: oklch(0.948 0.028 342.258);
--color-pink-200: oklch(0.899 0.061 343.231);
--color-pink-300: oklch(0.823 0.12 346.018);
--color-pink-400: oklch(0.718 0.202 349.761);
--color-pink-500: oklch(0.656 0.241 354.308);
--color-pink-600: oklch(0.592 0.249 0.584);
--color-pink-700: oklch(0.525 0.223 3.958);
- --color-pink-800: oklch(0.459 0.187 3.815);
--color-pink-900: oklch(0.408 0.153 2.432);
- --color-pink-950: oklch(0.284 0.109 3.907);
- --color-rose-50: oklch(0.969 0.015 12.422);
- --color-rose-100: oklch(0.941 0.03 12.58);
--color-rose-200: oklch(0.892 0.058 10.001);
--color-rose-300: oklch(0.81 0.117 11.638);
--color-rose-400: oklch(0.712 0.194 13.428);
--color-rose-500: oklch(0.645 0.246 16.439);
--color-rose-600: oklch(0.586 0.253 17.585);
--color-rose-700: oklch(0.514 0.222 16.935);
- --color-rose-800: oklch(0.455 0.188 13.697);
--color-rose-900: oklch(0.41 0.159 10.272);
- --color-rose-950: oklch(0.271 0.105 12.094);
- --color-slate-50: oklch(0.984 0.003 247.858);
- --color-slate-100: oklch(0.968 0.007 247.896);
- --color-slate-200: oklch(0.929 0.013 255.508);
- --color-slate-300: oklch(0.869 0.022 252.894);
- --color-slate-400: oklch(0.704 0.04 256.788);
- --color-slate-500: oklch(0.554 0.046 257.417);
- --color-slate-600: oklch(0.446 0.043 257.281);
- --color-slate-700: oklch(0.372 0.044 257.287);
- --color-slate-800: oklch(0.279 0.041 260.031);
- --color-slate-900: oklch(0.208 0.042 265.755);
- --color-slate-950: oklch(0.129 0.042 264.695);
- --color-gray-50: oklch(0.985 0.002 247.839);
- --color-gray-100: oklch(0.967 0.003 264.542);
- --color-gray-200: oklch(0.928 0.006 264.531);
- --color-gray-300: oklch(0.872 0.01 258.338);
- --color-gray-400: oklch(0.707 0.022 261.325);
- --color-gray-500: oklch(0.551 0.027 264.364);
- --color-gray-600: oklch(0.446 0.03 256.802);
- --color-gray-700: oklch(0.373 0.034 259.733);
- --color-gray-800: oklch(0.278 0.033 256.848);
- --color-gray-900: oklch(0.21 0.034 264.665);
- --color-gray-950: oklch(0.13 0.028 261.692);
--color-zinc-50: oklch(0.985 0 0);
--color-zinc-100: oklch(0.967 0.001 286.375);
--color-zinc-200: oklch(0.92 0.004 286.32);
@@ -252,38 +145,9 @@
--color-zinc-800: oklch(0.274 0.006 286.033);
--color-zinc-900: oklch(0.21 0.006 285.885);
--color-zinc-950: oklch(0.141 0.005 285.823);
- --color-neutral-50: oklch(0.985 0 0);
- --color-neutral-100: oklch(0.97 0 0);
- --color-neutral-200: oklch(0.922 0 0);
- --color-neutral-300: oklch(0.87 0 0);
- --color-neutral-400: oklch(0.708 0 0);
- --color-neutral-500: oklch(0.556 0 0);
- --color-neutral-600: oklch(0.439 0 0);
- --color-neutral-700: oklch(0.371 0 0);
- --color-neutral-800: oklch(0.269 0 0);
- --color-neutral-900: oklch(0.205 0 0);
- --color-neutral-950: oklch(0.145 0 0);
- --color-stone-50: oklch(0.985 0.001 106.423);
- --color-stone-100: oklch(0.97 0.001 106.424);
- --color-stone-200: oklch(0.923 0.003 48.717);
- --color-stone-300: oklch(0.869 0.005 56.366);
- --color-stone-400: oklch(0.709 0.01 56.259);
- --color-stone-500: oklch(0.553 0.013 58.071);
- --color-stone-600: oklch(0.444 0.011 73.639);
- --color-stone-700: oklch(0.374 0.01 67.558);
- --color-stone-800: oklch(0.268 0.007 34.298);
- --color-stone-900: oklch(0.216 0.006 56.043);
- --color-stone-950: oklch(0.147 0.004 49.25);
--color-black: #000;
--color-white: #fff;
--spacing: 0.25rem;
- --breakpoint-sm: 40rem;
- --breakpoint-md: 48rem;
- --breakpoint-lg: 64rem;
- --breakpoint-xl: 80rem;
- --breakpoint-2xl: 96rem;
- --container-3xs: 16rem;
- --container-2xs: 18rem;
--container-xs: 20rem;
--container-sm: 24rem;
--container-md: 28rem;
@@ -302,92 +166,23 @@
--text-base: 1rem;
--text-base--line-height: calc(1.5 / 1);
--text-lg: 1.125rem;
- --text-lg--line-height: calc(1.75 / 1.125);
--text-xl: 1.25rem;
- --text-xl--line-height: calc(1.75 / 1.25);
--text-2xl: 1.5rem;
- --text-2xl--line-height: calc(2 / 1.5);
- --text-3xl: 1.875rem;
- --text-3xl--line-height: calc(2.25 / 1.875);
- --text-4xl: 2.25rem;
- --text-4xl--line-height: calc(2.5 / 2.25);
- --text-5xl: 3rem;
- --text-5xl--line-height: 1;
- --text-6xl: 3.75rem;
- --text-6xl--line-height: 1;
- --text-7xl: 4.5rem;
- --text-7xl--line-height: 1;
- --text-8xl: 6rem;
- --text-8xl--line-height: 1;
- --text-9xl: 8rem;
- --text-9xl--line-height: 1;
- --font-weight-thin: 100;
- --font-weight-extralight: 200;
- --font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
- --font-weight-extrabold: 800;
- --font-weight-black: 900;
- --tracking-tighter: -0.05em;
- --tracking-tight: -0.025em;
- --tracking-normal: 0em;
- --tracking-wide: 0.025em;
- --tracking-wider: 0.05em;
- --tracking-widest: 0.1em;
- --leading-tight: 1.25;
- --leading-snug: 1.375;
- --leading-normal: 1.5;
- --leading-relaxed: 1.625;
- --leading-loose: 2;
- --radius-xs: 0.125rem;
--radius-sm: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--radius-xl: 0.75rem;
--radius-2xl: 1rem;
--radius-3xl: 1.5rem;
- --radius-4xl: 2rem;
- --shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
- --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
- --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
- --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
- 0 2px 4px -2px rgb(0 0 0 / 0.1);
- --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
- --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
- 0 8px 10px -6px rgb(0 0 0 / 0.1);
- --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
- --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
- --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
- --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
- --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
- --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
- --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
- --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
- --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
- --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
- --animate-spin: spin 1s linear infinite;
- --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
- --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
- --animate-bounce: bounce 1s infinite;
- --blur-xs: 4px;
- --blur-sm: 8px;
--blur-md: 12px;
- --blur-lg: 16px;
--blur-xl: 24px;
- --blur-2xl: 40px;
- --blur-3xl: 64px;
- --perspective-dramatic: 100px;
- --perspective-near: 300px;
- --perspective-normal: 500px;
- --perspective-midrange: 800px;
- --perspective-distant: 1200px;
- --aspect-video: 16 / 9;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
```
</details>
If you have `ripgrep` installed, you can use this command to verify that
these variables are indeed not used anywhere:
<details>
```shell
rg "\-\-font-serif\b"
rg "\-\-color-red-50\b"
rg "\-\-color-red-100\b"
rg "\-\-color-red-800\b"
rg "\-\-color-red-950\b"
rg "\-\-color-orange-50\b"
rg "\-\-color-orange-100\b"
rg "\-\-color-orange-800\b"
rg "\-\-color-orange-950\b"
rg "\-\-color-amber-50\b"
rg "\-\-color-amber-100\b"
rg "\-\-color-amber-200\b"
rg "\-\-color-amber-300\b"
rg "\-\-color-amber-800\b"
rg "\-\-color-amber-900\b"
rg "\-\-color-yellow-50\b"
rg "\-\-color-yellow-100\b"
rg "\-\-color-yellow-200\b"
rg "\-\-color-yellow-500\b"
rg "\-\-color-yellow-800\b"
rg "\-\-color-yellow-900\b"
rg "\-\-color-lime-50\b"
rg "\-\-color-lime-100\b"
rg "\-\-color-lime-200\b"
rg "\-\-color-lime-500\b"
rg "\-\-color-lime-800\b"
rg "\-\-color-lime-900\b"
rg "\-\-color-green-50\b"
rg "\-\-color-green-100\b"
rg "\-\-color-green-200\b"
rg "\-\-color-green-300\b"
rg "\-\-color-green-800\b"
rg "\-\-color-green-950\b"
rg "\-\-color-emerald-50\b"
rg "\-\-color-emerald-100\b"
rg "\-\-color-emerald-200\b"
rg "\-\-color-emerald-300\b"
rg "\-\-color-emerald-800\b"
rg "\-\-color-emerald-950\b"
rg "\-\-color-teal-50\b"
rg "\-\-color-teal-100\b"
rg "\-\-color-teal-200\b"
rg "\-\-color-teal-800\b"
rg "\-\-color-teal-950\b"
rg "\-\-color-cyan-50\b"
rg "\-\-color-cyan-100\b"
rg "\-\-color-cyan-200\b"
rg "\-\-color-cyan-600\b"
rg "\-\-color-cyan-800\b"
rg "\-\-color-cyan-900\b"
rg "\-\-color-sky-50\b"
rg "\-\-color-sky-100\b"
rg "\-\-color-sky-200\b"
rg "\-\-color-sky-400\b"
rg "\-\-color-sky-800\b"
rg "\-\-color-sky-950\b"
rg "\-\-color-blue-50\b"
rg "\-\-color-blue-100\b"
rg "\-\-color-blue-200\b"
rg "\-\-color-blue-800\b"
rg "\-\-color-blue-950\b"
rg "\-\-color-indigo-50\b"
rg "\-\-color-indigo-100\b"
rg "\-\-color-indigo-800\b"
rg "\-\-color-indigo-950\b"
rg "\-\-color-violet-50\b"
rg "\-\-color-violet-100\b"
rg "\-\-color-violet-800\b"
rg "\-\-color-violet-950\b"
rg "\-\-color-purple-50\b"
rg "\-\-color-purple-100\b"
rg "\-\-color-purple-800\b"
rg "\-\-color-purple-950\b"
rg "\-\-color-fuchsia-50\b"
rg "\-\-color-fuchsia-100\b"
rg "\-\-color-fuchsia-800\b"
rg "\-\-color-fuchsia-950\b"
rg "\-\-color-pink-50\b"
rg "\-\-color-pink-100\b"
rg "\-\-color-pink-800\b"
rg "\-\-color-pink-950\b"
rg "\-\-color-rose-50\b"
rg "\-\-color-rose-100\b"
rg "\-\-color-rose-800\b"
rg "\-\-color-rose-950\b"
rg "\-\-color-slate-50\b"
rg "\-\-color-slate-100\b"
rg "\-\-color-slate-200\b"
rg "\-\-color-slate-300\b"
rg "\-\-color-slate-400\b"
rg "\-\-color-slate-500\b"
rg "\-\-color-slate-600\b"
rg "\-\-color-slate-700\b"
rg "\-\-color-slate-800\b"
rg "\-\-color-slate-900\b"
rg "\-\-color-slate-950\b"
rg "\-\-color-gray-50\b"
rg "\-\-color-gray-100\b"
rg "\-\-color-gray-200\b"
rg "\-\-color-gray-300\b"
rg "\-\-color-gray-400\b"
rg "\-\-color-gray-500\b"
rg "\-\-color-gray-600\b"
rg "\-\-color-gray-700\b"
rg "\-\-color-gray-800\b"
rg "\-\-color-gray-900\b"
rg "\-\-color-gray-950\b"
rg "\-\-color-neutral-50\b"
rg "\-\-color-neutral-100\b"
rg "\-\-color-neutral-200\b"
rg "\-\-color-neutral-300\b"
rg "\-\-color-neutral-400\b"
rg "\-\-color-neutral-500\b"
rg "\-\-color-neutral-600\b"
rg "\-\-color-neutral-700\b"
rg "\-\-color-neutral-800\b"
rg "\-\-color-neutral-900\b"
rg "\-\-color-neutral-950\b"
rg "\-\-color-stone-50\b"
rg "\-\-color-stone-100\b"
rg "\-\-color-stone-200\b"
rg "\-\-color-stone-300\b"
rg "\-\-color-stone-400\b"
rg "\-\-color-stone-500\b"
rg "\-\-color-stone-600\b"
rg "\-\-color-stone-700\b"
rg "\-\-color-stone-800\b"
rg "\-\-color-stone-900\b"
rg "\-\-color-stone-950\b"
rg "\-\-breakpoint-sm\b"
rg "\-\-breakpoint-md\b"
rg "\-\-breakpoint-lg\b"
rg "\-\-breakpoint-xl\b"
rg "\-\-breakpoint-2xl\b"
rg "\-\-container-3xs\b"
rg "\-\-container-2xs\b"
rg "\-\-text-lg--line-height\b"
rg "\-\-text-xl--line-height\b"
rg "\-\-text-2xl--line-height\b"
rg "\-\-text-3xl\b"
rg "\-\-text-3xl--line-height\b"
rg "\-\-text-4xl\b"
rg "\-\-text-4xl--line-height\b"
rg "\-\-text-5xl\b"
rg "\-\-text-5xl--line-height\b"
rg "\-\-text-6xl\b"
rg "\-\-text-6xl--line-height\b"
rg "\-\-text-7xl\b"
rg "\-\-text-7xl--line-height\b"
rg "\-\-text-8xl\b"
rg "\-\-text-8xl--line-height\b"
rg "\-\-text-9xl\b"
rg "\-\-text-9xl--line-height\b"
rg "\-\-font-weight-thin\b"
rg "\-\-font-weight-extralight\b"
rg "\-\-font-weight-light\b"
rg "\-\-font-weight-extrabold\b"
rg "\-\-font-weight-black\b"
rg "\-\-tracking-tighter\b"
rg "\-\-tracking-tight\b"
rg "\-\-tracking-normal\b"
rg "\-\-tracking-wide\b"
rg "\-\-tracking-wider\b"
rg "\-\-tracking-widest\b"
rg "\-\-leading-tight\b"
rg "\-\-leading-snug\b"
rg "\-\-leading-normal\b"
rg "\-\-leading-relaxed\b"
rg "\-\-leading-loose\b"
rg "\-\-radius-xs\b"
rg "\-\-radius-4xl\b"
rg "\-\-shadow-2xs\b"
rg "\-\-shadow-xs\b"
rg "\-\-shadow-sm\b"
rg "\-\-shadow-md\b"
rg "\-\-shadow-lg\b"
rg "\-\-shadow-xl\b"
rg "\-\-shadow-2xl\b"
rg "\-\-inset-shadow-2xs\b"
rg "\-\-inset-shadow-xs\b"
rg "\-\-inset-shadow-sm\b"
rg "\-\-drop-shadow-xs\b"
rg "\-\-drop-shadow-sm\b"
rg "\-\-drop-shadow-md\b"
rg "\-\-drop-shadow-lg\b"
rg "\-\-drop-shadow-xl\b"
rg "\-\-drop-shadow-2xl\b"
rg "\-\-animate-spin\b"
rg "\-\-animate-ping\b"
rg "\-\-animate-pulse\b"
rg "\-\-animate-bounce\b"
rg "\-\-blur-xs\b"
rg "\-\-blur-sm\b"
rg "\-\-blur-lg\b"
rg "\-\-blur-2xl\b"
rg "\-\-blur-3xl\b"
rg "\-\-perspective-dramatic\b"
rg "\-\-perspective-near\b"
rg "\-\-perspective-normal\b"
rg "\-\-perspective-midrange\b"
rg "\-\-perspective-distant\b"
rg "\-\-aspect-video\b"
```
</details>
The only exception I noticed is that we have this:
```css
src/typography.utilities.css
10: @media (width >= theme(--breakpoint-sm)) {
```
But this is not a variable, but it's replaced at build time with the
actual value, so this is not a real issue.
Testing on other templates:
<img width="2968" alt="image"
src="https://github.com/user-attachments/assets/cabf121d-4cb9-468f-9cf5-ceb02609dc7d"
/>
Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16145
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
|
||
|
|
9bfeb337e2
|
Fix resolve_globs crash when at root directory (#15988)
Fixes a crash found in Dockerfiles when a build takes place at the root directory. It's not a good practice to keep your application logic in `/`, but it probably shouldn't cause a crash either. I found this particularly difficult to write tests for because it would involve either running a glob on my real filesystem starting from `/` or mocking the calls to `fs` which as far as I can tell isn't supported in the codebase and would be out of scope to try to do here. Fixes #15987 |