From 5c1bfd3a915631e12dfbb5f200ea6cf2e23104ec Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 18 Oct 2024 15:48:56 +0200 Subject: [PATCH] `content` rules from the JS config that are also covered by the automatic source detection should not be migrated to CSS (#14714) This PR changes the migration of `content` rules in the JS config to CSS codemods. When a `content` rule is processed which matches files that are _also matched by the automatic content discovery in v4_, we do not need to emit CSS for that rule. Take, for example this v3 configuration file: ```ts import { type Config } from 'tailwindcss' module.exports = { content: [ './src/**/*.{html,js}', './node_modules/my-external-lib/**/*.{html}' ], } satisfies Config ``` Provided the base directories match up, the first rule will also be covered by the automatic content discovery in v4 and thus we only need to convert the second rule to CSS: ```css @import "tailwindcss"; @source '../node_modules/my-external-lib/**/*.{html}'; ``` --- CHANGELOG.md | 4 ++ integrations/upgrade/index.test.ts | 4 -- integrations/upgrade/js-config.test.ts | 12 ++++-- .../src/migrate-js-config.ts | 38 ++++++++++++++++--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9b5f637..de378c91a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - _Upgrade (experimental)_: Migrate `plugins` with options to CSS ([#14700](https://github.com/tailwindlabs/tailwindcss/pull/14700)) +### Changed + +- _Upgrade (experimental)_: Don't create `@source` rules for `content` paths that are already covered by automatic source detection ([#14714](https://github.com/tailwindlabs/tailwindcss/pull/14714)) + ## [4.0.0-alpha.28] - 2024-10-17 ### Added diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 97409d53d..948c0a9e9 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -40,8 +40,6 @@ test( --- ./src/input.css --- @import 'tailwindcss'; - - @source './**/*.{html,js}'; " `) @@ -100,8 +98,6 @@ test( --- ./src/input.css --- @import 'tailwindcss' prefix(tw); - @source './**/*.{html,js}'; - .btn { @apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white; } diff --git a/integrations/upgrade/js-config.test.ts b/integrations/upgrade/js-config.test.ts index aa4088d00..ef7a188e1 100644 --- a/integrations/upgrade/js-config.test.ts +++ b/integrations/upgrade/js-config.test.ts @@ -1,5 +1,5 @@ import { expect } from 'vitest' -import { css, json, test, ts } from '../utils' +import { css, html, json, test, ts } from '../utils' test( `upgrade JS config files with flat theme values, darkMode, and content fields`, @@ -18,7 +18,7 @@ test( module.exports = { darkMode: 'selector', - content: ['./src/**/*.{html,js}', './my-app/**/*.{html,js}'], + content: ['./src/**/*.{html,js}', './node_modules/my-external-lib/**/*.{html}'], theme: { boxShadow: { sm: '0 2px 6px rgb(15 23 42 / 0.08)', @@ -72,6 +72,11 @@ test( @tailwind components; @tailwind utilities; `, + 'node_modules/my-external-lib/src/template.html': html` +
+ Hello world! +
+ `, }, }, async ({ exec, fs }) => { @@ -82,8 +87,7 @@ test( --- src/input.css --- @import 'tailwindcss'; - @source './**/*.{html,js}'; - @source '../my-app/**/*.{html,js}'; + @source '../node_modules/my-external-lib/**/*.{html}'; @variant dark (&:where(.dark, .dark *)); diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts index 19e030556..3c7818902 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/migrate-js-config.ts @@ -1,6 +1,7 @@ +import { Scanner } from '@tailwindcss/oxide' import fs from 'node:fs/promises' import { dirname } from 'path' -import type { Config } from 'tailwindcss' +import { type Config } from 'tailwindcss' import defaultTheme from 'tailwindcss/defaultTheme' import { fileURLToPath } from 'url' import { loadModule } from '../../@tailwindcss-node/src/compile' @@ -54,7 +55,7 @@ export async function migrateJsConfig( } if ('content' in unresolvedConfig) { - sources = migrateContent(unresolvedConfig as any, base) + sources = await migrateContent(unresolvedConfig as any, base) } if ('theme' in unresolvedConfig) { @@ -158,16 +159,31 @@ function createSectionKey(key: string[]): string { return sectionSegments.join('-') } -function migrateContent( +async function migrateContent( unresolvedConfig: Config & { content: any }, base: string, -): { base: string; pattern: string }[] { +): Promise<{ base: string; pattern: string }[]> { + let autoContentFiles = autodetectedSourceFiles(base) + let sources = [] for (let content of unresolvedConfig.content) { if (typeof content !== 'string') { throw new Error('Unsupported content value: ' + content) } - sources.push({ base, pattern: content }) + + let sourceFiles = patternSourceFiles({ base, pattern: content }) + + let autoContentContainsAllSourceFiles = true + for (let sourceFile of sourceFiles) { + if (!autoContentFiles.includes(sourceFile)) { + autoContentContainsAllSourceFiles = false + break + } + } + + if (!autoContentContainsAllSourceFiles) { + sources.push({ base, pattern: content }) + } } return sources } @@ -253,3 +269,15 @@ function keyframesToCss(keyframes: Record): string { let ast: AstNode[] = keyframesToRules({ theme: { keyframes } }) return toCss(ast).trim() + '\n' } + +function autodetectedSourceFiles(base: string) { + let scanner = new Scanner({ detectSources: { base } }) + scanner.scan() + return scanner.files +} + +function patternSourceFiles(source: { base: string; pattern: string }): string[] { + let scanner = new Scanner({ sources: [source] }) + scanner.scan() + return scanner.files +}