feat(formatter): add TailwindCSS support for JS/TS files#16990
Conversation
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
CodSpeed Performance ReportMerging #16990 will not alter performanceComparing Summary
Footnotes
|
|
Not quite a review, but here are some notes on what I noticed while looking through... 🚶🏻
|
1e49e6c to
eee8f18
Compare
I don't think this is a problem. We can likely load the original
I've noticed the same thing; the absence of
Yes, I started to test in real codebases |
|
I always found the official plugin order too lax and inconsistent; personally, I prefer https://github.com/schoero/eslint-plugin-better-tailwindcss |
b0d82c9 to
7fd5842
Compare
0d4ab65 to
c6b7cc1
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds experimental Tailwind CSS class sorting support to Oxfmt by integrating with prettier-plugin-tailwindcss. The implementation uses a patch-based approach to export necessary functions from the plugin while awaiting official integration support.
Key changes:
- Rust-side detection for Tailwind contexts (JSX attributes, function calls, template literals) with optimized context tracking
- External callback system for JS-side class sorting via prettier-plugin-tailwindcss
- Comprehensive configuration options matching prettier-plugin-tailwindcss (with
tailwindprefix removed)
Reviewed changes
Copilot reviewed 36 out of 39 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
crates/oxc_formatter/src/utils/tailwindcss.rs |
Core Tailwind detection and write utilities for JSX attributes, functions, and template literals |
crates/oxc_formatter/src/formatter/context.rs |
TailwindContextEntry stack for tracking Tailwind contexts during traversal |
crates/oxc_formatter/src/external_formatter.rs |
Renamed from embedded_formatter, now handles both embedded formatting and Tailwind sorting |
crates/oxc_formatter/src/formatter/format_element/mod.rs |
New FormatElement::TailwindClass variant for deferred class sorting |
crates/oxc_formatter/src/formatter/printer/mod.rs |
Printer updated to resolve TailwindClass indices to sorted strings |
crates/oxc_formatter/src/write/* |
JSXAttribute, CallExpression, StringLiteral, and TemplateElement formatting with Tailwind context awareness |
apps/oxfmt/src-js/libs/prettier.ts |
sortTailwindClasses function wrapping prettier-plugin-tailwindcss |
apps/oxfmt/test/tailwindcss.test.ts |
Comprehensive test suite with 58 tests covering edge cases |
patches/prettier-plugin-tailwindcss@0.7.2.patch |
Patch to export getTailwindConfig and sortClasses functions |
crates/oxc_formatter/src/oxfmtrc.rs |
Configuration schema for TailwindcssConfig |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Oh, CI is failing. 😮 |
Not sure why the oxfmt tests took too long; I've fixed it by increasing the timeout duration. |
Merge activity
|
Based on #16826 ## Summary Add experimental Tailwind CSS class sorting support to Oxfmt via integration with `prettier-plugin-tailwindcss`. This implementation uses a patch-based approach to export necessary functions from `prettier-plugin-tailwindcss`. We've requested better integration support from the Tailwind team at tailwindlabs/prettier-plugin-tailwindcss#430, and can improve the integration when that's available. ## Features - ✅ Sorts Tailwind CSS classes in `className` and `class` attributes - ✅ Sorts classes in custom JSX attributes (configurable) - ✅ Sorts classes in function calls like `clsx()`, `cn()`, `tw()` (configurable) - ✅ Supports template literals with expressions - ✅ Supports Tailwind v3 config files and v4 stylesheets - ✅ Preserves whitespace and duplicates (configurable) - ✅ **Fixes [prettier-plugin-tailwindcss#426](tailwindlabs/prettier-plugin-tailwindcss#426 - Correctly handles strings in nested non-Tailwind call expressions - ⏳ Non-JS file support (HTML, Vue, Svelte, etc.) - Coming in follow-up PR - ⏳ Tailwind CSS config migration in `oxfmt --migrate=prettier` - Coming in follow-up PR ## Example Usage ### Basic Usage Enable Tailwind CSS sorting with default options by setting `experimentalTailwindcss` to an empty object in your `.oxfmtrc.json`: ```json { "experimentalTailwindcss": {} } ``` ### With Configuration Options ```json { "experimentalTailwindcss": { "config": "./tailwind.config.js", "functions": ["clsx", "cn", "cva", "tw"], "attributes": ["myCustomClass"], "preserveWhitespace": false, "preserveDuplicates": false } } ``` ### Configuration Options All options from `prettier-plugin-tailwindcss` are supported (with `tailwind` prefix removed): | Option | Type | Description | Default | |--------|------|-------------|---------| | `config` | `string` | Path to your Tailwind CSS v3 configuration file. Paths are resolved relative to the Oxfmt configuration file. | `"./tailwind.config.js"` | | `stylesheet` | `string` | Path to your Tailwind CSS v4 stylesheet. Paths are resolved relative to the Oxfmt configuration file. | `undefined` | | `functions` | `string[]` | List of custom function names that contain Tailwind CSS classes (e.g., `["clsx", "cn", "cva", "tw"]`). | `[]` | | `attributes` | `string[]` | List of custom JSX attributes that should be sorted (e.g., `["myCustomClass"]`). Note: `class` and `className` are always sorted by default. | `[]` | | `preserveWhitespace` | `boolean` | Preserve whitespace around classes. | `false` | | `preserveDuplicates` | `boolean` | Preserve duplicate classes. | `false` | ## Migration from prettier-plugin-tailwindcss If you're currently using `prettier-plugin-tailwindcss`, you can migrate to Oxfmt's built-in support: ### Manual Migration (Current) #### Before (Prettier) ```json { "plugins": ["prettier-plugin-tailwindcss"], "tailwindConfig": "./tailwind.config.js", "tailwindFunctions": ["clsx", "cn"] } ``` #### After (Oxfmt) ```json { "experimentalTailwindcss": { "config": "./tailwind.config.js", "functions": ["clsx", "cn"] } } ``` **Key differences:** - Remove `tailwind` prefix from all option names (e.g., `tailwindConfig` → `config`) - No need to install `prettier-plugin-tailwindcss` separately, it is bundled within Oxfmt ### Automated Migration (Follow-up PR) Tailwind CSS configuration migration will be added to the existing `oxfmt --migrate=prettier` command in a follow-up PR. This will automatically convert your `prettier-plugin-tailwindcss` settings from `.prettierrc` to `.oxfmtrc.json`. ## Implementation Details ### Technical Approach - Uses `pnpm patch` to export necessary functions from `prettier-plugin-tailwindcss` - Implements Rust-side detection for Tailwind contexts (JSX attributes, function calls, template literals) - Passes class strings to the JS plugin for sorting via external callback - Optimized context tracking to avoid repeated AST traversal ### Improvements Over prettier-plugin-tailwindcss - **Context-based quasi position tracking** ([tailwindcss.rs:273-276](https://github.com/oxc-project/oxc/blob/main/crates/oxc_formatter/src/utils/tailwindcss.rs#L273-L276)) - Stores template literal position in context stack instead of traversing AST, improving performance - **Fixes [issue #426](tailwindlabs/prettier-plugin-tailwindcss#426 - Correctly handles strings in nested non-Tailwind call expressions (e.g., `value.includes("\n")` no longer gets corrupted) - **Comprehensive edge case testing** - Added 58 tests covering edge cases, nested scenarios, error handling, and performance ## Changes - Changed `experimentalTailwindcss` option from `boolean` to `TailwindcssOptions` object - Added all configuration options from `prettier-plugin-tailwindcss` (without `tailwind` prefix) - Exported necessary functions from `prettier-plugin-tailwindcss` via patch - Added comprehensive test suite with 58 tests - Improved documentation for all configuration options ## Test Plan - [x] `pnpm test` passes (58 tests) - [x] `cargo clippy` passes - [x] `just fmt` passes - [x] Basic Tailwind class sorting works with `class` and `className` attributes - [x] `attributes` option sorts custom JSX attributes - [x] `functions` option sorts string arguments in function calls (e.g., `clsx()`, `cn()`) - [x] Template literals with expressions handle whitespace correctly - [x] Edge cases: empty strings, Unicode, emoji, special characters - [x] Nested scenarios: deeply nested template literals, function calls - [x] Strings in nested non-Tailwind calls are preserved (fixes [#426](tailwindlabs/prettier-plugin-tailwindcss#426)) - [x] Test in https://github.com/tailwindlabs/headlessui and https://github.com/dubinc/dub ## Follow-up Work - **Non-JS file support** - HTML, Vue, Svelte, etc. by enabling original plugin when `experimentalTailwindcss` is set - **Migration support** - Add Tailwind CSS config migration to existing `oxfmt --migrate=prettier` command - **Better integration** - Improve when tailwindlabs/prettier-plugin-tailwindcss#430 is resolved 🤖 Generated with [Claude Code](https://claude.com/claude-code)
283c61a to
26ed46b
Compare

Based on #16826
Summary
Add experimental Tailwind CSS class sorting support to Oxfmt via integration with
prettier-plugin-tailwindcss.This implementation uses a patch-based approach to export necessary functions from
prettier-plugin-tailwindcss. We've requested better integration support from the Tailwind team at tailwindlabs/prettier-plugin-tailwindcss#430, and can improve the integration when that's available.Features
classNameandclassattributesclsx(),cn(),tw()(configurable)oxfmt --migrate=prettier- Coming in follow-up PRExample Usage
Basic Usage
Enable Tailwind CSS sorting with default options by setting
experimentalTailwindcssto an empty object in your.oxfmtrc.json:{ "experimentalTailwindcss": {} }With Configuration Options
{ "experimentalTailwindcss": { "config": "./tailwind.config.js", "functions": ["clsx", "cn", "cva", "tw"], "attributes": ["myCustomClass"], "preserveWhitespace": false, "preserveDuplicates": false } }Configuration Options
All options from
prettier-plugin-tailwindcssare supported (withtailwindprefix removed):configstring"./tailwind.config.js"stylesheetstringundefinedfunctionsstring[]["clsx", "cn", "cva", "tw"]).[]attributesstring[]["myCustomClass"]). Note:classandclassNameare always sorted by default.[]preserveWhitespacebooleanfalsepreserveDuplicatesbooleanfalseMigration from prettier-plugin-tailwindcss
If you're currently using
prettier-plugin-tailwindcss, you can migrate to Oxfmt's built-in support:Manual Migration (Current)
Before (Prettier)
{ "plugins": ["prettier-plugin-tailwindcss"], "tailwindConfig": "./tailwind.config.js", "tailwindFunctions": ["clsx", "cn"] }After (Oxfmt)
{ "experimentalTailwindcss": { "config": "./tailwind.config.js", "functions": ["clsx", "cn"] } }Key differences:
tailwindprefix from all option names (e.g.,tailwindConfig→config)prettier-plugin-tailwindcssseparately, it is bundled within OxfmtAutomated Migration (Follow-up PR)
Tailwind CSS configuration migration will be added to the existing
oxfmt --migrate=prettiercommand in a follow-up PR. This will automatically convert yourprettier-plugin-tailwindcsssettings from.prettierrcto.oxfmtrc.json.Implementation Details
Technical Approach
pnpm patchto export necessary functions fromprettier-plugin-tailwindcssImprovements Over prettier-plugin-tailwindcss
value.includes("\n")no longer gets corrupted)Changes
experimentalTailwindcssoption frombooleantoTailwindcssOptionsobjectprettier-plugin-tailwindcss(withouttailwindprefix)prettier-plugin-tailwindcssvia patchTest Plan
pnpm testpasses (58 tests)cargo clippypassesjust fmtpassesclassandclassNameattributesattributesoption sorts custom JSX attributesfunctionsoption sorts string arguments in function calls (e.g.,clsx(),cn())Follow-up Work
experimentalTailwindcssis setoxfmt --migrate=prettiercommandgetTailwindConfigandsortClassesforOxfmtformatter integration tailwindlabs/prettier-plugin-tailwindcss#430 is resolved🤖 Generated with Claude Code