Robin Malfait 462308d8d7
Sort upgraded CSS (#14866)
During the migration process, a lot of changes to the CSS file happen.
Some parts are converted, some parts are deleted and some new CSS is
added.

To make sure we are generating a sensible and good looking CSS file, we
will sort the final CSS and pretty print it.

The order we came up with looks like this:

```css
/* Imports */
@import "tailwindcss";
@import "../other.css";

/* Configuration */
@config "../path/to/tailwindcss.config.js";

@plugin "my-plugin-1";
@plugin "my-plugin-2";

@source "./foo/**/*.ts";
@source "./bar/**/*.ts";

@variant foo {}
@variant bar {}

@theme {}

/* Border compatibility CSS */
@layer base {}

/* Utilities */
@utility foo {}
@utility bar {}

/* Rest of your own CSS if any */
```

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-11-07 12:04:52 +00:00

312 lines
7.0 KiB
TypeScript

import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import path from 'node:path'
import postcss from 'postcss'
import { expect, it } from 'vitest'
import { formatNodes } from './codemods/format-nodes'
import { sortBuckets } from './codemods/sort-buckets'
import { migrateContents } from './migrate'
const css = dedent
let designSystem = await __unstable__loadDesignSystem(
css`
@import 'tailwindcss';
`,
{ base: __dirname },
)
let config = {
designSystem,
userConfig: {},
newPrefix: null,
configFilePath: path.resolve(__dirname, './tailwind.config.js'),
jsConfigMigration: null,
}
function migrate(input: string, config: any) {
return migrateContents(input, config, expect.getState().testPath)
.then((result) => postcss([sortBuckets(), formatNodes()]).process(result.root, result.opts))
.then((result) => result.css)
}
it('should print the input as-is', async () => {
expect(
await migrate(
css`
/* above */
.foo/* after */ {
/* above */
color: /* before */ red /* after */;
/* below */
}
`,
config,
),
).toMatchInlineSnapshot(`
"/* above */
.foo/* after */ {
/* above */
color: /* before */ red /* after */;
/* below */
}"
`)
})
it('should migrate a stylesheet', async () => {
expect(
await migrate(
css`
@tailwind base;
html {
overflow: hidden;
}
@tailwind components;
.a {
z-index: 1;
}
@layer components {
.b {
z-index: 2;
}
}
.c {
z-index: 3;
}
@tailwind utilities;
.d {
z-index: 4;
}
@layer utilities {
.e {
z-index: 5;
}
}
`,
config,
),
).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;
}
}
@utility b {
z-index: 2;
}
@utility e {
z-index: 5;
}
@layer base {
html {
overflow: hidden;
}
}
@layer components {
.a {
z-index: 1;
}
}
@layer components {
.c {
z-index: 3;
}
}
@layer utilities {
.d {
z-index: 4;
}
}"
`)
})
it('should migrate a stylesheet (with imports)', async () => {
expect(
await migrate(
css`
@import 'tailwindcss/base';
@import './my-base.css';
@import 'tailwindcss/components';
@import './my-components.css';
@import 'tailwindcss/utilities';
@import './my-utilities.css';
`,
config,
),
).toMatchInlineSnapshot(`
"@import 'tailwindcss';
@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;
}
}"
`)
})
it('should migrate a stylesheet (with preceding rules that should be wrapped in an `@layer`)', async () => {
expect(
await migrate(
css`
@charset "UTF-8";
@layer foo, bar, baz;
/**! My license comment */
html {
color: red;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
config,
),
).toMatchInlineSnapshot(`
"@charset "UTF-8";
@layer foo, bar, baz;
/**! My license comment */
@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;
}
}
@layer base {
html {
color: red;
}
}"
`)
})
it('should keep CSS as-is before existing `@layer` at-rules', async () => {
expect(
await migrate(
css`
.foo {
color: blue;
}
@layer components {
.bar {
color: red;
}
}
`,
config,
),
).toMatchInlineSnapshot(`
"@utility bar {
color: red;
}
.foo {
color: blue;
}"
`)
})