Skip to content

Commit 5c1bfd3

Browse files
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}'; ```
1 parent 3da49f9 commit 5c1bfd3

File tree

4 files changed

+45
-13
lines changed

4 files changed

+45
-13
lines changed

CHANGELOG.md

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

1212
- _Upgrade (experimental)_: Migrate `plugins` with options to CSS ([#14700](https://github.com/tailwindlabs/tailwindcss/pull/14700))
1313

14+
### Changed
15+
16+
- _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))
17+
1418
## [4.0.0-alpha.28] - 2024-10-17
1519

1620
### Added

integrations/upgrade/index.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ test(
4040
4141
--- ./src/input.css ---
4242
@import 'tailwindcss';
43-
44-
@source './**/*.{html,js}';
4543
"
4644
`)
4745

@@ -100,8 +98,6 @@ test(
10098
--- ./src/input.css ---
10199
@import 'tailwindcss' prefix(tw);
102100
103-
@source './**/*.{html,js}';
104-
105101
.btn {
106102
@apply tw:rounded-md! tw:px-2 tw:py-1 tw:bg-blue-500 tw:text-white;
107103
}

integrations/upgrade/js-config.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'vitest'
2-
import { css, json, test, ts } from '../utils'
2+
import { css, html, json, test, ts } from '../utils'
33

44
test(
55
`upgrade JS config files with flat theme values, darkMode, and content fields`,
@@ -18,7 +18,7 @@ test(
1818
1919
module.exports = {
2020
darkMode: 'selector',
21-
content: ['./src/**/*.{html,js}', './my-app/**/*.{html,js}'],
21+
content: ['./src/**/*.{html,js}', './node_modules/my-external-lib/**/*.{html}'],
2222
theme: {
2323
boxShadow: {
2424
sm: '0 2px 6px rgb(15 23 42 / 0.08)',
@@ -72,6 +72,11 @@ test(
7272
@tailwind components;
7373
@tailwind utilities;
7474
`,
75+
'node_modules/my-external-lib/src/template.html': html`
76+
<div class="text-red-500">
77+
Hello world!
78+
</div>
79+
`,
7580
},
7681
},
7782
async ({ exec, fs }) => {
@@ -82,8 +87,7 @@ test(
8287
--- src/input.css ---
8388
@import 'tailwindcss';
8489
85-
@source './**/*.{html,js}';
86-
@source '../my-app/**/*.{html,js}';
90+
@source '../node_modules/my-external-lib/**/*.{html}';
8791
8892
@variant dark (&:where(.dark, .dark *));
8993

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

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { Scanner } from '@tailwindcss/oxide'
12
import fs from 'node:fs/promises'
23
import { dirname } from 'path'
3-
import type { Config } from 'tailwindcss'
4+
import { type Config } from 'tailwindcss'
45
import defaultTheme from 'tailwindcss/defaultTheme'
56
import { fileURLToPath } from 'url'
67
import { loadModule } from '../../@tailwindcss-node/src/compile'
@@ -54,7 +55,7 @@ export async function migrateJsConfig(
5455
}
5556

5657
if ('content' in unresolvedConfig) {
57-
sources = migrateContent(unresolvedConfig as any, base)
58+
sources = await migrateContent(unresolvedConfig as any, base)
5859
}
5960

6061
if ('theme' in unresolvedConfig) {
@@ -158,16 +159,31 @@ function createSectionKey(key: string[]): string {
158159
return sectionSegments.join('-')
159160
}
160161

161-
function migrateContent(
162+
async function migrateContent(
162163
unresolvedConfig: Config & { content: any },
163164
base: string,
164-
): { base: string; pattern: string }[] {
165+
): Promise<{ base: string; pattern: string }[]> {
166+
let autoContentFiles = autodetectedSourceFiles(base)
167+
165168
let sources = []
166169
for (let content of unresolvedConfig.content) {
167170
if (typeof content !== 'string') {
168171
throw new Error('Unsupported content value: ' + content)
169172
}
170-
sources.push({ base, pattern: content })
173+
174+
let sourceFiles = patternSourceFiles({ base, pattern: content })
175+
176+
let autoContentContainsAllSourceFiles = true
177+
for (let sourceFile of sourceFiles) {
178+
if (!autoContentFiles.includes(sourceFile)) {
179+
autoContentContainsAllSourceFiles = false
180+
break
181+
}
182+
}
183+
184+
if (!autoContentContainsAllSourceFiles) {
185+
sources.push({ base, pattern: content })
186+
}
171187
}
172188
return sources
173189
}
@@ -253,3 +269,15 @@ function keyframesToCss(keyframes: Record<string, unknown>): string {
253269
let ast: AstNode[] = keyframesToRules({ theme: { keyframes } })
254270
return toCss(ast).trim() + '\n'
255271
}
272+
273+
function autodetectedSourceFiles(base: string) {
274+
let scanner = new Scanner({ detectSources: { base } })
275+
scanner.scan()
276+
return scanner.files
277+
}
278+
279+
function patternSourceFiles(source: { base: string; pattern: string }): string[] {
280+
let scanner = new Scanner({ sources: [source] })
281+
scanner.scan()
282+
return scanner.files
283+
}

0 commit comments

Comments
 (0)