From 66a5a379d0fbe377ebfef2794f46c9a68ce5ff0b Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sat, 5 Oct 2024 01:46:38 +0200 Subject: [PATCH 1/6] =?UTF-8?q?migrate=20`screen(=E2=80=A6)`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/codemods/migrate-media-screen.test.ts | 150 ++++++++++++++++++ .../src/codemods/migrate-media-screen.ts | 43 +++++ packages/@tailwindcss-upgrade/src/migrate.ts | 2 + 3 files changed, 195 insertions(+) create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts create mode 100644 packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts new file mode 100644 index 000000000000..0878e0a7795f --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts @@ -0,0 +1,150 @@ +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 >= 48rem) { + .foo { + color: red; + } + }" + `) +}) + +it('should migrate a custom min-width screen', async () => { + expect( + await migrate( + css` + @media screen(foo) { + .foo { + color: red; + } + } + `, + { + theme: { + screens: { + foo: { min: '123px' }, + }, + }, + }, + ), + ).toMatchInlineSnapshot(` + "@media (width >= 123px) { + .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; + } + }" + `) +}) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts new file mode 100644 index 000000000000..3c2cf3c7f371 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts @@ -0,0 +1,43 @@ +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((name) => { + let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[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, + } +} diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 76990936b549..a47289b09f0e 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -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' @@ -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)) From 04b69b67a1ff556ab5ba6e9e832de2e49eb6dcb4 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Sat, 5 Oct 2024 01:53:04 +0200 Subject: [PATCH 2/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 067c06bf59a1..322f5eaea3e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From f8dd2eff93d0fb34e3befd282883aff22b5db4d8 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 7 Oct 2024 11:55:52 +0200 Subject: [PATCH 3/6] =?UTF-8?q?if=20we=20resolve=20to=20a=20string,=20use?= =?UTF-8?q?=20the=20`theme(=E2=80=A6)`=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In other cases, or using legacy JS object syntax, we can't use the `theme(…)` function, therefore we fully expand the media query in your CSS. --- .../@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts index 3c2cf3c7f371..d8343984a890 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts @@ -20,6 +20,7 @@ export function migrateMediaScreen({ let mediaQueries = new DefaultMap((name) => { let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name] + if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))` return value ? buildMediaQuery(value) : null }) From 4079c4ca0fb51ef68809e8cd2ecd4929a6150fe4 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 7 Oct 2024 12:00:02 +0200 Subject: [PATCH 4/6] update tests --- .../src/codemods/migrate-media-screen.test.ts | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts index 0878e0a7795f..b7370d4d1953 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts @@ -33,7 +33,7 @@ it('should migrate a built-in breakpoint', async () => { } `), ).toMatchInlineSnapshot(` - "@media (width >= 48rem) { + "@media (width >= theme(--breakpoint-md)) { .foo { color: red; } @@ -41,7 +41,34 @@ it('should migrate a built-in breakpoint', async () => { `) }) -it('should migrate a custom min-width screen', async () => { +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` @@ -60,7 +87,7 @@ it('should migrate a custom min-width screen', async () => { }, ), ).toMatchInlineSnapshot(` - "@media (width >= 123px) { + "@media (width >= theme(--breakpoint-foo)) { .foo { color: red; } From c60ab4dc85c7ee5a9f80c6a1f2eee5e75c23b676 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 7 Oct 2024 12:32:48 +0200 Subject: [PATCH 5/6] alternative solution: `@custom-media` --- .../src/codemods/migrate-media-screen.test.ts | 9 ++++++--- .../src/codemods/migrate-media-screen.ts | 13 +++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts index b7370d4d1953..8ec4ea4d4722 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts @@ -114,7 +114,8 @@ it('should migrate a custom max-width screen', async () => { }, ), ).toMatchInlineSnapshot(` - "@media (123px >= width) { + "@custom-media --breakpoint-foo ((123px >= width)); + @media (--breakpoint-foo) { .foo { color: red; } @@ -141,7 +142,8 @@ it('should migrate a custom min and max-width screen', async () => { }, ), ).toMatchInlineSnapshot(` - "@media (123px >= width >= 100px) { + "@custom-media --breakpoint-foo ((123px >= width >= 100px)); + @media (--breakpoint-foo) { .foo { color: red; } @@ -168,7 +170,8 @@ it('should migrate a raw media query', async () => { }, ), ).toMatchInlineSnapshot(` - "@media only screen and (min-width: 123px) { + "@custom-media --breakpoint-foo (only screen and (min-width: 123px)); + @media (--breakpoint-foo) { .foo { color: red; } diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts index d8343984a890..87460fa9a010 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts @@ -1,4 +1,4 @@ -import { type Plugin, type Root } from 'postcss' +import { AtRule, 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' @@ -21,7 +21,16 @@ export function migrateMediaScreen({ let mediaQueries = new DefaultMap((name) => { let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name] if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))` - return value ? buildMediaQuery(value) : null + + if (value) { + let query = buildMediaQuery(value) + root.prepend( + new AtRule({ name: 'custom-media', params: `--breakpoint-${name} (${query})` }), + ) + return `(--breakpoint-${name})` + } + + return null }) root.walkAtRules((rule) => { From dc8526940ca19e4994f073a8c49866a62eada8eb Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Mon, 7 Oct 2024 12:40:05 +0200 Subject: [PATCH 6/6] Revert "alternative solution: `@custom-media`" This reverts commit a1113f837b738a360da5d31b145771ad329be72a. This is way too experimental and no browser implements this feature yet. See: https://caniuse.com/?search=custom-media We can use lightningcss that transpiles is properly, but in environments such as the CDN we don't have access to lightningcss. --- .../src/codemods/migrate-media-screen.test.ts | 9 +++------ .../src/codemods/migrate-media-screen.ts | 13 ++----------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts index 8ec4ea4d4722..b7370d4d1953 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts @@ -114,8 +114,7 @@ it('should migrate a custom max-width screen', async () => { }, ), ).toMatchInlineSnapshot(` - "@custom-media --breakpoint-foo ((123px >= width)); - @media (--breakpoint-foo) { + "@media (123px >= width) { .foo { color: red; } @@ -142,8 +141,7 @@ it('should migrate a custom min and max-width screen', async () => { }, ), ).toMatchInlineSnapshot(` - "@custom-media --breakpoint-foo ((123px >= width >= 100px)); - @media (--breakpoint-foo) { + "@media (123px >= width >= 100px) { .foo { color: red; } @@ -170,8 +168,7 @@ it('should migrate a raw media query', async () => { }, ), ).toMatchInlineSnapshot(` - "@custom-media --breakpoint-foo (only screen and (min-width: 123px)); - @media (--breakpoint-foo) { + "@media only screen and (min-width: 123px) { .foo { color: red; } diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts index 87460fa9a010..d8343984a890 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts @@ -1,4 +1,4 @@ -import { AtRule, type Plugin, type Root } from 'postcss' +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' @@ -21,16 +21,7 @@ export function migrateMediaScreen({ let mediaQueries = new DefaultMap((name) => { let value = designSystem?.resolveThemeValue(`--breakpoint-${name}`) ?? screens?.[name] if (typeof value === 'string') return `(width >= theme(--breakpoint-${name}))` - - if (value) { - let query = buildMediaQuery(value) - root.prepend( - new AtRule({ name: 'custom-media', params: `--breakpoint-${name} (${query})` }), - ) - return `(--breakpoint-${name})` - } - - return null + return value ? buildMediaQuery(value) : null }) root.walkAtRules((rule) => {