Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Address follow-up work for #14639 #14650

Merged
merged 14 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
- _Upgrade (experimental)_: Automatically discover JavaScript config files ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643))
- _Upgrade (experimental)_: Fully convert simple JS configs to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639))
- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639))
- _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))
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))
Expand Down
195 changes: 193 additions & 2 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'vitest'
import { css, json, test, ts } from '../utils'

test(
`upgrades a simple JS config file to CSS`,
`upgrade JS config files with flat theme values, darkMode, and content fields`,
{
fs: {
'package.json': json`
Expand Down Expand Up @@ -103,7 +103,198 @@ test(
)

test(
`does not upgrade a complex JS config file to CSS`,
'does not upgrade JS config files with functions in the theme config',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'

export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,ts}')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';
@config '../tailwind.config.ts';
"
`)

expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
"
--- tailwind.config.ts ---
import { type Config } from 'tailwindcss'

export default {
theme: {
extend: {
colors: ({ colors }) => ({
gray: colors.neutral,
}),
},
},
} satisfies Config
"
`)
},
)

test(
'does not upgrade JS config files with typography styles in the theme config',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we specifically check for typography or do we bail on any complex object under theme?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bails out right now because it's using a function inside theme.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it so should that be how the test is named then since it's not really about typography stuff? It's just testing another heuristic right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also do we have a heuristic that prevents deeply nested objects as theme values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also do we have a heuristic that prevents deeply nested objects as theme values?

I don't think we do, but this is what this should eventually be, I think. I think in the compat layer right now these typography values would be added to the Theme, I did not see any heuristic trying to remove it.

We'll need to be able to support functions in the theme object and when we do we have to figure out how to still not add stuff for typography. That's really the two differences here.

Right now that test is not different from the one above "does not upgrade JS config files with functions in the theme config", so we can drop it. I just wanted to be explicit about this case since when we add support for the one above, we probably don't want to forget that this should not do weirdly nested stuff now.

{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'
import typographyStyles from './typography'

export default {
theme: {
typography: typographyStyles,
},
} satisfies Config
`,
'typography.ts': ts`
import { type PluginUtils } from 'tailwindcss/types/config'

export default function typographyStyles({ theme }: PluginUtils) {
return {
DEFAULT: {
css: {
'--tw-prose-body': theme('colors.zinc.600'),
color: 'var(--tw-prose-body)',
},
},
}
}
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@config '../tailwind.config.ts';
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';
@config '../tailwind.config.ts';
"
`)

expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
"
--- tailwind.config.ts ---
import { type Config } from 'tailwindcss'
import typographyStyles from './typography'

export default {
theme: {
typography: typographyStyles,
},
} satisfies Config
"
`)
},
)

test(
'does not upgrade JS config files with static plugins',
philipp-spiess marked this conversation as resolved.
Show resolved Hide resolved
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import customPlugin from './custom-plugin'

export default {
plugins: [typography, customPlugin],
} satisfies Config
`,
'custom-plugin.js': ts`
export default function ({ addVariant }) {
addVariant('inverted', '@media (inverted-colors: inverted)')
addVariant('hocus', ['&:focus', '&:hover'])
}
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';
@config '../tailwind.config.ts';
"
`)

expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
"
--- tailwind.config.ts ---
import { type Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'
import customPlugin from './custom-plugin'

export default {
plugins: [typography, customPlugin],
} satisfies Config
"
`)
},
)

test(
`does not upgrade JS config files with dynamic plugins`,
{
fs: {
'package.json': json`
Expand Down
8 changes: 3 additions & 5 deletions packages/@tailwindcss-node/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,8 @@ async function importModule(path: string): Promise<any> {
try {
return await import(path)
} catch (error) {
try {
jiti ??= createJiti(import.meta.url, { moduleCache: false, fsCache: false })
return await jiti.import(path)
} catch {}
throw error
jiti ??= createJiti(import.meta.url, { moduleCache: false, fsCache: false })
return await jiti.import(path)
}
philipp-spiess marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -144,6 +141,7 @@ async function resolveCssId(id: string, base: string): Promise<string | false |
const jsResolver = EnhancedResolve.ResolverFactory.createResolver({
fileSystem: new EnhancedResolve.CachedInputFileSystem(fs, 4000),
useSyncFileSystemCalls: true,
extensions: ['.js', '.json', '.node', '.ts'],
philipp-spiess marked this conversation as resolved.
Show resolved Hide resolved
})

function resolveJsId(id: string, base: string): Promise<string | false | undefined> {
Expand Down
14 changes: 6 additions & 8 deletions packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ export function migrateConfig(
cssConfig.append(postcss.parse(css + jsConfigMigration.css))
}

// Inject the `@config` in a sensible place
// 1. Below the last `@import`
// 2. At the top of the file
// Inject the `@config` directive after the last `@import` or at the
// top of the file if no `@import` rules are present
let locationNode = null as AtRule | null

walk(root, (node) => {
Expand Down Expand Up @@ -99,10 +98,9 @@ export function migrateConfig(

if (!hasTailwindImport) return

// - 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 a full `@import "tailwindcss"` is present or this is the root
// stylesheet, we can inject the `@config` directive directly into this
// file.
if (hasFullTailwindImport || sheet.parents.size <= 0) {
injectInto(sheet)
return
Expand Down Expand Up @@ -134,7 +132,7 @@ function relativeToStylesheet(sheet: Stylesheet, absolute: string) {
if (relative[0] !== '.') {
relative = `./${relative}`
}
// Ensure relative is a posix style path since we will merge it with the
// Ensure relative is a POSIX style path since we will merge it with the
// glob.
return normalizePath(relative)
}
12 changes: 6 additions & 6 deletions packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export async function migrateJsConfig(
fs.readFile(fullConfigPath, 'utf-8'),
])

if (!isSimpleConfig(unresolvedConfig, source)) {
if (!canMigrateConfig(unresolvedConfig, source)) {
info(
'The configuration file is not a simple object. Please refer to the migration guide for how to migrate it fully to Tailwind CSS v4. For now, we will load the configuration file as-is.',
'Your configuration file could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file.',
)
return null
}
Expand Down Expand Up @@ -63,8 +63,8 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<
let { extend: extendTheme, ...overwriteTheme } = unresolvedConfig.theme

let resetNamespaces = new Map<string, boolean>()
// Before we merge the resetting theme values with the `extend` values, we
// capture all namespaces that need to be reset
// Before we merge theme overrides with theme extensions, we capture all
// namespaces that need to be reset.
for (let [key, value] of themeableValues(overwriteTheme)) {
if (typeof value !== 'string' && typeof value !== 'number') {
continue
Expand Down Expand Up @@ -119,7 +119,7 @@ function createSectionKey(key: string[]): string {
let sectionSegments = []
for (let i = 0; i < key.length - 1; i++) {
let segment = key[i]
// ignore tuples
// Ignore tuples
if (key[i + 1][0] === '-') {
break
}
Expand All @@ -143,7 +143,7 @@ function migrateContent(
}

// Applies heuristics to determine if we can attempt to migrate the config
function isSimpleConfig(unresolvedConfig: Config, source: string): boolean {
function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
// The file may not contain any functions
if (source.includes('function') || source.includes(' => ')) {
return false
Expand Down
3 changes: 3 additions & 0 deletions packages/tailwindcss/src/compat/config/resolve-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DesignSystem } from '../../design-system'
import colors from '../colors'
import type { PluginWithConfig } from '../plugin-api'
import { createThemeFn } from '../plugin-functions'
import { deepMerge, isPlainObject } from './deep-merge'
Expand Down Expand Up @@ -117,6 +118,7 @@ export function mergeThemeExtension(

export interface PluginUtils {
theme(keypath: string, defaultValue?: any): any
colors: typeof colors
}

function extractConfigs(ctx: ResolutionContext, { config, base, path }: ConfigFile): void {
Expand Down Expand Up @@ -176,6 +178,7 @@ function extractConfigs(ctx: ResolutionContext, { config, base, path }: ConfigFi
function mergeTheme(ctx: ResolutionContext) {
let api: PluginUtils = {
theme: createThemeFn(ctx.design, () => ctx.theme, resolveValue),
colors,
philipp-spiess marked this conversation as resolved.
Show resolved Hide resolved
}

function resolveValue(value: ThemeValue | null | undefined): ResolvedThemeValue {
Expand Down