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

Register migrateImport to ensure it actually runs #14769

Merged
merged 7 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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 @@ -576,7 +576,7 @@
}

export async function fetchStyles(port: number, path = '/'): Promise<string> {
let index = await fetch(`http://localhost:${port}${path}`)

Check failure on line 579 in integrations/utils.ts

View workflow job for this annotation

GitHub Actions / tests (20, namespace-profile-default, false)

vite/astro.test.ts > dev mode

TypeError: fetch failed ❯ Module.fetchStyles utils.ts:579:15 ❯ vite/astro.test.ts:43:17 ❯ Module.retryAssertion utils.ts:569:14 ❯ vite/astro.test.ts:42:5 ❯ utils.ts:394:14 Caused by: Caused by: Error: connect ECONNREFUSED 127.0.0.1:44155 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 44155 }

Check failure on line 579 in integrations/utils.ts

View workflow job for this annotation

GitHub Actions / tests (20, namespace-profile-default, false)

vite/astro.test.ts > dev mode

TypeError: fetch failed ❯ Module.fetchStyles utils.ts:579:15 ❯ vite/astro.test.ts:43:17 ❯ Module.retryAssertion utils.ts:569:14 ❯ vite/astro.test.ts:42:5 ❯ utils.ts:394:14 Caused by: Error: connect ECONNREFUSED 127.0.0.1:38477 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 38477 }

Check failure on line 579 in integrations/utils.ts

View workflow job for this annotation

GitHub Actions / tests (20, namespace-profile-default, false)

vite/astro.test.ts > dev mode

TypeError: fetch failed ❯ Module.fetchStyles utils.ts:579:15 ❯ vite/astro.test.ts:43:17 ❯ Module.retryAssertion utils.ts:569:14 ❯ vite/astro.test.ts:42:5 ❯ utils.ts:394:14 Caused by: Error: connect ECONNREFUSED 127.0.0.1:45415 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 45415 }
let html = await index.text()

let linkRegex = /<link rel="stylesheet" href="([a-zA-Z0-9\/_\.\?=%-]+)"/gi
Expand Down Expand Up @@ -620,11 +620,3 @@
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
Loading