Skip to content

Commit

Permalink
Add codemod and interop for legacy container component configu (#14999
Browse files Browse the repository at this point in the history
)

This PR adds support for handling v3 [`container` customizations
](https://tailwindcss.com/docs/container#customizing). This is done by
adding a custom utility to extend the core `container` utility. A
concrete example can be taken from the added integration test.

### Input

```ts
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./src/**/*.html'],
  theme: {
    container: {
      center: true,
      padding: {
        DEFAULT: '2rem',
        '2xl': '4rem',
      },
      screens: {
        md: '48rem', // Matches a default --breakpoint
        xl: '1280px',
        '2xl': '1536px',
      },
    },
  },
}
```

### Output

```css
@import "tailwindcss";

@Utility container {
  margin-inline: auto;
  padding-inline: 2rem;

  @media (width >= theme(--breakpoint-sm)) {
    max-width: none;
  }

  @media (width >= 48rem) {
    max-width: 48rem;
  }

  @media (width >= 1280px) {
    max-width: 1280px;
  }

  @media (width >= 1536px) {
    max-width: 1536px;
    padding-inline: 4rem;
  }
}
````


## Test Plan

This PR adds extensive tests to the compat layer as part of unit tests.
Additionally it does at a test to the codemod setup that shows that the
right `@utility` code is generated. Furthermore I compared the
implementation against v3 on both the compat layer and the custom
`@utility`:


https://github.com/user-attachments/assets/44d6cbfb-4861-4225-9593-602b719f628f
  • Loading branch information
philipp-spiess authored Nov 14, 2024
1 parent 4079059 commit 890f18d
Show file tree
Hide file tree
Showing 8 changed files with 834 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Support opacity values in increments of `0.25` by default ([#14980](https://github.com/tailwindlabs/tailwindcss/pull/14980))
- Support specifying the color interpolation method for gradients via modifier ([#14984](https://github.com/tailwindlabs/tailwindcss/pull/14984))
- Reintroduce `container` component as a utility ([#14993](https://github.com/tailwindlabs/tailwindcss/pull/14993))
- Reintroduce `container` component as a utility ([#14993](https://github.com/tailwindlabs/tailwindcss/pull/14993), [#14999](https://github.com/tailwindlabs/tailwindcss/pull/14999))
- _Upgrade (experimental)_: Migrate `container` component configuration to CSS ([#14999](https://github.com/tailwindlabs/tailwindcss/pull/14999))

### Fixed

Expand Down
93 changes: 93 additions & 0 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1435,4 +1435,97 @@ describe('border compatibility', () => {
`)
},
)

test(
'migrates `container` component configurations',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'
export default {
content: ['./src/**/*.html'],
theme: {
container: {
center: true,
padding: {
DEFAULT: '2rem',
'2xl': '4rem',
},
screens: {
md: '48rem', // Matches a default --breakpoint
xl: '1280px',
'2xl': '1536px',
},
},
},
} satisfies Config
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
'src/index.html': html`
<div class="container"></div>
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- src/index.html ---
<div class="container"></div>
--- src/input.css ---
@import 'tailwindcss';
@utility container {
margin-inline: auto;
padding-inline: 2rem;
@media (width >= theme(--breakpoint-sm)) {
max-width: none;
}
@media (width >= 48rem) {
max-width: 48rem;
}
@media (width >= 1280px) {
max-width: 1280px;
}
@media (width >= 1536px) {
max-width: 1536px;
padding-inline: 4rem;
}
}
/*
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
"
`)
},
)
})
11 changes: 10 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { fileURLToPath } from 'node:url'
import { type Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
import { loadModule } from '../../@tailwindcss-node/src/compile'
import { toCss, type AstNode } from '../../tailwindcss/src/ast'
import { atRule, toCss, type AstNode } from '../../tailwindcss/src/ast'
import {
keyPathToCssProperty,
themeableValues,
} from '../../tailwindcss/src/compat/apply-config-to-theme'
import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme'
import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config'
import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import { buildCustomContainerUtilityRules } from '../../tailwindcss/src/compat/container'
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { escape } from '../../tailwindcss/src/utils/escape'
Expand Down Expand Up @@ -148,6 +149,14 @@ async function migrateTheme(
}

css += '}\n' // @theme

if ('container' in resolvedConfig.theme) {
let rules = buildCustomContainerUtilityRules(resolvedConfig.theme.container, designSystem)
if (rules.length > 0) {
css += '\n' + toCss([atRule('@utility', 'container', rules)])
}
}

css += '}\n' // @tw-bucket

return css
Expand Down
2 changes: 2 additions & 0 deletions packages/tailwindcss/src/compat/apply-compat-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { applyKeyframesToTheme } from './apply-keyframes-to-theme'
import { createCompatConfig } from './config/create-compat-config'
import { resolveConfig } from './config/resolve-config'
import type { UserConfig } from './config/types'
import { registerContainerCompat } from './container'
import { darkModePlugin } from './dark-mode'
import { buildPluginApi, type CssPluginOptions, type Plugin } from './plugin-api'
import { registerScreensConfig } from './screens-config'
Expand Down Expand Up @@ -239,6 +240,7 @@ function upgradeToFullPluginSupport({

registerThemeVariantOverrides(resolvedUserConfig, designSystem)
registerScreensConfig(resolvedUserConfig, designSystem)
registerContainerCompat(resolvedUserConfig, designSystem)

// If a prefix has already been set in CSS don't override it
if (!designSystem.theme.prefix && resolvedConfig.prefix) {
Expand Down
3 changes: 3 additions & 0 deletions packages/tailwindcss/src/compat/apply-config-to-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ export function themeableValues(config: ResolvedConfig['theme']): [string[], unk
const IS_VALID_KEY = /^[a-zA-Z0-9-_%/\.]+$/

export function keyPathToCssProperty(path: string[]) {
// The legacy container component config should not be included in the Theme
if (path[0] === 'container') return null

path = structuredClone(path)

if (path[0] === 'animation') path[0] = 'animate'
Expand Down
Loading

0 comments on commit 890f18d

Please sign in to comment.