mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2026-01-25 16:44:12 +00:00
Register migrateImport to ensure it actually runs (#14769)
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>
This commit is contained in:
parent
d643d79f4b
commit
39cfcfa427
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
@ -1808,7 +1809,6 @@ test(
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
@config '../tailwind.config.ts';
|
||||
|
||||
--- ./src/root.5.css ---
|
||||
@import './root.5/tailwind.css';
|
||||
@ -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;
|
||||
}
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'
|
||||
@ -37,6 +38,7 @@ export async function migrateContents(
|
||||
}
|
||||
|
||||
return postcss()
|
||||
.use(migrateImport())
|
||||
.use(migrateAtApply(options))
|
||||
.use(migrateMediaScreen(options))
|
||||
.use(migrateVariantsDirective())
|
||||
@ -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)
|
||||
|
||||
7
playgrounds/v3/.eslintrc.json
Normal file
7
playgrounds/v3/.eslintrc.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/jsx-no-comment-textnodes": "off"
|
||||
}
|
||||
}
|
||||
36
playgrounds/v3/.gitignore
vendored
Normal file
36
playgrounds/v3/.gitignore
vendored
Normal file
@ -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
playgrounds/v3/app/globals.css
Normal file
3
playgrounds/v3/app/globals.css
Normal file
@ -0,0 +1,3 @@
|
||||
@import 'tailwindcss/base';
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
23
playgrounds/v3/app/layout.tsx
Normal file
23
playgrounds/v3/app/layout.tsx
Normal file
@ -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
playgrounds/v3/app/page.tsx
Normal file
3
playgrounds/v3/app/page.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export default function Home() {
|
||||
return <h1 className="text-3xl font-bold underline border ring">Hello world!</h1>
|
||||
}
|
||||
4
playgrounds/v3/next.config.mjs
Normal file
4
playgrounds/v3/next.config.mjs
Normal file
@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
|
||||
export default nextConfig
|
||||
26
playgrounds/v3/package.json
Normal file
26
playgrounds/v3/package.json
Normal file
@ -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
playgrounds/v3/postcss.config.js
Normal file
6
playgrounds/v3/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
40
playgrounds/v3/scripts/upgrade.mjs
Normal file
40
playgrounds/v3/scripts/upgrade.mjs
Normal file
@ -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
playgrounds/v3/tailwind.config.js
Normal file
4
playgrounds/v3/tailwind.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./app/**/*.tsx'],
|
||||
}
|
||||
26
playgrounds/v3/tsconfig.json
Normal file
26
playgrounds/v3/tsconfig.json
Normal file
@ -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"],
|
||||
}
|
||||
938
pnpm-lock.yaml
generated
938
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user