Skip to content

Commit

Permalink
Migrate @media screen(…) (#14603)
Browse files Browse the repository at this point in the history
This PR adds a codemod that migrates the `@media screen(…)` to the
properly expanded `@media (…)` syntax.

```css
@media screen(md) {
  .foo {
    color: red;
  }
}
```

Will be converted to:
```css
@media (width >= 48rem) {
  .foo {
    color: red;
  }
}
```

If you happen to have custom screens (even complex ones), the screen
will be converted to a custom media query.

```css
@media screen(foo) {
  .foo {
    color: red;
  }
}
```
With a custom `tailwind.config.js` file with a config like this:
```js
module.exports = {
  // …
  theme: {
    screens: {
      foo: { min: '100px', max: '123px' },
    },
  }
}
```

Then the codemod will convert it to:
```css
@media (123px >= width >= 100px) {
  .foo {
    color: red;
  }
}
```
  • Loading branch information
RobinMalfait authored Oct 10, 2024
1 parent 958bfc9 commit 3ae22f1
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Pass options when using `addComponents` and `matchComponents` ([#14590](https://github.com/tailwindlabs/tailwindcss/pull/14590))
- _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
- _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600))
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))

## [4.0.0-alpha.26] - 2024-10-03

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { __unstable__loadDesignSystem } from '@tailwindcss/node'
import dedent from 'dedent'
import postcss from 'postcss'
import { expect, it } from 'vitest'
import type { UserConfig } from '../../../tailwindcss/src/compat/config/types'
import { formatNodes } from './format-nodes'
import { migrateMediaScreen } from './migrate-media-screen'

const css = dedent

async function migrate(input: string, userConfig: UserConfig = {}) {
return postcss()
.use(
migrateMediaScreen({
designSystem: await __unstable__loadDesignSystem(`@import 'tailwindcss';`, {
base: __dirname,
}),
userConfig,
}),
)
.use(formatNodes())
.process(input, { from: expect.getState().testPath })
.then((result) => result.css)
}

it('should migrate a built-in breakpoint', async () => {
expect(
await migrate(css`
@media screen(md) {
.foo {
color: red;
}
}
`),
).toMatchInlineSnapshot(`
"@media (width >= theme(--breakpoint-md)) {
.foo {
color: red;
}
}"
`)
})

it('should migrate a custom min-width screen (string)', async () => {
expect(
await migrate(
css`
@media screen(foo) {
.foo {
color: red;
}
}
`,
{
theme: {
screens: {
foo: '123px',
},
},
},
),
).toMatchInlineSnapshot(`
"@media (width >= theme(--breakpoint-foo)) {
.foo {
color: red;
}
}"
`)
})

it('should migrate a custom min-width screen (object)', async () => {
expect(
await migrate(
css`
@media screen(foo) {
.foo {
color: red;
}
}
`,
{
theme: {
screens: {
foo: { min: '123px' },
},
},
},
),
).toMatchInlineSnapshot(`
"@media (width >= theme(--breakpoint-foo)) {
.foo {
color: red;
}
}"
`)
})

it('should migrate a custom max-width screen', async () => {
expect(
await migrate(
css`
@media screen(foo) {
.foo {
color: red;
}
}
`,
{
theme: {
screens: {
foo: { max: '123px' },
},
},
},
),
).toMatchInlineSnapshot(`
"@media (123px >= width) {
.foo {
color: red;
}
}"
`)
})

it('should migrate a custom min and max-width screen', async () => {
expect(
await migrate(
css`
@media screen(foo) {
.foo {
color: red;
}
}
`,
{
theme: {
screens: {
foo: { min: '100px', max: '123px' },
},
},
},
),
).toMatchInlineSnapshot(`
"@media (123px >= width >= 100px) {
.foo {
color: red;
}
}"
`)
})

it('should migrate a raw media query', async () => {
expect(
await migrate(
css`
@media screen(foo) {
.foo {
color: red;
}
}
`,
{
theme: {
screens: {
foo: { raw: 'only screen and (min-width: 123px)' },
},
},
},
),
).toMatchInlineSnapshot(`
"@media only screen and (min-width: 123px) {
.foo {
color: red;
}
}"
`)
})
44 changes: 44 additions & 0 deletions packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { type Plugin, type Root } from 'postcss'
import type { Config } from 'tailwindcss'
import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config'
import { buildMediaQuery } from '../../../tailwindcss/src/compat/screens-config'
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
import { DefaultMap } from '../../../tailwindcss/src/utils/default-map'

export function migrateMediaScreen({
designSystem,
userConfig,
}: {
designSystem?: DesignSystem
userConfig?: Config
} = {}): Plugin {
function migrate(root: Root) {
if (!designSystem || !userConfig) return

let resolvedUserConfig = resolveConfig(designSystem, [{ base: '', config: userConfig }])
let screens = resolvedUserConfig?.theme?.screens || {}

let mediaQueries = new DefaultMap<string, string | null>((name) => {
let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name]
if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))`
return value ? buildMediaQuery(value) : null
})

root.walkAtRules((rule) => {
if (rule.name !== 'media') return

let screen = rule.params.match(/screen\(([^)]+)\)/)
if (!screen) return

let value = mediaQueries.get(screen[1])
if (!value) return

rule.params = value
})
}

return {
postcssPlugin: '@tailwindcss/upgrade/migrate-media-screen',
OnceExit: migrate,
}
}
2 changes: 2 additions & 0 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { formatNodes } from './codemods/format-nodes'
import { migrateAtApply } from './codemods/migrate-at-apply'
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
import { migrateMediaScreen } from './codemods/migrate-media-screen'
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'

Expand All @@ -18,6 +19,7 @@ export interface MigrateOptions {
export async function migrateContents(contents: string, options: MigrateOptions, file?: string) {
return postcss()
.use(migrateAtApply(options))
.use(migrateMediaScreen(options))
.use(migrateAtLayerUtilities())
.use(migrateMissingLayers())
.use(migrateTailwindDirectives(options))
Expand Down

0 comments on commit 3ae22f1

Please sign in to comment.