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 @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- _Upgrade (experimental)_: Migrate `grid-cols-[subgrid]` and `grid-rows-[subgrid]` to `grid-cols-subgrid` and `grid-rows-subgrid` ([#14840](https://github.com/tailwindlabs/tailwindcss/pull/14840))
- _Upgrade (experimental)_: Support migrating projects with multiple config files ([#14863](https://github.com/tailwindlabs/tailwindcss/pull/14863))

### Fixed

Expand Down
54 changes: 52 additions & 2 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
import { expect } from 'vitest'
import { candidate, css, html, js, json, test } from '../utils'

test(
'error when no CSS file with @tailwind is used',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
},
"devDependencies": {
"@tailwindcss/cli": "workspace:^"
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
}
`,
'src/index.html': html`
<h1>🤠👋</h1>
<div class="!flex"></div>
`,
'src/fonts.css': css`/* Unrelated CSS file */`,
},
},
async ({ fs, exec }) => {
let output = await exec('npx @tailwindcss/upgrade')
expect(output).toContain('Cannot find any CSS files that reference Tailwind CSS.')

// Files should not be modified
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- ./src/index.html ---
<h1>🤠👋</h1>
<div class="!flex"></div>

--- ./src/fonts.css ---
/* Unrelated CSS file */
"
`)
},
)

test(
`upgrades a v3 project to v4`,
{
Expand Down Expand Up @@ -858,6 +903,11 @@ test(
prefix: 'tw__',
}
`,
'src/index.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
'src/index.html': html`
<div class="tw__bg-gradient-to-t"></div>
`,
Expand Down Expand Up @@ -1304,7 +1354,7 @@ test(
@tailwind base;
@tailwind components;
@tailwind utilities;
@config "../tailwind.config.js";
@config "../tailwind.config.ts";
`,
'src/root.3.css': css`
/* Inject missing @config above first @theme */
Expand Down Expand Up @@ -1421,7 +1471,7 @@ test(
border-width: 0;
}
}
@config "../tailwind.config.js";
@config "../tailwind.config.ts";

--- ./src/root.3.css ---
/* Inject missing @config above first @theme */
Expand Down
152 changes: 152 additions & 0 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,158 @@ test(
},
)

test(
'multi-root project',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,

// Project A
'project-a/tailwind.config.ts': ts`
export default {
content: {
relative: true,
files: ['./src/**/*.html'],
},
theme: {
extend: {
colors: {
primary: 'red',
},
},
},
}
`,
'project-a/src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@config "../tailwind.config.ts";
`,
'project-a/src/index.html': html`<div class="!text-primary"></div>`,

// Project B
'project-b/tailwind.config.ts': ts`
export default {
content: {
relative: true,
files: ['./src/**/*.html'],
},
theme: {
extend: {
colors: {
primary: 'blue',
},
},
},
}
`,
'project-b/src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@config "../tailwind.config.ts";
`,
'project-b/src/index.html': html`<div class="!text-primary"></div>`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('project-{a,b}/**/*.{css,ts}')).toMatchInlineSnapshot(`
"
--- project-a/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-primary: red;
}

--- project-b/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-primary: blue;
}
"
`)
},
)

describe('border compatibility', () => {
test(
'migrate border compatibility',
Expand Down
2 changes: 1 addition & 1 deletion packages/@tailwindcss-cli/src/utils/renderer.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { relative, wordWrap } from './renderer'
import { normalizeWindowsSeperators } from './test-helpers'
Expand Down
4 changes: 2 additions & 2 deletions packages/@tailwindcss-postcss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import QuickLRU from '@alloc/quick-lru'
import { compile, env } from '@tailwindcss/node'
import { clearRequireCache } from '@tailwindcss/node/require-cache'
import { Scanner } from '@tailwindcss/oxide'
import fs from 'fs'
import { Features, transform } from 'lightningcss'
import path from 'path'
import fs from 'node:fs'
import path from 'node:path'
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
import fixRelativePathsPlugin from './postcss-fix-relative-paths'

Expand Down
23 changes: 6 additions & 17 deletions packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,13 @@ export function migrateConfig(

let cssConfig = new AtRule()

if (jsConfigMigration === null) {
// Skip if there is already a `@config` directive
{
let hasConfig = false
root.walkAtRules('config', () => {
hasConfig = true
return false
})
if (hasConfig) return
}
// Remove the `@config` directive if it exists and we couldn't migrate the
// config file.
if (jsConfigMigration !== null) {
root.walkAtRules('config', (node) => {
node.remove()
})

cssConfig.append(
new AtRule({
name: 'config',
params: `'${relativeToStylesheet(sheet, configFilePath)}'`,
}),
)
} else {
let css = '\n\n'
for (let source of jsConfigMigration.sources) {
let absolute = path.resolve(source.base, source.pattern)
Expand Down
6 changes: 1 addition & 5 deletions packages/@tailwindcss-upgrade/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ it('should migrate a stylesheet', async () => {
).toMatchInlineSnapshot(`
"@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
Expand Down Expand Up @@ -216,8 +214,7 @@ it('should migrate a stylesheet (with imports)', async () => {
textarea {
border-width: 0;
}
}
@config './tailwind.config.js';"
}"
`)
})

Expand All @@ -242,7 +239,6 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in
@layer foo, bar, baz;
/**! 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
Expand Down
Loading