mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Add codemod for border style compatibility (#14746)
This PR adds a codemod that ensures that the border styles from Tailwind CSS v3 work as expected once your project is migrated to Tailwind CSS v4. In Tailwind CSS v3, the default border color is `colors.gray.200` and in Tailwind CSS v4 the default border color is `currentColor`. Similarly in Tailwind CSS v3, DOM elements such as `input`, `select`, and `textarea` have a border width of `0px`, in Tailwind CSS v4, we don't change the border width of these elements and keep them as `1px`. If your project happens to already use the same value for the default border color (`currentColor`) as we use in Tailwind CSS v4, then nothing happens. But this is very unlikely, so we will make sure that we honor your `borderColor.DEFAULT` value. If you didn't change the default values in your `tailwind.config.js`, then we will inject compatibility CSS using the default Tailwind CSS v3 values to ensure the default color and width are applied correctly.
This commit is contained in:
parent
816e7307de
commit
c6572ab929
@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- _Upgrade (experimental)_: Migrate `max-w-screen-*` utilities to `max-w-[var(…)]`([#14754](https://github.com/tailwindlabs/tailwindcss/pull/14754))
|
||||
- _Upgrade (experimental)_: Migrate `@variants` and `@responsive` directives ([#14748](https://github.com/tailwindlabs/tailwindcss/pull/14748))
|
||||
- _Upgrade (experimental)_: Migrate `@screen` directive ([#14749](https://github.com/tailwindlabs/tailwindcss/pull/14749))
|
||||
- _Upgrade (experimental)_: Generate compatibility styles for legacy default border color ([#14746](https://github.com/tailwindlabs/tailwindcss/pull/14746))
|
||||
- _Upgrade (experimental)_: Generate compatibility styles for legacy default border width on form elements ([#14746](https://github.com/tailwindlabs/tailwindcss/pull/14746))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
import { expect } from 'vitest'
|
||||
import { describe, expect } from 'vitest'
|
||||
import { css, html, json, test, ts } from '../utils'
|
||||
|
||||
test(
|
||||
@ -92,6 +92,40 @@ test(
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@source '../node_modules/my-external-lib/**/*.{html}';
|
||||
|
||||
@variant dark (&:where(.dark, .dark *));
|
||||
@ -212,6 +246,40 @@ test(
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@plugin '@tailwindcss/typography';
|
||||
@plugin '../custom-plugin' {
|
||||
is-null: null;
|
||||
@ -279,6 +347,40 @@ test(
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-gray-50: oklch(0.985 0 0);
|
||||
--color-gray-100: oklch(0.97 0 0);
|
||||
@ -345,6 +447,40 @@ test(
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@config '../tailwind.config.ts';
|
||||
"
|
||||
`)
|
||||
@ -403,6 +539,40 @@ test(
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@config '../tailwind.config.ts';
|
||||
"
|
||||
`)
|
||||
@ -457,6 +627,40 @@ test(
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@config '../tailwind.config.ts';
|
||||
"
|
||||
`)
|
||||
@ -478,3 +682,215 @@ test(
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
describe('border compatibility', () => {
|
||||
test(
|
||||
'migrate border compatibility',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
|
||||
// Empty / default config
|
||||
export default {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
} satisfies Config
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'migrate border compatibility if a custom border color is used',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
borderColor: ({ colors }) => ({
|
||||
DEFAULT: colors.blue[500],
|
||||
}),
|
||||
},
|
||||
},
|
||||
} satisfies Config
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: oklch(0.623 0.214 259.815);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'migrate border compatibility if a custom border color is used, that matches the default v4 border color',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
borderColor: ({ colors }) => ({
|
||||
DEFAULT: 'currentColor',
|
||||
}),
|
||||
},
|
||||
},
|
||||
} satisfies Config
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "workspace:^",
|
||||
"@tailwindcss/oxide": "workspace:^",
|
||||
"dedent": "1.5.3",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"globby": "^14.0.2",
|
||||
"jiti": "^2.0.0-beta.3",
|
||||
@ -44,7 +45,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"@types/postcss-import": "^14.0.3",
|
||||
"dedent": "1.5.3"
|
||||
"@types/postcss-import": "^14.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,254 @@
|
||||
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
|
||||
import dedent from 'dedent'
|
||||
import postcss from 'postcss'
|
||||
import { expect, it } from 'vitest'
|
||||
import { formatNodes } from './format-nodes'
|
||||
import { migrateBorderCompatibility } from './migrate-border-compatibility'
|
||||
|
||||
const css = dedent
|
||||
|
||||
async function migrate(input: string) {
|
||||
let designSystem = await __unstable__loadDesignSystem(
|
||||
css`
|
||||
@import 'tailwindcss';
|
||||
`,
|
||||
{ base: __dirname },
|
||||
)
|
||||
|
||||
return postcss()
|
||||
.use(migrateBorderCompatibility({ designSystem }))
|
||||
.use(formatNodes())
|
||||
.process(input, { from: expect.getState().testPath })
|
||||
.then((result) => result.css)
|
||||
}
|
||||
|
||||
it("should add compatibility CSS after the `@import 'tailwindcss'`", async () => {
|
||||
expect(
|
||||
await migrate(css`
|
||||
@import 'tailwindcss';
|
||||
`),
|
||||
).toMatchInlineSnapshot(`
|
||||
"@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should add the compatibility CSS after the last `@import`', async () => {
|
||||
expect(
|
||||
await migrate(css`
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
`),
|
||||
).toMatchInlineSnapshot(`
|
||||
"@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should add the compatibility CSS after the last import, even if a body-less `@layer` exists', async () => {
|
||||
expect(
|
||||
await migrate(css`
|
||||
@charset "UTF-8";
|
||||
@layer foo, bar, baz;
|
||||
|
||||
/**!
|
||||
* License header
|
||||
*/
|
||||
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
`),
|
||||
).toMatchInlineSnapshot(`
|
||||
"@charset "UTF-8";
|
||||
@layer foo, bar, baz;
|
||||
|
||||
/**!
|
||||
* License header
|
||||
*/
|
||||
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
it('should add the compatibility CSS before the first `@layer base`', async () => {
|
||||
expect(
|
||||
await migrate(css`
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
@variant foo {
|
||||
}
|
||||
|
||||
@utility bar {
|
||||
}
|
||||
|
||||
@layer base {
|
||||
}
|
||||
|
||||
@utility baz {
|
||||
}
|
||||
|
||||
@layer base {
|
||||
}
|
||||
`),
|
||||
).toMatchInlineSnapshot(`
|
||||
"@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
@variant foo {
|
||||
}
|
||||
|
||||
@utility bar {
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
}
|
||||
|
||||
@utility baz {
|
||||
}
|
||||
|
||||
@layer base {
|
||||
}"
|
||||
`)
|
||||
})
|
||||
@ -0,0 +1,202 @@
|
||||
import dedent from 'dedent'
|
||||
import postcss, { AtRule, type Plugin, type Root } from 'postcss'
|
||||
import type { Config } from 'tailwindcss'
|
||||
import { keyPathToCssProperty } from '../../../tailwindcss/src/compat/apply-config-to-theme'
|
||||
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
|
||||
import { toKeyPath } from '../../../tailwindcss/src/utils/to-key-path'
|
||||
import * as ValueParser from '../../../tailwindcss/src/value-parser'
|
||||
|
||||
// Defaults in v4
|
||||
const DEFAULT_BORDER_COLOR = 'currentColor'
|
||||
|
||||
const css = dedent
|
||||
const BORDER_COLOR_COMPATIBILITY_CSS = css`
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: theme(borderColor.DEFAULT);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const BORDER_WIDTH_COMPATIBILITY_CSS = css`
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function migrateBorderCompatibility({
|
||||
designSystem,
|
||||
userConfig,
|
||||
}: {
|
||||
designSystem: DesignSystem
|
||||
userConfig?: Config
|
||||
}): Plugin {
|
||||
// @ts-expect-error
|
||||
let defaultBorderColor = userConfig?.theme?.borderColor?.DEFAULT
|
||||
|
||||
function canResolveThemeValue(path: string) {
|
||||
let variable = `--${keyPathToCssProperty(toKeyPath(path))}` as const
|
||||
return Boolean(designSystem.theme.get([variable]))
|
||||
}
|
||||
|
||||
function migrate(root: Root) {
|
||||
let targetNode = null as AtRule | null
|
||||
|
||||
root.walkAtRules((node) => {
|
||||
if (node.name === 'import') {
|
||||
targetNode = node
|
||||
} else if (node.name === 'layer' && node.params === 'base') {
|
||||
targetNode = node
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (!targetNode) return
|
||||
|
||||
// Figure out the compatibility CSS to inject
|
||||
let compatibilityCssString = ''
|
||||
if (defaultBorderColor !== DEFAULT_BORDER_COLOR) {
|
||||
compatibilityCssString += BORDER_COLOR_COMPATIBILITY_CSS
|
||||
compatibilityCssString += '\n\n'
|
||||
}
|
||||
|
||||
compatibilityCssString += BORDER_WIDTH_COMPATIBILITY_CSS
|
||||
let compatibilityCss = postcss.parse(compatibilityCssString)
|
||||
|
||||
// Replace the `theme(…)` with v3 values if we can't resolve the theme
|
||||
// value.
|
||||
compatibilityCss.walkDecls((decl) => {
|
||||
if (decl.value.includes('theme(')) {
|
||||
decl.value = substituteFunctionsInValue(ValueParser.parse(decl.value), (path) => {
|
||||
if (canResolveThemeValue(path)) {
|
||||
return defaultBorderColor
|
||||
} else {
|
||||
if (path === 'borderColor.DEFAULT') {
|
||||
return 'var(--color-gray-200, currentColor)'
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Cleanup `--border-color` definition in `theme(…)`
|
||||
root.walkAtRules('theme', (node) => {
|
||||
node.walkDecls('--border-color', (decl) => {
|
||||
decl.remove()
|
||||
})
|
||||
|
||||
if (node.nodes?.length === 0) {
|
||||
node.remove()
|
||||
}
|
||||
})
|
||||
|
||||
// Inject the compatibility CSS
|
||||
if (targetNode.name === 'import') {
|
||||
targetNode.after(compatibilityCss)
|
||||
|
||||
let next = targetNode.next()
|
||||
if (next) next.raws.before = '\n\n'
|
||||
} else {
|
||||
let rawsBefore = compatibilityCss.last?.raws.before
|
||||
|
||||
targetNode.before(compatibilityCss)
|
||||
|
||||
let prev = targetNode.prev()
|
||||
if (prev) prev.raws.before = rawsBefore
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
postcssPlugin: '@tailwindcss/upgrade/migrate-border-compatibility',
|
||||
OnceExit: migrate,
|
||||
}
|
||||
}
|
||||
|
||||
function substituteFunctionsInValue(
|
||||
ast: ValueParser.ValueAstNode[],
|
||||
handle: (value: string, fallback?: string) => string | null,
|
||||
) {
|
||||
ValueParser.walk(ast, (node, { replaceWith }) => {
|
||||
if (node.kind === 'function' && node.value === 'theme') {
|
||||
if (node.nodes.length < 1) return
|
||||
|
||||
let pathNode = node.nodes[0]
|
||||
if (pathNode.kind !== 'word') return
|
||||
|
||||
let path = pathNode.value
|
||||
|
||||
// For the theme function arguments, we require all separators to contain
|
||||
// comma (`,`), spaces alone should be merged into the previous word to
|
||||
// avoid splitting in this case:
|
||||
//
|
||||
// theme(--color-red-500 / 75%) theme(--color-red-500 / 75%, foo, bar)
|
||||
//
|
||||
// We only need to do this for the first node, as the fallback values are
|
||||
// passed through as-is.
|
||||
let skipUntilIndex = 1
|
||||
for (let i = skipUntilIndex; i < node.nodes.length; i++) {
|
||||
if (node.nodes[i].value.includes(',')) {
|
||||
break
|
||||
}
|
||||
path += ValueParser.toCss([node.nodes[i]])
|
||||
skipUntilIndex = i + 1
|
||||
}
|
||||
|
||||
path = eventuallyUnquote(path)
|
||||
let fallbackValues = node.nodes.slice(skipUntilIndex + 1)
|
||||
|
||||
let replacement =
|
||||
fallbackValues.length > 0 ? handle(path, ValueParser.toCss(fallbackValues)) : handle(path)
|
||||
if (replacement === null) return
|
||||
|
||||
replaceWith(ValueParser.parse(replacement))
|
||||
}
|
||||
})
|
||||
|
||||
return ValueParser.toCss(ast)
|
||||
}
|
||||
|
||||
function eventuallyUnquote(value: string) {
|
||||
if (value[0] !== "'" && value[0] !== '"') return value
|
||||
|
||||
let unquoted = ''
|
||||
let quoteChar = value[0]
|
||||
for (let i = 1; i < value.length - 1; i++) {
|
||||
let currentChar = value[i]
|
||||
let nextChar = value[i + 1]
|
||||
|
||||
if (currentChar === '\\' && (nextChar === quoteChar || nextChar === '\\')) {
|
||||
unquoted += nextChar
|
||||
i++
|
||||
} else {
|
||||
unquoted += currentChar
|
||||
}
|
||||
}
|
||||
|
||||
return unquoted
|
||||
}
|
||||
@ -97,6 +97,41 @@ it('should migrate a stylesheet', async () => {
|
||||
|
||||
@config './tailwind.config.js';
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
overflow: hidden;
|
||||
@ -149,6 +184,39 @@ it('should migrate a stylesheet (with imports)', async () => {
|
||||
@import './my-base.css' layer(base);
|
||||
@import './my-components.css' layer(components);
|
||||
@import './my-utilities.css' layer(utilities);
|
||||
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@config './tailwind.config.js';"
|
||||
`)
|
||||
})
|
||||
@ -175,6 +243,38 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in
|
||||
/**! My license comment */
|
||||
@import 'tailwindcss';
|
||||
@config './tailwind.config.js';
|
||||
/*
|
||||
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
/*
|
||||
Form elements have a 1px border by default in Tailwind CSS v4, so we've
|
||||
added these compatibility styles to make sure everything still looks the
|
||||
same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add \`border-0\` to
|
||||
any form elements that shouldn't have a border.
|
||||
*/
|
||||
@layer base {
|
||||
input:where(:not([type='button'], [type='reset'], [type='submit'])),
|
||||
select,
|
||||
textarea {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@layer base {
|
||||
html {
|
||||
color: red;
|
||||
|
||||
@ -6,6 +6,7 @@ import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
|
||||
import { segment } from '../../tailwindcss/src/utils/segment'
|
||||
import { migrateAtApply } from './codemods/migrate-at-apply'
|
||||
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
|
||||
import { migrateBorderCompatibility } from './codemods/migrate-border-compatibility'
|
||||
import { migrateConfig } from './codemods/migrate-config'
|
||||
import { migrateMediaScreen } from './codemods/migrate-media-screen'
|
||||
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
|
||||
@ -37,13 +38,14 @@ export async function migrateContents(
|
||||
|
||||
return postcss()
|
||||
.use(migrateAtApply(options))
|
||||
.use(migrateThemeToVar(options))
|
||||
.use(migrateMediaScreen(options))
|
||||
.use(migrateVariantsDirective())
|
||||
.use(migrateAtLayerUtilities(stylesheet))
|
||||
.use(migrateMissingLayers())
|
||||
.use(migrateTailwindDirectives(options))
|
||||
.use(migrateConfig(stylesheet, options))
|
||||
.use(migrateBorderCompatibility(options))
|
||||
.use(migrateThemeToVar(options))
|
||||
.process(stylesheet.root, { from: stylesheet.file ?? undefined })
|
||||
}
|
||||
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -279,6 +279,9 @@ importers:
|
||||
'@tailwindcss/oxide':
|
||||
specifier: workspace:^
|
||||
version: link:../../crates/node
|
||||
dedent:
|
||||
specifier: 1.5.3
|
||||
version: 1.5.3
|
||||
enhanced-resolve:
|
||||
specifier: ^5.17.1
|
||||
version: 5.17.1
|
||||
@ -322,9 +325,6 @@ importers:
|
||||
'@types/postcss-import':
|
||||
specifier: ^14.0.3
|
||||
version: 14.0.3
|
||||
dedent:
|
||||
specifier: 1.5.3
|
||||
version: 1.5.3
|
||||
|
||||
packages/@tailwindcss-vite:
|
||||
dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user