Skip to content

Commit

Permalink
Register migrateImport to ensure it actually runs (#14769)
Browse files Browse the repository at this point in the history
This PR makes sure the `migrateImport` codemod is properly registered so
that it runs as part of the upgrade process.

## Test plan

This PR adds a new `v3` playground with an `upgrade` script that you can
use to run the upgrade from the local package. When you add a
non-prefixed `@import` to the v3 example, the paths are now properly
updated with no errors logged:


https://github.com/user-attachments/assets/85949bbb-756b-4ee2-8ac0-234fe1b2ca39

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
  • Loading branch information
3 people authored Oct 24, 2024
1 parent d643d79 commit 39cfcfa
Show file tree
Hide file tree
Showing 16 changed files with 1,145 additions and 80 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Ensure individual logical property utilities are sorted later than left/right pair utilities ([#14777](https://github.com/tailwindlabs/tailwindcss/pull/14777))
- _Upgrade (experimental)_: Ensure `@import` statements for relative CSS files are actually migrated to use relative path syntax ([#14769](https://github.com/tailwindlabs/tailwindcss/pull/14769))

## [4.0.0-alpha.29] - 2024-10-23

Expand Down
79 changes: 78 additions & 1 deletion integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,7 @@ test(
/* Inject missing @config due to nested imports with tailwind imports */
@import './root.4/base.css';
@import './root.4/utilities.css';
@config '../tailwind.config.ts';
/*
The default border color has changed to \`currentColor\` in Tailwind CSS v4,
Expand Down Expand Up @@ -1808,7 +1809,6 @@ test(
border-width: 0;
}
}
@config '../tailwind.config.ts';
--- ./src/root.5.css ---
@import './root.5/tailwind.css';
Expand Down Expand Up @@ -1963,3 +1963,80 @@ test(
`)
},
)

test(
'relative imports without a relative path prefix are migrated to include a relative path prefix',
{
fs: {
'package.json': json`
{
"dependencies": {
"tailwindcss": "workspace:^",
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`module.exports = {}`,
'src/index.css': css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'styles/components';
@import 'tailwindcss/utilities';
`,
'src/styles/components.css': css`
.btn {
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
}
`,
},
},
async ({ fs, exec }) => {
await exec('npx @tailwindcss/upgrade --force')

expect(await fs.dumpFiles('./src/**/*.css')).toMatchInlineSnapshot(`
"
--- ./src/index.css ---
@import 'tailwindcss';
@import './styles/components.css' layer(components);
/*
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;
}
}
--- ./src/styles/components.css ---
.btn {
@apply bg-black px-4 py-2 rounded-md text-white font-medium hover:bg-zinc-800;
}
"
`)
},
)
8 changes: 0 additions & 8 deletions integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,3 @@ async function gracefullyRemove(dir: string) {
await fs.rm(dir, { recursive: true, force: true })
}
}

async function dirExists(dir: string): Promise<boolean> {
try {
return await fs.stat(dir).then((stat) => stat.isDirectory())
} catch {
return false
}
}
17 changes: 15 additions & 2 deletions packages/@tailwindcss-upgrade/src/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { migrateAtApply } from './codemods/migrate-at-apply'
import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities'
import { migrateBorderCompatibility } from './codemods/migrate-border-compatibility'
import { migrateConfig } from './codemods/migrate-config'
import { migrateImport } from './codemods/migrate-import'
import { migrateMediaScreen } from './codemods/migrate-media-screen'
import { migrateMissingLayers } from './codemods/migrate-missing-layers'
import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives'
Expand Down Expand Up @@ -37,6 +38,7 @@ export async function migrateContents(
}

return postcss()
.use(migrateImport())
.use(migrateAtApply(options))
.use(migrateMediaScreen(options))
.use(migrateVariantsDirective())
Expand Down Expand Up @@ -84,9 +86,20 @@ export async function analyze(stylesheets: Stylesheet[]) {
: process.cwd()

// Resolve the import to a file path
let resolvedPath: string | false
let resolvedPath: string | false = false
try {
resolvedPath = resolveCssId(id, basePath)
// We first try to resolve the file as relative to the current file
// to mimic the behavior of `postcss-import` since that's what was
// used to resolve imports in Tailwind CSS v3.
if (id[0] !== '.') {
try {
resolvedPath = resolveCssId(`./${id}`, basePath)
} catch {}
}

if (!resolvedPath) {
resolvedPath = resolveCssId(id, basePath)
}
} catch (err) {
console.warn(`Failed to resolve import: ${id}. Skipping.`)
console.error(err)
Expand Down
7 changes: 7 additions & 0 deletions playgrounds/v3/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off",
"react/jsx-no-comment-textnodes": "off"
}
}
36 changes: 36 additions & 0 deletions playgrounds/v3/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
3 changes: 3 additions & 0 deletions playgrounds/v3/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
23 changes: 23 additions & 0 deletions playgrounds/v3/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="[&_h1]:font-thin">
<head>{/* <script src="https://cdn.tailwindcss.com"></script> */}</head>
<body className={inter.className}>{children}</body>
</html>
)
}
3 changes: 3 additions & 0 deletions playgrounds/v3/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Home() {
return <h1 className="text-3xl font-bold underline border ring">Hello world!</h1>
}
4 changes: 4 additions & 0 deletions playgrounds/v3/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}

export default nextConfig
26 changes: 26 additions & 0 deletions playgrounds/v3/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "v3-playground",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"upgrade": "node scripts/upgrade.mjs"
},
"dependencies": {
"next": "14.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3"
},
"devDependencies": {
"@types/node": "^20.14.8",
"@types/react": "^18.3.9",
"@types/react-dom": "^18.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.11.1",
"eslint-config-next": "^14.2.5",
"typescript": "^5.5.4"
}
}
6 changes: 6 additions & 0 deletions playgrounds/v3/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
40 changes: 40 additions & 0 deletions playgrounds/v3/scripts/upgrade.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { execSync } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const __dirname = fileURLToPath(new URL('.', import.meta.url))
const cwd = path.join(__dirname, '..')

