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}';
```
This commit is contained in:
Philipp Spiess 2024-10-18 15:48:56 +02:00 committed by GitHub
parent 3da49f9837
commit 5c1bfd3a91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 13 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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`
<div class="text-red-500">
Hello world!
</div>
`,
},
},
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 *));

View File

@ -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, unknown>): 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
}