Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved monorepo support by loading Tailwind CSS relative to the input file instead of prettier config file ([#386](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/386))
- Improved monorepo support by loading v3 configs relative to the input file instead of prettier config file ([#386](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/386))
- Fix whitespace removal inside nested concat and template expressions ([#396](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/396))
- Fallback to Tailwind CSS v4 instead of v3 by default ([#390](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/390))

## [0.6.14] - 2025-07-09

Expand Down
4 changes: 2 additions & 2 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ function patchCjsInterop() {
let code = [
`import {createRequire as __global__createRequire__} from 'module'`,
`import {dirname as __global__dirname__} from 'path'`,
`import {fileURLToPath} from 'url'`,
`import {fileURLToPath as __global__fileURLToPath__} from 'url'`,

// CJS interop fixes
`const require=__global__createRequire__(import.meta.url)`,
`const __filename=fileURLToPath(import.meta.url)`,
`const __filename=__global__fileURLToPath__(import.meta.url)`,
`const __dirname=__global__dirname__(__filename)`,
]

Expand Down
8 changes: 0 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,6 @@ export async function getTailwindConfig(options: ParserOptions): Promise<any> {
stylesheet ??= `${pkgDir}/theme.css`
}

// No stylesheet was given or otherwise found in a local v4 installation
// nor was a tailwind config given or found.
//
// Fallback to v3
if (!stylesheet) {
return pathToApiMap.remember(null, () => loadV3(null, null))
}

return pathToApiMap.remember(`${pkgDir}:${stylesheet}`, () => loadV4(mod, stylesheet))
}

Expand Down
23 changes: 23 additions & 0 deletions src/versions/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-ignore
import index from 'tailwindcss-v4/index.css'
// @ts-ignore
import preflight from 'tailwindcss-v4/preflight.css'
// @ts-ignore
import theme from 'tailwindcss-v4/theme.css'
// @ts-ignore
import utilities from 'tailwindcss-v4/utilities.css'

export const assets: Record<string, string> = {
tailwindcss: index,
'tailwindcss/index': index,
'tailwindcss/index.css': index,

'tailwindcss/preflight': preflight,
'tailwindcss/preflight.css': preflight,

'tailwindcss/theme': theme,
'tailwindcss/theme.css': theme,

'tailwindcss/utilities': utilities,
'tailwindcss/utilities.css': utilities,
}
30 changes: 18 additions & 12 deletions src/versions/v4.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import { pathToFileURL } from 'node:url'
import { createJiti, type Jiti } from 'jiti'
import * as v4 from 'tailwindcss-v4'
import { resolveCssFrom, resolveJsFrom } from '../resolve'
import type { UnifiedApi } from '../types'
import { assets } from './assets'

interface DesignSystem {
getClassOrder(classList: string[]): [string, bigint | null][]
Expand Down Expand Up @@ -40,11 +41,10 @@ interface ApiV4 {

export async function loadV4(mod: ApiV4 | null, stylesheet: string | null): Promise<UnifiedApi> {
// This is not Tailwind v4
let isFallback = false
if (!mod || !mod.__unstable__loadDesignSystem) {
throw new Error('Unable to load Tailwind CSS v4: Your installation of Tailwind CSS is not v4')

// TODO
// mod = (await import('tailwindcss-v4')) as ApiV4
mod = v4 as ApiV4
isFallback = true
}

// Create a Jiti instance that can be used to load plugins and config files
Expand All @@ -63,9 +63,7 @@ export async function loadV4(mod: ApiV4 | null, stylesheet: string | null): Prom
} else {
importBasePath = process.cwd()
stylesheet = path.join(importBasePath, 'fake.css')

// TODO: bundled theme.css file?
css = ''
css = assets['tailwindcss/theme.css']
}

// Load the design system and set up a compatible context object that is
Expand All @@ -90,11 +88,19 @@ export async function loadV4(mod: ApiV4 | null, stylesheet: string | null): Prom
}),

loadStylesheet: async (id: string, base: string) => {
let resolved = resolveCssFrom(base, id)
try {
let resolved = resolveCssFrom(base, id)

return {
base: path.dirname(resolved),
content: await fs.readFile(resolved, 'utf-8'),
}
} catch (err) {
if (isFallback && id in assets) {
return { base, content: assets[id] }
}

return {
base: path.dirname(resolved),
content: await fs.readFile(resolved, 'utf-8'),
throw err
}
},

Expand Down
10 changes: 10 additions & 0 deletions tests/fixtures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ let fixtures = [
dir: 'no-prettier-config',
ext: 'html',
},
{
name: 'no local install of Tailwind CSS (uses v4)',
dir: 'no-local-version',
ext: 'html',
},
{
name: 'no stylesheet given (uses v4)',
dir: 'no-stylesheet-given',
ext: 'html',
},
{
name: 'inferred config path',
dir: 'basic',
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/no-local-version/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "tailwindcss";

@theme {
--color-tomato: tomato;
}
1 change: 1 addition & 0 deletions tests/fixtures/no-local-version/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="sm:bg-tomato bg-red-500"></div>
1 change: 1 addition & 0 deletions tests/fixtures/no-local-version/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="bg-red-500 sm:bg-tomato"></div>
6 changes: 6 additions & 0 deletions tests/fixtures/no-local-version/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions tests/fixtures/no-local-version/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"prettier": {
"tailwindStylesheet": "./app.css"
}
}
1 change: 1 addition & 0 deletions tests/fixtures/no-stylesheet-given/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="sm:bg-red-500 bg-red-500"></div>
1 change: 1 addition & 0 deletions tests/fixtures/no-stylesheet-given/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="bg-red-500 sm:bg-red-500"></div>
18 changes: 16 additions & 2 deletions tests/format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ describe('other', () => {

expect(result).toEqual('<div class="unknown-class group peer container p-0"></div>')
})

test('parasite utilities (v4)', async ({ expect }) => {
let result = await format('<div class="group peer unknown-class p-0 container"></div>', {
tailwindPackageName: 'tailwindcss-v4',
})

expect(result).toEqual('<div class="group peer unknown-class container p-0"></div>')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't unknown-class exist before group and peer? 🤔

Because while group and peer don't exist, they are still more known than unknown classes.

I don't know if we test this already, but named group and peers should be in a similar position.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In v4 we don't sort group and peer though

})

test('parasite utilities (no install == v4)', async ({ expect }) => {
let result = await format('<div class="group peer unknown-class p-0 container"></div>')

expect(result).toEqual('<div class="group peer unknown-class container p-0"></div>')
})
})

describe('whitespace', () => {
Expand Down Expand Up @@ -85,7 +99,7 @@ describe('whitespace', () => {
test('duplicate classes are dropped', async ({ expect }) => {
let result = await format('<div class="underline line-through underline flex"></div>')

expect(result).toEqual('<div class="flex underline line-through"></div>')
expect(result).toEqual('<div class="flex line-through underline"></div>')
})
})

Expand Down Expand Up @@ -114,6 +128,6 @@ describe('errors', () => {
tailwindPackageName: 'tailwindcss-v3',
})

await expect(result).rejects.toThrowError(/Unable to load Tailwind CSS v4/)
await expect(result).rejects.toThrowError(/no such file or directory/)
})
})