let originalLockfile = await fs.readFile(path.join(cwd, '../../pnpm-lock.yaml'), 'utf-8')

console.log('Overwriting dependencies for @tailwindcss/upgrade')

// Apply package patches
let json = JSON.parse(await fs.readFile('package.json', 'utf-8'))
json.pnpm = {
overrides: {
'@tailwindcss/upgrade>tailwindcss': 'file:../../dist/tailwindcss.tgz',
'@tailwindcss/upgrade>@tailwindcss/node': 'file:../../dist/tailwindcss-node.tgz',
},
}
json.devDependencies['@tailwindcss/upgrade'] = 'file:../../dist/tailwindcss-upgrade.tgz'
await fs.writeFile('package.json', JSON.stringify(json, null, 2))

try {
execSync('pnpm install --ignore-workspace', { cwd })
} catch (error) {
console.error(error.stdout?.toString() ?? error)
}

execSync('npx @tailwindcss/upgrade --force', { cwd, stdio: 'inherit' })

// Undo package patches
json = JSON.parse(await fs.readFile('package.json', 'utf-8'))
delete json.pnpm
delete json.devDependencies['@tailwindcss/upgrade']
await fs.writeFile('package.json', JSON.stringify(json, null, 2))

// Restore original lockfile (to avoid unnecessary changes in git diff)
await fs.writeFile(path.join(cwd, '../../pnpm-lock.yaml'), originalLockfile)
await fs.unlink(path.join(cwd, 'pnpm-lock.yaml'))
4 changes: 4 additions & 0 deletions playgrounds/v3/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./app/**/*.tsx'],
}
26 changes: 26 additions & 0 deletions playgrounds/v3/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next",
},
],
"paths": {
"@/*": ["./*"],
},
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
}
Loading

0 comments on commit 39cfcfa

Please sign in to comment.