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

Upgrade: Migrate spacing scale #14905

Merged
merged 10 commits into from
Nov 7, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Upgrade (experimental)_: Rename `drop-shadow` to `drop-shadow-sm` and `drop-shadow-sm` to `drop-shadow-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
- _Upgrade (experimental)_: Rename `rounded` to `rounded-sm` and `rounded-sm` to `rounded-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
- _Upgrade (experimental)_: Rename `blur` to `blur-sm` and `blur-sm` to `blur-xs` ([#14875](https://github.com/tailwindlabs/tailwindcss/pull/14875))
- _Upgrade (experimental)_: Migrate `theme()` usage and JS config files to use the new `--spacing` multiplier where possible ([#14905](https://github.com/tailwindlabs/tailwindcss/pull/14905))

### Fixed

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

test(
'migrates extended spacing keys',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'

export default {
content: ['./src/**/*.html'],
theme: {
extend: {
spacing: {
2: '0.5rem',
4.5: '1.125rem',
5.5: '1.375em', // Units are different from --spacing scale
13: '3.25rem',
100: '100px',
miami: '1337px',
},
},
},
} satisfies Config
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;

.container {
width: theme(spacing.2);
width: theme(spacing[4.5]);
width: theme(spacing[5.5]);
width: theme(spacing[13]);
width: theme(spacing[100]);
width: theme(spacing.miami);
}
`,
'src/index.html': html`
<div
class="[width:theme(spacing.2)]
[width:theme(spacing[4.5])]
[width:theme(spacing[5.5])]
[width:theme(spacing[13])]
[width:theme(spacing[100])]
[width:theme(spacing.miami)]"
></div>
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- src/index.html ---
<div
class="[width:calc(var(--spacing)*2)]
[width:calc(var(--spacing)*4.5)]
[width:var(--spacing-5_5)]
[width:calc(var(--spacing)*13)]
[width:var(--spacing-100)]
[width:var(--spacing-miami)]"
></div>

--- src/input.css ---
@import 'tailwindcss';

@theme {
--spacing-100: 100px;
--spacing-5_5: 1.375em;
--spacing-miami: 1337px;
}

/*
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);
}
}

/*
Form elements have a 1px border by default 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 \`border-0\` to
any form elements that shouldn't have a border.
*/
@layer base {
input:where(:not([type='button'], [type='reset'], [type='submit'])),
select,
textarea {
border-width: 0;
}
}

.container {
width: calc(var(--spacing) * 2);
width: calc(var(--spacing) * 4.5);
width: var(--spacing-5_5);
width: calc(var(--spacing) * 13);
width: var(--spacing-100);
width: var(--spacing-miami);
}
"
`)
},
)

test(
'retains overwriting spacing scale',
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.ts': ts`
import { type Config } from 'tailwindcss'

export default {
content: ['./src/**/*.html'],
theme: {
spacing: {
2: '0.5rem',
4.5: '1.125rem',
5.5: '1.375em',
13: '3.25rem',
100: '100px',
miami: '1337px',
},
},
} satisfies Config
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;

.container {
width: theme(spacing.2);
width: theme(spacing[4.5]);
width: theme(spacing[5.5]);
width: theme(spacing[13]);
width: theme(spacing[100]);
width: theme(spacing.miami);
}
`,
'src/index.html': html`
<div
class="[width:theme(spacing.2)]
[width:theme(spacing[4.5])]
[width:theme(spacing[5.5])]
[width:theme(spacing[13])]
[width:theme(spacing[100])]
[width:theme(spacing.miami)]"
></div>
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade')

expect(await fs.dumpFiles('src/**/*.{css,html}')).toMatchInlineSnapshot(`
"
--- src/index.html ---
<div
class="[width:var(--spacing-2)]
[width:var(--spacing-4_5)]
[width:var(--spacing-5_5)]
[width:var(--spacing-13)]
[width:var(--spacing-100)]
[width:var(--spacing-miami)]"
></div>

--- src/input.css ---
@import 'tailwindcss';

@theme {
--spacing-*: initial;
--spacing-2: 0.5rem;
--spacing-13: 3.25rem;
--spacing-100: 100px;
--spacing-4_5: 1.125rem;
--spacing-5_5: 1.375em;
--spacing-miami: 1337px;
}

/*
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);
}
}

/*
Form elements have a 1px border by default 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 \`border-0\` to
any form elements that shouldn't have a border.
*/
@layer base {
input:where(:not([type='button'], [type='reset'], [type='submit'])),
select,
textarea {
border-width: 0;
}
}

.container {
width: var(--spacing-2);
width: var(--spacing-4_5);
width: var(--spacing-5_5);
width: var(--spacing-13);
width: var(--spacing-100);
width: var(--spacing-miami);
}
"
`)
},
)
})
44 changes: 43 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import {
} 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 { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { escape } from '../../tailwindcss/src/utils/escape'
import { isValidSpacingMultiplier } from '../../tailwindcss/src/utils/infer-data-type'
import { findStaticPlugins, type StaticPluginOptions } from './utils/extract-static-plugins'
import { info } from './utils/renderer'

Expand Down Expand Up @@ -101,6 +102,8 @@ async function migrateTheme(
Array.from(replacedThemeKeys.entries()).map(([key]) => [key, false]),
)

removeUnnecessarySpacingKeys(designSystem, resolvedConfig, replacedThemeKeys)

let prevSectionKey = ''
let css = '\n@tw-bucket theme {\n'
css += `\n@theme {\n`
Expand Down Expand Up @@ -317,3 +320,42 @@ function patternSourceFiles(source: { base: string; pattern: string }): string[]
scanner.scan()
return scanner.files
}

function removeUnnecessarySpacingKeys(
designSystem: DesignSystem,
resolvedConfig: ResolvedConfig,
replacedThemeKeys: Set<string>,
) {
// We want to keep the spacing scale as-is if the user is overwriting
if (replacedThemeKeys.has('spacing')) return

// Ensure we have a spacing multiplier
let spacingScale = designSystem.theme.get(['--spacing'])
if (!spacingScale) return

let [spacingMultiplier, spacingUnit] = splitNumberAndUnit(spacingScale)
if (!spacingMultiplier || !spacingUnit) return

if (spacingScale && !replacedThemeKeys.has('spacing')) {
for (let [key, value] of Object.entries(resolvedConfig.theme.spacing ?? {})) {
let [multiplier, unit] = splitNumberAndUnit(value as string)
if (multiplier === null) continue

if (!isValidSpacingMultiplier(key)) continue
if (unit !== spacingUnit) continue

if (parseFloat(multiplier) === Number(key) * parseFloat(spacingMultiplier)) {
delete resolvedConfig.theme.spacing[key]
designSystem.theme.clearNamespace(escape(`--spacing-${key.replaceAll('.', '_')}`), 0)
}
}
}
}

function splitNumberAndUnit(value: string): [string, string] | [null, null] {
let match = value.match(/^([0-9.]+)(.*)$/)
if (!match) {
return [null, null]
}
return [match[1], match[2]]
}
Loading