diff --git a/CHANGELOG.md b/CHANGELOG.md index 805324e..23d0c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Handle quote escapes in LESS when sorting `@apply` ([#392](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/pull/392)) - 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)) +- 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 diff --git a/build.mjs b/build.mjs index a651ac8..9675c9a 100644 --- a/build.mjs +++ b/build.mjs @@ -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)`, ] diff --git a/src/config.ts b/src/config.ts index e724d65..e0802d7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -117,14 +117,6 @@ export async function getTailwindConfig(options: ParserOptions): Promise { 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)) } diff --git a/src/versions/assets.ts b/src/versions/assets.ts new file mode 100644 index 0000000..6f2a447 --- /dev/null +++ b/src/versions/assets.ts @@ -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 = { + 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, +} diff --git a/src/versions/v4.ts b/src/versions/v4.ts index 2586630..ddadc95 100644 --- a/src/versions/v4.ts +++ b/src/versions/v4.ts @@ -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][] @@ -40,11 +41,10 @@ interface ApiV4 { export async function loadV4(mod: ApiV4 | null, stylesheet: string | null): Promise { // 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 @@ -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 @@ -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 } }, diff --git a/tests/fixtures.test.ts b/tests/fixtures.test.ts index 18b4351..cafe06c 100644 --- a/tests/fixtures.test.ts +++ b/tests/fixtures.test.ts @@ -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', diff --git a/tests/fixtures/no-local-version/app.css b/tests/fixtures/no-local-version/app.css new file mode 100644 index 0000000..7f66730 --- /dev/null +++ b/tests/fixtures/no-local-version/app.css @@ -0,0 +1,5 @@ +@import "tailwindcss"; + +@theme { + --color-tomato: tomato; +} diff --git a/tests/fixtures/no-local-version/index.html b/tests/fixtures/no-local-version/index.html new file mode 100644 index 0000000..f7a7609 --- /dev/null +++ b/tests/fixtures/no-local-version/index.html @@ -0,0 +1 @@ +
diff --git a/tests/fixtures/no-local-version/output.html b/tests/fixtures/no-local-version/output.html new file mode 100644 index 0000000..8409de1 --- /dev/null +++ b/tests/fixtures/no-local-version/output.html @@ -0,0 +1 @@ +
diff --git a/tests/fixtures/no-local-version/package-lock.json b/tests/fixtures/no-local-version/package-lock.json new file mode 100644 index 0000000..803df1e --- /dev/null +++ b/tests/fixtures/no-local-version/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "no-local-version", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/tests/fixtures/no-local-version/package.json b/tests/fixtures/no-local-version/package.json new file mode 100644 index 0000000..336bae0 --- /dev/null +++ b/tests/fixtures/no-local-version/package.json @@ -0,0 +1,5 @@ +{ + "prettier": { + "tailwindStylesheet": "./app.css" + } +} diff --git a/tests/fixtures/no-stylesheet-given/index.html b/tests/fixtures/no-stylesheet-given/index.html new file mode 100644 index 0000000..8ea0b8b --- /dev/null +++ b/tests/fixtures/no-stylesheet-given/index.html @@ -0,0 +1 @@ +
diff --git a/tests/fixtures/no-stylesheet-given/output.html b/tests/fixtures/no-stylesheet-given/output.html new file mode 100644 index 0000000..77ce1eb --- /dev/null +++ b/tests/fixtures/no-stylesheet-given/output.html @@ -0,0 +1 @@ +
diff --git a/tests/format.test.ts b/tests/format.test.ts index 56d091e..cf55ce1 100644 --- a/tests/format.test.ts +++ b/tests/format.test.ts @@ -26,6 +26,20 @@ describe('other', () => { expect(result).toEqual('
') }) + + test('parasite utilities (v4)', async ({ expect }) => { + let result = await format('
', { + tailwindPackageName: 'tailwindcss-v4', + }) + + expect(result).toEqual('
') + }) + + test('parasite utilities (no install == v4)', async ({ expect }) => { + let result = await format('
') + + expect(result).toEqual('
') + }) }) describe('whitespace', () => { @@ -61,7 +75,7 @@ describe('whitespace', () => { test('duplicate classes are dropped', async ({ expect }) => { let result = await format('
') - expect(result).toEqual('
') + expect(result).toEqual('
') }) }) @@ -90,6 +104,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/) }) })