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
Append the generated CSS at the right place
  • Loading branch information
philipp-spiess committed Oct 11, 2024
commit 4acd80338b0931a1bd62793e9f0910096e69de7e
39 changes: 36 additions & 3 deletions integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,42 @@ test(
},
},
async ({ exec, fs }) => {
console.log(await exec('npx @tailwindcss/upgrade'))
await exec('npx @tailwindcss/upgrade')

await fs.expectFileToContain('src/input.css', css` @import 'tailwindcss'; `)
expect(fs.read('tailwind.config.ts')).rejects.toMatchInlineSnapshot()
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
"
--- src/input.css ---
@import 'tailwindcss';

@source './**/*.{html,js}';

@variant dark (&:where(.dark, .dark *));

@theme {
--box-shadow-*: initial;
--box-shadow-sm: 0 2px 6px rgb(15 23 42 / 0.08);

--color-*: initial;
--color-red-500: #ef4444;

--font-size-*: initial;
--font-size-xs: 0.75rem;
--font-size-xs--line-height: 1rem;
--font-size-sm: 0.875rem;
--font-size-sm--line-height: 1.5rem;
--font-size-base: 1rem;
--font-size-base--line-height: 2rem;

--color-red-600: #dc2626;

--font-family-sans: Inter, system-ui, sans-serif;
--font-family-display: Cabinet Grotesk, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";

--border-radius-4xl: 2rem;
}
"
`)

expect((await fs.dumpFiles('tailwind.config.ts')).trim()).toBe('')
},
)
2 changes: 1 addition & 1 deletion integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function test(
) {
return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)(
name,
{ timeout: TEST_TIMEOUT, retry: debug || only ? 0 : 3 },
{ timeout: TEST_TIMEOUT, retry: process.env.CI ? 2 : 0 },
async (options) => {
let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT
await fs.mkdir(rootDir, { recursive: true })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,60 @@
import path from 'node:path'
import { AtRule, type Plugin, type Root } from 'postcss'
import { AtRule, type Plugin, Root } from 'postcss'
import { normalizePath } from '../../../@tailwindcss-node/src/normalize-path'
import type { JSConfigMigration } from '../migrate-js-config'
import type { Stylesheet } from '../stylesheet'
import { walk, WalkAction } from '../utils/walk'

export function migrateAtConfig(
export function migrateConfig(
sheet: Stylesheet,
{ configFilePath }: { configFilePath: string },
{
configFilePath,
jsConfigMigration,
}: { configFilePath: string; jsConfigMigration: JSConfigMigration },
): Plugin {
function injectInto(sheet: Stylesheet) {
let root = sheet.root

// We don't have a sheet with a file path
if (!sheet.file) return

// Skip if there is already a `@config` directive
{
let hasConfig = false
root.walkAtRules('config', () => {
hasConfig = true
return false
})
if (hasConfig) return
}
let cssConfig = new AtRule()
cssConfig.raws.tailwind_pretty = true

if (jsConfigMigration === null) {
// Skip if there is already a `@config` directive
{
let hasConfig = false
root.walkAtRules('config', () => {
hasConfig = true
return false
})
if (hasConfig) return
}

// Figure out the path to the config file
let sheetPath = sheet.file
let configPath = configFilePath
cssConfig.append(
new AtRule({
name: 'config',
params: `'${relativeToStylesheet(sheet, configFilePath)}'`,
}),
)
} else {
for (let source of jsConfigMigration.sources) {
let absolute = path.resolve(source.base, source.pattern)
cssConfig.append(
new AtRule({
name: 'source',
params: `'${relativeToStylesheet(sheet, absolute)}'`,
}),
)
}

if (jsConfigMigration.css.nodes.length > 0 && jsConfigMigration.sources.length > 0) {
jsConfigMigration.css.nodes[0].raws.before = '\n\n'
}

let relative = path.relative(path.dirname(sheetPath), configPath)
if (relative[0] !== '.') {
relative = `./${relative}`
cssConfig.append(jsConfigMigration.css.nodes)
}
// Ensure relative is a posix style path since we will merge it with the
// glob.
relative = normalizePath(relative)

// Inject the `@config` in a sensible place
// 1. Below the last `@import`
Expand All @@ -49,12 +69,10 @@ export function migrateAtConfig(
return WalkAction.Skip
})

let configNode = new AtRule({ name: 'config', params: `'${relative}'` })

if (!locationNode) {
root.prepend(configNode)
root.prepend(cssConfig.nodes)
} else if (locationNode.name === 'import') {
locationNode.after(configNode)
locationNode.after(cssConfig.nodes)
}
}

Expand Down Expand Up @@ -95,7 +113,21 @@ export function migrateAtConfig(
}

return {
postcssPlugin: '@tailwindcss/upgrade/migrate-at-config',
postcssPlugin: '@tailwindcss/upgrade/migrate-config',
OnceExit: migrate,
}
}

function relativeToStylesheet(sheet: Stylesheet, absolute: string) {
if (!sheet.file) throw new Error('Can not find a path for the stylesheet')

let sheetPath = sheet.file

let relative = path.relative(path.dirname(sheetPath), absolute)
if (relative[0] !== '.') {
relative = `./${relative}`
}
// Ensure relative is a posix style path since we will merge it with the
// glob.
return normalizePath(relative)
}
26 changes: 15 additions & 11 deletions packages/@tailwindcss-upgrade/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ if (flags['--help']) {
}

async function run() {
let base = process.cwd()

eprintln(header())
eprintln()

Expand All @@ -51,7 +53,7 @@ async function run() {
}
}

let config = await prepareConfig(flags['--config'], { base: process.cwd() })
let config = await prepareConfig(flags['--config'], { base })

{
// Template migrations
Expand Down Expand Up @@ -82,19 +84,16 @@ async function run() {
success('Template migration complete.')
}

{
// Migrate JS config

info('Migrating JavaScript configuration files using the provided configuration file.')
// Migrate JS config

await migrateJsConfig(config.configFilePath)
}
info('Migrating JavaScript configuration files using the provided configuration file.')
let jsConfigMigration = await migrateJsConfig(config.configFilePath, base)

{
// Stylesheet migrations

// Use provided files
let files = flags._.map((file) => path.resolve(process.cwd(), file))
let files = flags._.map((file) => path.resolve(base, file))

// Discover CSS files in case no files were provided
if (files.length === 0) {
Expand Down Expand Up @@ -134,7 +133,7 @@ async function run() {

// Migrate each file
let migrateResults = await Promise.allSettled(
stylesheets.map((sheet) => migrateStylesheet(sheet, config)),
stylesheets.map((sheet) => migrateStylesheet(sheet, { ...config, jsConfigMigration })),
)

for (let result of migrateResults) {
Expand Down Expand Up @@ -167,14 +166,19 @@ async function run() {

{
// PostCSS config migration
await migratePostCSSConfig(process.cwd())
await migratePostCSSConfig(base)
}

try {
// Upgrade Tailwind CSS
await pkg('add tailwindcss@next', process.cwd())
await pkg('add tailwindcss@next', base)
} catch {}

// Remove the JS config if it was fully migrated
if (jsConfigMigration !== null) {
await fs.rm(config.configFilePath)
}

// Figure out if we made any changes
if (isRepoDirty()) {
success('Verify the changes and commit them to your repository.')
Expand Down
37 changes: 27 additions & 10 deletions packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'node:fs/promises'
import { dirname } from 'path'
import postcss from 'postcss'
import type { Config } from 'tailwindcss'
import { fileURLToPath } from 'url'
import { loadModule } from '../../@tailwindcss-node/src/compile'
Expand All @@ -13,7 +14,17 @@ import { info } from './utils/renderer'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

export async function migrateJsConfig(fullConfigPath: string): Promise<void> {
export type JSConfigMigration =
// Could not convert the config file, need to inject it as-is in a @config directive
null | {
sources: { base: string; pattern: string }[]
css: postcss.Root
}

export async function migrateJsConfig(
fullConfigPath: string,
base: string,
): Promise<JSConfigMigration> {
let [unresolvedConfig, source] = await Promise.all([
loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config,
fs.readFile(fullConfigPath, 'utf-8'),
Expand All @@ -23,24 +34,28 @@ export async function migrateJsConfig(fullConfigPath: string): Promise<void> {
info(
'The configuration file is not a simple object. Please refer to the migration guide for how to migrate it fully to Tailwind CSS v4. For now, we will load the configuration file as-is.',
)
return
return null
}

let sources: { base: string; pattern: string }[] = []
let cssConfigs: string[] = []

if ('darkMode' in unresolvedConfig) {
cssConfigs.push(migrateDarkMode(unresolvedConfig as any))
}

if ('content' in unresolvedConfig) {
cssConfigs.push(migrateContent(unresolvedConfig as any))
sources = migrateContent(unresolvedConfig as any, base)
}

if ('theme' in unresolvedConfig) {
cssConfigs.push(await migrateTheme(unresolvedConfig as any))
}

console.log(cssConfigs.join('\n'))
return {
sources,
css: postcss.parse(cssConfigs.join('\n')),
}
}

async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<string> {
Expand All @@ -49,7 +64,7 @@ async function migrateTheme(unresolvedConfig: Config & { theme: any }): Promise<
let resetNamespaces = new Set()
let prevSectionKey = ''

let css = `@theme reference inline {\n`
let css = `@theme {`
for (let [key, value] of themeableValues(overwriteTheme)) {
if (typeof value !== 'string' && typeof value !== 'number') {
continue
Expand Down Expand Up @@ -112,16 +127,18 @@ function createSectionKey(key: string[]): string {
return sectionSegments.join('-')
}

function migrateContent(unresolvedConfig: Config & { content: any }): string {
let css = ''
function migrateContent(
unresolvedConfig: Config & { content: any },
base: string,
): { base: string; pattern: string }[] {
let sources = []
for (let content of unresolvedConfig.content) {
if (typeof content !== 'string') {
throw new Error('Unsupported content value: ' + content)
}

css += `@source "${content}";\n`
sources.push({ base, pattern: content })
}
return css
return sources
}

// Applies heuristics to determine if we can attempt to migrate the config
Expand Down
6 changes: 4 additions & 2 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import type { DesignSystem } from '../../tailwindcss/src/design-system'
import { DefaultMap } from '../../tailwindcss/src/utils/default-map'
import { segment } from '../../tailwindcss/src/utils/segment'
import { migrateAtApply } from './codemods/migrate-at-apply'
import { migrateAtConfig } from './codemods/migrate-at-config'
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
import { migrateConfig } from './codemods/migrate-config'
import { migrateMediaScreen } from './codemods/migrate-media-screen'
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'
import type { JSConfigMigration } from './migrate-js-config'
import { Stylesheet, type StylesheetConnection, type StylesheetId } from './stylesheet'
import { resolveCssId } from './utils/resolve'
import { walk, WalkAction } from './utils/walk'
Expand All @@ -19,6 +20,7 @@ export interface MigrateOptions {
designSystem: DesignSystem
userConfig: Config
configFilePath: string
jsConfigMigration: JSConfigMigration
}

export async function migrateContents(
Expand All @@ -37,7 +39,7 @@ export async function migrateContents(
.use(migrateAtLayerUtilities(stylesheet))
.use(migrateMissingLayers())
.use(migrateTailwindDirectives(options))
.use(migrateAtConfig(stylesheet, options))
.use(migrateConfig(stylesheet, options))
.process(stylesheet.root, { from: stylesheet.file ?? undefined })
}

Expand Down