Skip to content

Commit 894bf9f

Browse files
Support migrating projects with multiple config files (#14863)
When migrating a project from Tailwind CSS v3 to Tailwind CSS v4, then we started the migration process in the following order: 1. Migrate the JS/TS config file 2. Migrate the source files (found via the `content` option) 3. Migrate the CSS files However, if you have a setup where you have multiple CSS root files (e.g.: `frontend` and `admin` are separated), then that typically means that you have an `@config` directive in your CSS files. These point to the Tailwind CSS config file. This PR changes the migration order to do the following: 1. Build a tree of all the CSS files 2. For each `@config` directive, migrate the JS/TS config file 3. For each JS/TS config file, migrate the source files If a CSS file does not contain any `@config` directives, then we start by filling in the `@config` directive with the default Tailwind CSS config file (if found, or the one passed in). If no default config file or passed in config file can be found, then we will error out (just like we do now) --------- Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
1 parent df6dfb0 commit 894bf9f

File tree

16 files changed

+533
-92
lines changed

16 files changed

+533
-92
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- _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))
13+
- _Upgrade (experimental)_: Support migrating projects with multiple config files ([#14863](https://github.com/tailwindlabs/tailwindcss/pull/14863))
1314

1415
### Fixed
1516

integrations/upgrade/index.test.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,51 @@
11
import { expect } from 'vitest'
22
import { candidate, css, html, js, json, test } from '../utils'
33

4+
test(
5+
'error when no CSS file with @tailwind is used',
6+
{
7+
fs: {
8+
'package.json': json`
9+
{
10+
"dependencies": {
11+
"@tailwindcss/upgrade": "workspace:^"
12+
},
13+
"devDependencies": {
14+
"@tailwindcss/cli": "workspace:^"
15+
}
16+
}
17+
`,
18+
'tailwind.config.js': js`
19+
/** @type {import('tailwindcss').Config} */
20+
module.exports = {
21+
content: ['./src/**/*.{html,js}'],
22+
}
23+
`,
24+
'src/index.html': html`
25+
<h1>🤠👋</h1>
26+
<div class="!flex"></div>
27+
`,
28+
'src/fonts.css': css`/* Unrelated CSS file */`,
29+
},
30+
},
31+
async ({ fs, exec }) => {
32+
let output = await exec('npx @tailwindcss/upgrade')
33+
expect(output).toContain('Cannot find any CSS files that reference Tailwind CSS.')
34+
35+
// Files should not be modified
36+
expect(await fs.dumpFiles('./src/**/*.{css,html}')).toMatchInlineSnapshot(`
37+
"
38+
--- ./src/index.html ---
39+
<h1>🤠👋</h1>
40+
<div class="!flex"></div>
41+
42+
--- ./src/fonts.css ---
43+
/* Unrelated CSS file */
44+
"
45+
`)
46+
},
47+
)
48+
449
test(
550
`upgrades a v3 project to v4`,
651
{
@@ -858,6 +903,11 @@ test(
858903
prefix: 'tw__',
859904
}
860905
`,
906+
'src/index.css': css`
907+
@tailwind base;
908+
@tailwind components;
909+
@tailwind utilities;
910+
`,
861911
'src/index.html': html`
862912
<div class="tw__bg-gradient-to-t"></div>
863913
`,
@@ -1304,7 +1354,7 @@ test(
13041354
@tailwind base;
13051355
@tailwind components;
13061356
@tailwind utilities;
1307-
@config "../tailwind.config.js";
1357+
@config "../tailwind.config.ts";
13081358
`,
13091359
'src/root.3.css': css`
13101360
/* Inject missing @config above first @theme */
@@ -1421,7 +1471,7 @@ test(
14211471
border-width: 0;
14221472
}
14231473
}
1424-
@config "../tailwind.config.js";
1474+
@config "../tailwind.config.ts";
14251475
14261476
--- ./src/root.3.css ---
14271477
/* Inject missing @config above first @theme */

integrations/upgrade/js-config.test.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,158 @@ test(
783783
},
784784
)
785785

786+
test(
787+
'multi-root project',
788+
{
789+
fs: {
790+
'package.json': json`
791+
{
792+
"dependencies": {
793+
"@tailwindcss/upgrade": "workspace:^"
794+
}
795+
}
796+
`,
797+
798+
// Project A
799+
'project-a/tailwind.config.ts': ts`
800+
export default {
801+
content: {
802+
relative: true,
803+
files: ['./src/**/*.html'],
804+
},
805+
theme: {
806+
extend: {
807+
colors: {
808+
primary: 'red',
809+
},
810+
},
811+
},
812+
}
813+
`,
814+
'project-a/src/input.css': css`
815+
@tailwind base;
816+
@tailwind components;
817+
@tailwind utilities;
818+
@config "../tailwind.config.ts";
819+
`,
820+
'project-a/src/index.html': html`<div class="!text-primary"></div>`,
821+
822+
// Project B
823+
'project-b/tailwind.config.ts': ts`
824+
export default {
825+
content: {
826+
relative: true,
827+
files: ['./src/**/*.html'],
828+
},
829+
theme: {
830+
extend: {
831+
colors: {
832+
primary: 'blue',
833+
},
834+
},
835+
},
836+
}
837+
`,
838+
'project-b/src/input.css': css`
839+
@tailwind base;
840+
@tailwind components;
841+
@tailwind utilities;
842+
@config "../tailwind.config.ts";
843+
`,
844+
'project-b/src/index.html': html`<div class="!text-primary"></div>`,
845+
},
846+
},
847+
async ({ exec, fs }) => {
848+
await exec('npx @tailwindcss/upgrade')
849+
850+
expect(await fs.dumpFiles('project-{a,b}/**/*.{css,ts}')).toMatchInlineSnapshot(`
851+
"
852+
--- project-a/src/input.css ---
853+
@import 'tailwindcss';
854+
855+
/*
856+
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
857+
so we've added these compatibility styles to make sure everything still
858+
looks the same as it did with Tailwind CSS v3.
859+
860+
If we ever want to remove these styles, we need to add an explicit border
861+
color utility to any element that depends on these defaults.
862+
*/
863+
@layer base {
864+
*,
865+
::after,
866+
::before,
867+
::backdrop,
868+
::file-selector-button {
869+
border-color: var(--color-gray-200, currentColor);
870+
}
871+
}
872+
873+
/*
874+
Form elements have a 1px border by default in Tailwind CSS v4, so we've
875+
added these compatibility styles to make sure everything still looks the
876+
same as it did with Tailwind CSS v3.
877+
878+
If we ever want to remove these styles, we need to add \`border-0\` to
879+
any form elements that shouldn't have a border.
880+
*/
881+
@layer base {
882+
input:where(:not([type='button'], [type='reset'], [type='submit'])),
883+
select,
884+
textarea {
885+
border-width: 0;
886+
}
887+
}
888+
889+
@theme {
890+
--color-primary: red;
891+
}
892+
893+
--- project-b/src/input.css ---
894+
@import 'tailwindcss';
895+
896+
/*
897+
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
898+
so we've added these compatibility styles to make sure everything still
899+
looks the same as it did with Tailwind CSS v3.
900+
901+
If we ever want to remove these styles, we need to add an explicit border
902+
color utility to any element that depends on these defaults.
903+
*/
904+
@layer base {
905+
*,
906+
::after,
907+
::before,
908+
::backdrop,
909+
::file-selector-button {
910+
border-color: var(--color-gray-200, currentColor);
911+
}
912+
}
913+
914+
/*
915+
Form elements have a 1px border by default in Tailwind CSS v4, so we've
916+
added these compatibility styles to make sure everything still looks the
917+
same as it did with Tailwind CSS v3.
918+
919+
If we ever want to remove these styles, we need to add \`border-0\` to
920+
any form elements that shouldn't have a border.
921+
*/
922+
@layer base {
923+
input:where(:not([type='button'], [type='reset'], [type='submit'])),
924+
select,
925+
textarea {
926+
border-width: 0;
927+
}
928+
}
929+
930+
@theme {
931+
--color-primary: blue;
932+
}
933+
"
934+
`)
935+
},
936+
)
937+
786938
describe('border compatibility', () => {
787939
test(
788940
'migrate border compatibility',

packages/@tailwindcss-cli/src/utils/renderer.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import path from 'path'
1+
import path from 'node:path'
22
import { describe, expect, it } from 'vitest'
33
import { relative, wordWrap } from './renderer'
44
import { normalizeWindowsSeperators } from './test-helpers'

packages/@tailwindcss-postcss/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import QuickLRU from '@alloc/quick-lru'
22
import { compile, env } from '@tailwindcss/node'
33
import { clearRequireCache } from '@tailwindcss/node/require-cache'
44
import { Scanner } from '@tailwindcss/oxide'
5-
import fs from 'fs'
65
import { Features, transform } from 'lightningcss'
7-
import path from 'path'
6+
import fs from 'node:fs'
7+
import path from 'node:path'
88
import postcss, { type AcceptedPlugin, type PluginCreator } from 'postcss'
99
import fixRelativePathsPlugin from './postcss-fix-relative-paths'
1010

packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,13 @@ export function migrateConfig(
3131

3232
let cssConfig = new AtRule()
3333

34-
if (jsConfigMigration === null) {
35-
// Skip if there is already a `@config` directive
36-
{
37-
let hasConfig = false
38-
root.walkAtRules('config', () => {
39-
hasConfig = true
40-
return false
41-
})
42-
if (hasConfig) return
43-
}
34+
// Remove the `@config` directive if it exists and we couldn't migrate the
35+
// config file.
36+
if (jsConfigMigration !== null) {
37+
root.walkAtRules('config', (node) => {
38+
node.remove()
39+
})
4440

45-
cssConfig.append(
46-
new AtRule({
47-
name: 'config',
48-
params: `'${relativeToStylesheet(sheet, configFilePath)}'`,
49-
}),
50-
)
51-
} else {
5241
let css = '\n\n'
5342
for (let source of jsConfigMigration.sources) {
5443
let absolute = path.resolve(source.base, source.pattern)

packages/@tailwindcss-upgrade/src/index.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ it('should migrate a stylesheet', async () => {
9595
).toMatchInlineSnapshot(`
9696
"@import 'tailwindcss';
9797
98-
@config './tailwind.config.js';
99-
10098
/*
10199
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
102100
so we've added these compatibility styles to make sure everything still
@@ -216,8 +214,7 @@ it('should migrate a stylesheet (with imports)', async () => {
216214
textarea {
217215
border-width: 0;
218216
}
219-
}
220-
@config './tailwind.config.js';"
217+
}"
221218
`)
222219
})
223220

@@ -242,7 +239,6 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in
242239
@layer foo, bar, baz;
243240
/**! My license comment */
244241
@import 'tailwindcss';
245-
@config './tailwind.config.js';
246242
/*
247243
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
248244
so we've added these compatibility styles to make sure everything still

0 commit comments

Comments
 (0)