Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Remove spacing values from JS theme that can be computed from the def…
…ault scale
  • Loading branch information
philipp-spiess authored and adamwathan committed Nov 7, 2024
commit bc722b1d4d2863da902f12e751020fc91e89628d
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);
}
"
`)
},
)
})
45 changes: 44 additions & 1 deletion packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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'
Expand Down Expand Up @@ -101,6 +101,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 +319,44 @@ 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

let num = Number(key)
if (num < 0 || num % 0.25 !== 0 || String(num) !== key) continue

if (unit !== spacingUnit) continue

if (parseFloat(multiplier) === num * 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]]
}