This PR fixes an issue when using `@tailwindcss/vite` and you're trying to resolve paths. In the latest 4.2.3 release, we added support for following the vite `aliases` option. However, some people run into issues because if you just use `@` as an alias then using `@tailwindcss/typography` wouldn't resolve because it's a package, and not something local. With this PR we fix that by making sure that Vite's resolver can actually resolve to an absolute path. If not, then we fallback to the resolving that happens in `@tailwindcss/node`. Fixes: #19946 ## Test plan 1. Added an integration test that reproduces this issue, and is now solved 2. Tested it on a reproduction provided in the corresponding issue Before: <img width="1694" height="1856" alt="image" src="https://github.com/user-attachments/assets/c50f7104-78b3-476d-9e60-0c83e0976a5c" /> After: <img width="1694" height="1856" alt="image" src="https://github.com/user-attachments/assets/44ea0e3b-f60f-479f-a4a1-203a6230475b" />
435 lines
12 KiB
TypeScript
435 lines
12 KiB
TypeScript
import { describe } from 'vitest'
|
|
import {
|
|
candidate,
|
|
css,
|
|
fetchStyles,
|
|
html,
|
|
js,
|
|
json,
|
|
retryAssertion,
|
|
test,
|
|
ts,
|
|
txt,
|
|
} from '../utils'
|
|
|
|
test(
|
|
'resolves tsconfig paths in production build',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"vite": "^8"
|
|
}
|
|
}
|
|
`,
|
|
'tsconfig.json': json`
|
|
{
|
|
"compilerOptions": {
|
|
"baseUrl": ".",
|
|
"paths": {
|
|
"@/*": ["./src/*"]
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
tsconfigPaths: true,
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="underline custom-underline">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import '@/styles/base.css';
|
|
@plugin '@/plugin.js';
|
|
`,
|
|
'src/styles/base.css': css`
|
|
@reference 'tailwindcss/theme';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/plugin.js': js`
|
|
export default function ({ addUtilities }) {
|
|
addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec, expect }) => {
|
|
await exec('pnpm vite build')
|
|
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [filename] = files[0]
|
|
|
|
await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`])
|
|
},
|
|
)
|
|
|
|
test(
|
|
'resolves tsconfig paths in dev mode',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"vite": "^8"
|
|
}
|
|
}
|
|
`,
|
|
'tsconfig.json': json`
|
|
{
|
|
"compilerOptions": {
|
|
"baseUrl": ".",
|
|
"paths": {
|
|
"@/*": ["./src/*"]
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
tsconfigPaths: true,
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="underline custom-underline">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import '@/styles/base.css';
|
|
@plugin '@/plugin.js';
|
|
`,
|
|
'src/styles/base.css': css`
|
|
@reference 'tailwindcss/theme';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/plugin.js': js`
|
|
export default function ({ addUtilities }) {
|
|
addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ spawn, expect }) => {
|
|
let process = await spawn('pnpm vite dev')
|
|
await process.onStdout((m) => m.includes('ready in'))
|
|
|
|
let url = ''
|
|
await process.onStdout((m) => {
|
|
let match = /Local:\s*(http.*)\//.exec(m)
|
|
if (match) url = match[1]
|
|
return Boolean(url)
|
|
})
|
|
|
|
await retryAssertion(async () => {
|
|
let styles = await fetchStyles(url, '/index.html')
|
|
expect(styles).toContain(candidate`underline`)
|
|
expect(styles).toContain(candidate`custom-underline`)
|
|
})
|
|
},
|
|
)
|
|
|
|
test(
|
|
'resolves at-sign aliases in production build',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"vite": "^8"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
export default defineConfig({
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }],
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="underline custom-underline">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import '@/src/styles/base.css';
|
|
@plugin '@/src/plugin.js';
|
|
`,
|
|
'src/styles/base.css': css`
|
|
@reference 'tailwindcss/theme';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/plugin.js': js`
|
|
export default function ({ addUtilities }) {
|
|
addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec, expect }) => {
|
|
await exec('pnpm vite build')
|
|
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [filename] = files[0]
|
|
|
|
await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`])
|
|
},
|
|
)
|
|
|
|
test(
|
|
'resolves package plugins in production build with at-sign aliases',
|
|
{
|
|
fs: {
|
|
'package.json': json`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
"tailwind-scrollbar": "^4.0.2",
|
|
"tailwindcss-motion": "^1.1.1",
|
|
"vite": "^8"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
export default defineConfig({
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
alias: [{ find: '@', replacement: fileURLToPath(new URL('.', import.meta.url)) }],
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="scrollbar scrollbar-thumb-red-500 motion-preset-fade">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import 'tailwindcss';
|
|
@plugin 'tailwind-scrollbar';
|
|
@plugin 'tailwindcss-motion';
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec, expect }) => {
|
|
await exec('pnpm vite build')
|
|
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [filename] = files[0]
|
|
|
|
await fs.expectFileToContain(filename, [
|
|
candidate`scrollbar`,
|
|
candidate`scrollbar-thumb-red-500`,
|
|
candidate`motion-preset-fade`,
|
|
])
|
|
},
|
|
)
|
|
|
|
describe.each(['postcss', 'lightningcss'])('%s', (transformer) => {
|
|
test(
|
|
'resolves aliases in production build',
|
|
{
|
|
fs: {
|
|
'package.json': txt`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''}
|
|
"vite": "^5.3.5"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
export default defineConfig({
|
|
css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"},
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
alias: {
|
|
'#css-alias': fileURLToPath(new URL('./src/alias.css', import.meta.url)),
|
|
'#js-alias': fileURLToPath(new URL('./src/plugin.js', import.meta.url)),
|
|
},
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="underline custom-underline">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import '#css-alias';
|
|
@plugin '#js-alias';
|
|
`,
|
|
'src/alias.css': css`
|
|
@reference 'tailwindcss/theme';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/plugin.js': js`
|
|
export default function ({ addUtilities }) {
|
|
addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ fs, exec, expect }) => {
|
|
await exec('pnpm vite build')
|
|
|
|
let files = await fs.glob('dist/**/*.css')
|
|
expect(files).toHaveLength(1)
|
|
let [filename] = files[0]
|
|
|
|
await fs.expectFileToContain(filename, [candidate`underline`, candidate`custom-underline`])
|
|
},
|
|
)
|
|
|
|
test(
|
|
'resolves aliases in dev mode',
|
|
{
|
|
fs: {
|
|
'package.json': txt`
|
|
{
|
|
"type": "module",
|
|
"dependencies": {
|
|
"@tailwindcss/vite": "workspace:^",
|
|
"tailwindcss": "workspace:^"
|
|
},
|
|
"devDependencies": {
|
|
${transformer === 'lightningcss' ? `"lightningcss": "^1",` : ''}
|
|
"vite": "^5.3.5"
|
|
}
|
|
}
|
|
`,
|
|
'vite.config.ts': ts`
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { defineConfig } from 'vite'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
export default defineConfig({
|
|
css: ${transformer === 'postcss' ? '{}' : "{ transformer: 'lightningcss' }"},
|
|
build: { cssMinify: false },
|
|
plugins: [tailwindcss()],
|
|
resolve: {
|
|
alias: {
|
|
'#css-alias': fileURLToPath(new URL('./src/alias.css', import.meta.url)),
|
|
'#js-alias': fileURLToPath(new URL('./src/plugin.js', import.meta.url)),
|
|
},
|
|
},
|
|
})
|
|
`,
|
|
'index.html': html`
|
|
<head>
|
|
<link rel="stylesheet" href="./src/index.css" />
|
|
</head>
|
|
<body>
|
|
<div class="underline custom-underline">Hello, world!</div>
|
|
</body>
|
|
`,
|
|
'src/index.css': css`
|
|
@import '#css-alias';
|
|
@plugin '#js-alias';
|
|
`,
|
|
'src/alias.css': css`
|
|
@reference 'tailwindcss/theme';
|
|
@import 'tailwindcss/utilities';
|
|
`,
|
|
'src/plugin.js': js`
|
|
export default function ({ addUtilities }) {
|
|
addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
|
|
}
|
|
`,
|
|
},
|
|
},
|
|
async ({ spawn, expect }) => {
|
|
let process = await spawn('pnpm vite dev')
|
|
await process.onStdout((m) => m.includes('ready in'))
|
|
|
|
let url = ''
|
|
await process.onStdout((m) => {
|
|
let match = /Local:\s*(http.*)\//.exec(m)
|
|
if (match) url = match[1]
|
|
return Boolean(url)
|
|
})
|
|
|
|
await retryAssertion(async () => {
|
|
let styles = await fetchStyles(url, '/index.html')
|
|
expect(styles).toContain(candidate`underline`)
|
|
expect(styles).toContain(candidate`custom-underline`)
|
|
})
|
|
},
|
|
)
|
|
})
|