From 4fe8e7be09c11c4b5e0f78f0ccfd78401e037e54 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 9 Oct 2024 18:45:50 +0200 Subject: [PATCH 01/15] =?UTF-8?q?Ensure=20`@config=20"=E2=80=A6"`=20is=20a?= =?UTF-8?q?dded=20when=20`tailwind.config.js`=20is=20detected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- integrations/upgrade/index.test.ts | 65 ++++++++++++++++- .../src/codemods/migrate-at-config.ts | 73 +++++++++++++++++++ .../@tailwindcss-upgrade/src/index.test.ts | 15 +++- packages/@tailwindcss-upgrade/src/migrate.ts | 3 + .../src/template/prepare-config.ts | 9 ++- 5 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 8835c5483f36..40b0aa47129d 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -39,7 +39,8 @@ test(
--- ./src/input.css --- - @import 'tailwindcss';" + @import 'tailwindcss'; + @config "../tailwind.config.js";" `) let packageJsonContent = await fs.read('package.json') @@ -95,6 +96,8 @@ test( --- ./src/input.css --- @import 'tailwindcss' prefix(tw); + @config "../tailwind.config.js"; + .btn { @apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white; }" @@ -140,6 +143,8 @@ test( --- ./src/index.css --- @import 'tailwindcss'; + @config "../tailwind.config.js"; + .a { @apply flex; } @@ -193,6 +198,8 @@ test( --- ./src/index.css --- @import 'tailwindcss'; + @config "../tailwind.config.js"; + @layer base { html { color: #333; @@ -251,6 +258,8 @@ test( --- ./src/index.css --- @import 'tailwindcss'; + @config "../tailwind.config.js"; + @utility btn { @apply rounded-md px-2 py-1 bg-blue-500 text-white; } @@ -615,6 +624,7 @@ test( --- ./src/index.css --- @import 'tailwindcss'; @import './utilities.css'; + @config "../tailwind.config.js"; --- ./src/utilities.css --- @utility no-scrollbar { @@ -730,6 +740,7 @@ test( @import './c.1.css' layer(utilities); @import './c.1.utilities.css'; @import './d.1.css'; + @config "../tailwind.config.js"; --- ./src/a.1.css --- @import './a.1.utilities.css' @@ -862,14 +873,64 @@ test( --- ./src/root.1.css --- @import 'tailwindcss/utilities' layer(utilities); @import './a.1.css' layer(utilities); + @config "../tailwind.config.js"; --- ./src/root.2.css --- @import 'tailwindcss/utilities' layer(utilities); @import './a.1.css' layer(components); + @config "../tailwind.config.js"; --- ./src/root.3.css --- @import 'tailwindcss/utilities' layer(utilities); - @import './a.1.css' layer(utilities);" + @import './a.1.css' layer(utilities); + @config "../tailwind.config.js";" + `) + }, +) + +test( + 'injecting `@config` when a tailwind.config.{js,ts,…} is detected', + { + fs: { + 'package.json': json` + { + "dependencies": { + "@tailwindcss/upgrade": "workspace:^" + } + } + `, + 'tailwind.config.ts': js` + export default { + content: ['./src/**/*.{html,js}'], + } + `, + 'src/index.html': html` +

🤠👋

+
+ `, + 'src/input.css': css` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + }, + async ({ exec, fs }) => { + await exec('npx @tailwindcss/upgrade --force') + + await fs.expectFileToContain( + 'src/index.html', + html` +

🤠👋

+
+ `, + ) + + expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(` + " + --- ./src/input.css --- + @import 'tailwindcss'; + @config "../tailwind.config.ts";" `) }, ) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts new file mode 100644 index 000000000000..d3bb4b6dbef6 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -0,0 +1,73 @@ +import path from 'node:path' +import { AtRule, type Plugin, type Root } from 'postcss' +import type { Stylesheet } from '../stylesheet' +import { walk, WalkAction } from '../utils/walk' + +export function migrateAtConfig( + sheet: Stylesheet, + { configFilePath }: { configFilePath: string }, +): Plugin { + function migrate(root: Root) { + let hasConfig = false + root.walkAtRules('config', () => { + hasConfig = true + return false + }) + + // We already have a `@config` + if (hasConfig) return + + // We don't have a sheet with a file path + // TODO: Why? Tests? + if (!sheet.file) return + + // Should this sheet have an `@config`? + // 1. It should be a root CSS file + if (sheet.parents.size > 0) return + + // 2. It should include a `@import "tailwindcss"` + let hasTailwindImport = false + root.walkAtRules('import', (node) => { + if (node.params.includes('tailwindcss')) { + hasTailwindImport = true + return false + } + }) + if (!hasTailwindImport) return + + // Figure out the path to the config file + let sheetPath = sheet.file + let configPath = configFilePath + + let relativePath = path.relative(path.dirname(sheetPath), configPath) + if (relativePath[0] !== '.') { + relativePath = `./${relativePath}` + } + + // Inject the `@config` in a sensible place + let locationNode = null as AtRule | null + + walk(root, (node) => { + if (node.type === 'atrule' && (node.name === 'import' || node.name === 'theme')) { + locationNode = node + } + + return WalkAction.Skip + }) + + let configNode = new AtRule({ name: 'config', params: `"${relativePath}"` }) + + if (!locationNode) { + root.prepend(configNode) + } else if (locationNode.name === 'import') { + locationNode.after(configNode) + } else if (locationNode.name === 'theme') { + locationNode.before(configNode) + } + } + + return { + postcssPlugin: '@tailwindcss/upgrade/migrate-at-config', + OnceExit: migrate, + } +} diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts index fb5457774f88..d4763d375ee8 100644 --- a/packages/@tailwindcss-upgrade/src/index.test.ts +++ b/packages/@tailwindcss-upgrade/src/index.test.ts @@ -1,5 +1,6 @@ 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' @@ -13,7 +14,13 @@ let designSystem = await __unstable__loadDesignSystem( `, { base: __dirname }, ) -let config = { designSystem, userConfig: {}, newPrefix: null } + +let config = { + designSystem, + userConfig: {}, + newPrefix: null, + configFilePath: path.resolve(__dirname, './tailwind.config.js'), +} function migrate(input: string, config: any) { return migrateContents(input, config, expect.getState().testPath) @@ -87,6 +94,8 @@ it('should migrate a stylesheet', async () => { ).toMatchInlineSnapshot(` "@import 'tailwindcss'; + @config "./tailwind.config.js"; + @layer base { html { overflow: hidden; @@ -138,7 +147,8 @@ it('should migrate a stylesheet (with imports)', async () => { "@import 'tailwindcss'; @import './my-base.css' layer(base); @import './my-components.css' layer(components); - @import './my-utilities.css' layer(utilities);" + @import './my-utilities.css' layer(utilities); + @config "./tailwind.config.js";" `) }) @@ -163,6 +173,7 @@ 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"; @layer base { html { color: red; diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 0b32cace52c0..7ce0c1bb1f6a 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -5,6 +5,7 @@ import type { DesignSystem } from '../../tailwindcss/src/design-system' import { DefaultMap } from '../../tailwindcss/src/utils/default-map' import { segment } from '../../tailwindcss/src/utils/segment' import { migrateAtApply } from './codemods/migrate-at-apply' +import { migrateAtConfig } from './codemods/migrate-at-config' import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities' import { migrateMediaScreen } from './codemods/migrate-media-screen' import { migrateMissingLayers } from './codemods/migrate-missing-layers' @@ -17,6 +18,7 @@ export interface MigrateOptions { newPrefix: string | null designSystem: DesignSystem userConfig: Config + configFilePath: string } export async function migrateContents( @@ -35,6 +37,7 @@ export async function migrateContents( .use(migrateAtLayerUtilities(stylesheet)) .use(migrateMissingLayers()) .use(migrateTailwindDirectives(options)) + .use(migrateAtConfig(stylesheet, options)) .process(stylesheet.root, { from: stylesheet.file ?? undefined }) } diff --git a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts index c9667f455352..b76f8990b2c5 100644 --- a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts +++ b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts @@ -22,6 +22,7 @@ export async function prepareConfig( designSystem: DesignSystem globs: { base: string; pattern: string }[] userConfig: Config + configFilePath: string newPrefix: string | null }> { @@ -57,7 +58,13 @@ export async function prepareConfig( __unstable__loadDesignSystem(input, { base: __dirname }), ]) - return { designSystem, globs: compiler.globs, userConfig, newPrefix } + return { + designSystem, + globs: compiler.globs, + userConfig, + newPrefix, + configFilePath: fullConfigPath, + } } catch (e: any) { error('Could not load the configuration file: ' + e.message) process.exit(1) From ab7d224ce20eac3017d6ff9bb3194bf751e6fe09 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 11:35:32 +0200 Subject: [PATCH 02/15] tmp: run on windows --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d28a1e1e0660..bd64f77c5026 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: on-next-branch: - ${{ github.ref == 'refs/heads/next' }} exclude: - - on-next-branch: false + - on-next-branch: true runner: windows-latest - on-next-branch: false runner: macos-14 From 2dd6480a5efd791b0ac84918f734fe90b79bafce Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 11:42:38 +0200 Subject: [PATCH 03/15] update test to use the `dumpFiles` --- integrations/upgrade/index.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 40b0aa47129d..e4ba9d41011b 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -918,16 +918,12 @@ test( async ({ exec, fs }) => { await exec('npx @tailwindcss/upgrade --force') - await fs.expectFileToContain( - 'src/index.html', - html` -

🤠👋

-
- `, - ) - - expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(` + expect(await fs.dumpFiles('./src/**/*.{html,css}')).toMatchInlineSnapshot(` " + --- ./src/index.html --- +

🤠👋

+
+ --- ./src/input.css --- @import 'tailwindcss'; @config "../tailwind.config.ts";" From 107bfa91bc0c412d34389d2f35ddba464fd7e298 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 11:51:52 +0200 Subject: [PATCH 04/15] always use unix file paths with `/` --- .../@tailwindcss-upgrade/src/codemods/migrate-at-config.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index d3bb4b6dbef6..b77f051690a6 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -18,7 +18,6 @@ export function migrateAtConfig( if (hasConfig) return // We don't have a sheet with a file path - // TODO: Why? Tests? if (!sheet.file) return // Should this sheet have an `@config`? @@ -39,7 +38,7 @@ export function migrateAtConfig( let sheetPath = sheet.file let configPath = configFilePath - let relativePath = path.relative(path.dirname(sheetPath), configPath) + let relativePath = path.relative(path.dirname(sheetPath), configPath).replaceAll('\\', '/') if (relativePath[0] !== '.') { relativePath = `./${relativePath}` } From f79a6df7cdba7ffe3c10ac7e501aab39f4c58de7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:07:21 +0200 Subject: [PATCH 05/15] update test with more scenarios --- integrations/upgrade/index.test.ts | 71 ++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index e4ba9d41011b..cb2c997de9d4 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -908,11 +908,42 @@ test(

🤠👋

`, - 'src/input.css': css` + 'src/root.1.css': css` + /* Inject missing @config */ + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + 'src/root.2.css': css` + /* Already contains @config */ + @tailwind base; + @tailwind components; + @tailwind utilities; + @config "../tailwind.config.js"; + `, + 'src/root.3.css': css` + /* Inject missing @config above first @theme */ @tailwind base; @tailwind components; @tailwind utilities; + + @variant hocus (&:hover, &:focus); + + @theme { + --color-red-500: #f00; + } + + @theme { + --color-blue-500: #00f; + } + `, + 'src/root.4.css': css` + /* Inject missing @config due to nested imports with tailwind imports */ + @import './root.4/base.css'; + @import './root.4/utilities.css'; `, + 'src/root.4/base.css': css`@import 'tailwindcss/base';`, + 'src/root.4/utilities.css': css`@import 'tailwindcss/utilities';`, }, }, async ({ exec, fs }) => { @@ -924,9 +955,43 @@ test(

🤠👋

- --- ./src/input.css --- + --- ./src/root.1.css --- + /* Inject missing @config */ + @import 'tailwindcss'; + @config "../tailwind.config.ts"; + + --- ./src/root.2.css --- + /* Already contains @config */ + @import 'tailwindcss'; + @config "../tailwind.config.js"; + + --- ./src/root.3.css --- + /* Inject missing @config above first @theme */ @import 'tailwindcss'; - @config "../tailwind.config.ts";" + + @variant hocus (&:hover, &:focus); + + @config "../tailwind.config.ts"; + + @theme { + --color-red-500: #f00; + } + + @theme { + --color-blue-500: #00f; + } + + --- ./src/root.4.css --- + /* Inject missing @config due to nested imports with tailwind imports */ + @import './root.4/base.css'; + @import './root.4/utilities.css'; + + --- ./src/root.4/base.css --- + @import 'tailwindcss/theme' layer(theme); + @import 'tailwindcss/preflight' layer(base); + + --- ./src/root.4/utilities.css --- + @import 'tailwindcss/utilities' layer(utilities);" `) }, ) From 54e2fecda905f02e71cc96b9446b9a5e97106836 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:07:34 +0200 Subject: [PATCH 06/15] improve location node Inject the `@config` in a sensible place: 1. Above the first `@theme` 2. Below the last `@import` 3. At the top of the file --- .../src/codemods/migrate-at-config.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index b77f051690a6..ac966bec1466 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -24,10 +24,10 @@ export function migrateAtConfig( // 1. It should be a root CSS file if (sheet.parents.size > 0) return - // 2. It should include a `@import "tailwindcss"` + // 2. It should include an `@import "tailwindcss"` let hasTailwindImport = false root.walkAtRules('import', (node) => { - if (node.params.includes('tailwindcss')) { + if (node.params.match(/['"]tailwindcss\/?(.*?)['"]/)) { hasTailwindImport = true return false } @@ -44,10 +44,18 @@ export function migrateAtConfig( } // Inject the `@config` in a sensible place + // 1. Above the first `@theme` + // 2. Below the last `@import` + // 3. At the top of the file let locationNode = null as AtRule | null + let firstThemeNode = null as AtRule | null walk(root, (node) => { - if (node.type === 'atrule' && (node.name === 'import' || node.name === 'theme')) { + if (node.type === 'atrule' && node.name === 'theme' && firstThemeNode === null) { + firstThemeNode = node + locationNode = node + return WalkAction.Skip + } else if (node.type === 'atrule' && node.name === 'import') { locationNode = node } From 6536b7ced48a29085f645f2216cfb86830959ca7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:07:55 +0200 Subject: [PATCH 07/15] add missing known directives --- .../src/codemods/migrate-missing-layers.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts index 6d3de9fe6c33..35050b932968 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts @@ -10,7 +10,13 @@ export function migrateMissingLayers(): Plugin { root.each((node) => { if (node.type === 'atrule') { // Known Tailwind directives that should not be inside a layer. - if (node.name === 'theme' || node.name === 'utility') { + if ( + node.name === 'config' || + node.name === 'source' || + node.name === 'theme' || + node.name === 'utility' || + node.name === 'variant' + ) { if (bucket.length > 0) { buckets.push([lastLayer, bucket.splice(0)]) } From 037247aeebf4090da30a71b8ceba3bd86a542247 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:30:36 +0200 Subject: [PATCH 08/15] inject `@config` in root file If you have a setup where you have an index.css file, which in turn imports 2 new files with Tailwind CSS imports. In that case we want to inject the `@config` into the root, not in the individual files. Input: ``` --- ./src/root.4.css --- /* Inject missing @config due to nested imports with tailwind imports */ @import './root.4/base.css'; @import './root.4/utilities.css'; --- ./src/root.4/base.css --- @import 'tailwindcss/base'; --- ./src/root.4/utilities.css --- @import 'tailwindcss/utilities'; ``` Output: ``` --- ./src/root.4.css --- /* Inject missing @config due to nested imports with tailwind imports */ @import './root.4/base.css'; @import './root.4/utilities.css'; @config "../tailwind.config.ts"; --- ./src/root.4/base.css --- @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/preflight' layer(base); --- ./src/root.4/utilities.css --- @import 'tailwindcss/utilities' layer(utilities);" ``` --- integrations/upgrade/index.test.ts | 1 + .../src/codemods/migrate-at-config.ts | 60 ++++++++++++------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index cb2c997de9d4..b7cfcbe92162 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -985,6 +985,7 @@ test( /* Inject missing @config due to nested imports with tailwind imports */ @import './root.4/base.css'; @import './root.4/utilities.css'; + @config "../tailwind.config.ts"; --- ./src/root.4/base.css --- @import 'tailwindcss/theme' layer(theme); diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index ac966bec1466..b98b69c3e98c 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -7,32 +7,21 @@ export function migrateAtConfig( sheet: Stylesheet, { configFilePath }: { configFilePath: string }, ): Plugin { - function migrate(root: Root) { - let hasConfig = false - root.walkAtRules('config', () => { - hasConfig = true - return false - }) - - // We already have a `@config` - if (hasConfig) return + function injectInto(sheet: Stylesheet) { + let root = sheet.root // We don't have a sheet with a file path if (!sheet.file) return - // Should this sheet have an `@config`? - // 1. It should be a root CSS file - if (sheet.parents.size > 0) return - - // 2. It should include an `@import "tailwindcss"` - let hasTailwindImport = false - root.walkAtRules('import', (node) => { - if (node.params.match(/['"]tailwindcss\/?(.*?)['"]/)) { - hasTailwindImport = true + // Skip if there is already a `@config` directive + { + let hasConfig = false + root.walkAtRules('config', () => { + hasConfig = true return false - } - }) - if (!hasTailwindImport) return + }) + if (hasConfig) return + } // Figure out the path to the config file let sheetPath = sheet.file @@ -73,6 +62,35 @@ export function migrateAtConfig( } } + function migrate(root: Root) { + // We can only migrate if there is an `@import "tailwindcss"` (or sub-import) + let hasTailwindImport = false + root.walkAtRules('import', (node) => { + if (node.params.match(/['"]tailwindcss\/?(.*?)['"]/)) { + hasTailwindImport = true + return false + } + }) + + if (!hasTailwindImport) return + + // If we are not the root file, we need to inject the `@config` into the + // root file. + if (sheet.parents.size > 0) { + for (let parent of sheet.ancestors()) { + if (parent.parents.size === 0) { + injectInto(parent) + } + } + } + + // If it is the root file, we have to inject the `@config` into the current + // file. + else { + injectInto(sheet) + } + } + return { postcssPlugin: '@tailwindcss/upgrade/migrate-at-config', OnceExit: migrate, From d36a221b289426785ed633db6f544419bc0e1c0f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:35:10 +0200 Subject: [PATCH 09/15] use `normalizePath` This is the same setup as in `./packages/@tailwindcss-vite/src/index.ts` --- .../src/codemods/migrate-at-config.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index b98b69c3e98c..2c29b059a536 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -1,5 +1,6 @@ import path from 'node:path' import { AtRule, type Plugin, type Root } from 'postcss' +import { normalizePath } from '../../../@tailwindcss-node/src/normalize-path' import type { Stylesheet } from '../stylesheet' import { walk, WalkAction } from '../utils/walk' @@ -27,10 +28,13 @@ export function migrateAtConfig( let sheetPath = sheet.file let configPath = configFilePath - let relativePath = path.relative(path.dirname(sheetPath), configPath).replaceAll('\\', '/') - if (relativePath[0] !== '.') { - relativePath = `./${relativePath}` + let relative = path.relative(path.dirname(sheetPath), configPath) + if (relative[0] !== '.') { + relative = `./${relative}` } + // Ensure relative is a posix style path since we will merge it with the + // glob. + relative = normalizePath(relative) // Inject the `@config` in a sensible place // 1. Above the first `@theme` @@ -51,7 +55,7 @@ export function migrateAtConfig( return WalkAction.Skip }) - let configNode = new AtRule({ name: 'config', params: `"${relativePath}"` }) + let configNode = new AtRule({ name: 'config', params: `"${relative}"` }) if (!locationNode) { root.prepend(configNode) From 1e5fe27c6566efff2d919118da759c090e9ed796 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 12:54:29 +0200 Subject: [PATCH 10/15] inject `@config` if file has direct `@import "tailwindcss"` If a non-root file contains `@import "tailwindcss"` (without its individual imports), then we can inject the `@config` in this file as well. This makes it more co-located with where you setup Tailwind CSS --- integrations/upgrade/index.test.ts | 16 +++++++++++- .../src/codemods/migrate-at-config.ts | 25 ++++++++++++------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index b7cfcbe92162..ea01762bfeb6 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -944,6 +944,12 @@ test( `, 'src/root.4/base.css': css`@import 'tailwindcss/base';`, 'src/root.4/utilities.css': css`@import 'tailwindcss/utilities';`, + + 'src/root.5.css': css`@import './root.5/tailwind.css';`, + 'src/root.5/tailwind.css': css` + /* Inject missing @config in this file, due to full import */ + @import 'tailwindcss'; + `, }, }, async ({ exec, fs }) => { @@ -987,12 +993,20 @@ test( @import './root.4/utilities.css'; @config "../tailwind.config.ts"; + --- ./src/root.5.css --- + @import './root.5/tailwind.css'; + --- ./src/root.4/base.css --- @import 'tailwindcss/theme' layer(theme); @import 'tailwindcss/preflight' layer(base); --- ./src/root.4/utilities.css --- - @import 'tailwindcss/utilities' layer(utilities);" + @import 'tailwindcss/utilities' layer(utilities); + + --- ./src/root.5/tailwind.css --- + /* Inject missing @config in this file, due to full import */ + @import 'tailwindcss'; + @config "../../tailwind.config.ts";" `) }, ) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index 2c29b059a536..ba14ffd17a68 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -69,17 +69,30 @@ export function migrateAtConfig( function migrate(root: Root) { // We can only migrate if there is an `@import "tailwindcss"` (or sub-import) let hasTailwindImport = false + let hasFullTailwindImport = false root.walkAtRules('import', (node) => { - if (node.params.match(/['"]tailwindcss\/?(.*?)['"]/)) { + if (node.params.match(/['"]tailwindcss['"]/)) { hasTailwindImport = true + hasFullTailwindImport = true return false + } else if (node.params.match(/['"]tailwindcss\/.*?['"]/)) { + hasTailwindImport = true } }) if (!hasTailwindImport) return - // If we are not the root file, we need to inject the `@config` into the - // root file. + // - If a full `@import "tailwindcss"` is present, we can inject the + // `@config` directive directly into this stylesheet. + // - If we are the root file (no parents), then we can inject the `@config` + // directive directly into this file as well. + if (hasFullTailwindImport || sheet.parents.size <= 0) { + injectInto(sheet) + return + } + + // Otherwise, if we are not the root file, we need to inject the `@config` + // into the root file. if (sheet.parents.size > 0) { for (let parent of sheet.ancestors()) { if (parent.parents.size === 0) { @@ -87,12 +100,6 @@ export function migrateAtConfig( } } } - - // If it is the root file, we have to inject the `@config` into the current - // file. - else { - injectInto(sheet) - } } return { From 4759a4f282234798de369678c291fd4c8e550dd3 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 13:27:57 +0200 Subject: [PATCH 11/15] Revert "tmp: run on windows" This reverts commit d40e2306dc17f570f5138d7ad605a2c2fa29458c. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd64f77c5026..d28a1e1e0660 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: on-next-branch: - ${{ github.ref == 'refs/heads/next' }} exclude: - - on-next-branch: true + - on-next-branch: false runner: windows-latest - on-next-branch: false runner: macos-14 From 754190ed636a55cdfe4124497116399454e7d28f Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 13:30:40 +0200 Subject: [PATCH 12/15] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee4ea3aa1be..3b5434b0467c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596)) - _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600)) - _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603)) +- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635)) ## [4.0.0-alpha.26] - 2024-10-03 From f8c57e6a0c22ffeb273ceac038c69981041e955d Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 15:51:30 +0200 Subject: [PATCH 13/15] always inject after last `@import` --- integrations/upgrade/index.test.ts | 3 +-- .../src/codemods/migrate-at-config.ts | 14 +++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index ea01762bfeb6..2fa93aa12d10 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -974,11 +974,10 @@ test( --- ./src/root.3.css --- /* Inject missing @config above first @theme */ @import 'tailwindcss'; + @config "../tailwind.config.ts"; @variant hocus (&:hover, &:focus); - @config "../tailwind.config.ts"; - @theme { --color-red-500: #f00; } diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts index ba14ffd17a68..4e1f10663b27 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-config.ts @@ -37,18 +37,12 @@ export function migrateAtConfig( relative = normalizePath(relative) // Inject the `@config` in a sensible place - // 1. Above the first `@theme` - // 2. Below the last `@import` - // 3. At the top of the file + // 1. Below the last `@import` + // 2. At the top of the file let locationNode = null as AtRule | null - let firstThemeNode = null as AtRule | null walk(root, (node) => { - if (node.type === 'atrule' && node.name === 'theme' && firstThemeNode === null) { - firstThemeNode = node - locationNode = node - return WalkAction.Skip - } else if (node.type === 'atrule' && node.name === 'import') { + if (node.type === 'atrule' && node.name === 'import') { locationNode = node } @@ -61,8 +55,6 @@ export function migrateAtConfig( root.prepend(configNode) } else if (locationNode.name === 'import') { locationNode.after(configNode) - } else if (locationNode.name === 'theme') { - locationNode.before(configNode) } } From 20d8925769888106516bf24eeba1bcba0e4d3f84 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 15:56:58 +0200 Subject: [PATCH 14/15] =?UTF-8?q?ensure=20`dumpFiles(=E2=80=A6)`=20has=20a?= =?UTF-8?q?=20clean=20`\n`=20at=20the=20end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If not, some files could look weird: ``` @import "tailwind.css";" ``` Which feels wrong, but the last `"` is coming from the `toMatchInlineSnapshot()` function. --- integrations/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integrations/utils.ts b/integrations/utils.ts index ce700bce7f7a..2afc35ed038a 100644 --- a/integrations/utils.ts +++ b/integrations/utils.ts @@ -330,7 +330,8 @@ export function test( return a[0].localeCompare(z[0]) }) .map(([file, content]) => `--- ${file} ---\n${content || ''}`) - .join('\n\n')}` + .join('\n\n') + .trim()}\n` }, async expectFileToContain(filePath, contents) { return retryAssertion(async () => { From a200bbced378a93646001b3ef57841abc6376696 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Oct 2024 15:57:59 +0200 Subject: [PATCH 15/15] update snapshots to include `\n` --- integrations/upgrade/index.test.ts | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 2fa93aa12d10..b529d7a6299e 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -40,7 +40,8 @@ test( --- ./src/input.css --- @import 'tailwindcss'; - @config "../tailwind.config.js";" + @config "../tailwind.config.js"; + " `) let packageJsonContent = await fs.read('package.json') @@ -100,7 +101,8 @@ test( .btn { @apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white; - }" + } + " `) }, ) @@ -155,7 +157,8 @@ test( .c { @apply flex! flex-col! items-center!; - }" + } + " `) }, ) @@ -210,7 +213,8 @@ test( .btn { color: red; } - }" + } + " `) }, ) @@ -270,7 +274,8 @@ test( } -ms-overflow-style: none; scrollbar-width: none; - }" + } + " `) }, ) @@ -542,7 +547,8 @@ test(
--- ./src/other.html --- -
" +
+ " `) }, ) @@ -582,7 +588,8 @@ test(
--- ./src/other.html --- -
" +
+ " `) }, ) @@ -633,7 +640,8 @@ test( } -ms-overflow-style: none; scrollbar-width: none; - }" + } + " `) }, ) @@ -815,7 +823,8 @@ test( --- ./src/d.4.css --- @utility from-a-4 { color: blue; - }" + } + " `) }, ) @@ -883,7 +892,8 @@ test( --- ./src/root.3.css --- @import 'tailwindcss/utilities' layer(utilities); @import './a.1.css' layer(utilities); - @config "../tailwind.config.js";" + @config "../tailwind.config.js"; + " `) }, ) @@ -1005,7 +1015,8 @@ test( --- ./src/root.5/tailwind.css --- /* Inject missing @config in this file, due to full import */ @import 'tailwindcss'; - @config "../../tailwind.config.ts";" + @config "../../tailwind.config.ts"; + " `) }, )