Fix issue around resolving paths in @tailwindcss/vite (#19947)
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" />
This commit is contained in:
parent
2e3fa490a5
commit
685c19e266
5 changed files with 230 additions and 5 deletions
|
|
@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- _Experimental_: add `@container-size` utility ([#18901](https://github.com/tailwindlabs/tailwindcss/pull/18901))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure imports in `@import` and `@plugin` still resolve correctly when using Vite aliases in `@tailwindcss/vite` ([#19947](https://github.com/tailwindlabs/tailwindcss/pull/19947))
|
||||
|
||||
## [4.2.3] - 2026-04-20
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -164,6 +164,130 @@ test(
|
|||
},
|
||||
)
|
||||
|
||||
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',
|
||||
|
|
|
|||
80
integrations/vite/sveltekit.test.ts
Normal file
80
integrations/vite/sveltekit.test.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { candidate, css, html, js, json, test, ts } from '../utils'
|
||||
|
||||
test(
|
||||
'sveltekit build resolves package plugins',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"prepare": "svelte-kit sync || echo ''"
|
||||
},
|
||||
"dependencies": {
|
||||
"svelte": "^5",
|
||||
"tailwindcss": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6",
|
||||
"@sveltejs/kit": "^2",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/vite": "workspace:^",
|
||||
"vite": "^8"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'svelte.config.js': js`
|
||||
import adapter from '@sveltejs/adapter-auto'
|
||||
|
||||
export default {
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
}
|
||||
`,
|
||||
'vite.config.ts': ts`
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { sveltekit } from '@sveltejs/kit/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
build: { cssMinify: false },
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
})
|
||||
`,
|
||||
'src/app.html': html`
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
'src/routes/+layout.svelte': html`
|
||||
<script>
|
||||
import '../app.css'
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
`,
|
||||
'src/routes/+page.svelte': html`
|
||||
<div class="form-input">Hello, world!</div>
|
||||
`,
|
||||
'src/app.css': css`
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/forms';
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs, expect }) => {
|
||||
await exec('pnpm vite build')
|
||||
|
||||
let files = await fs.glob('.svelte-kit/output/client/**/*.css')
|
||||
expect(files.length).toBeGreaterThan(0)
|
||||
|
||||
await fs.expectFileToContain(files[0][0], [candidate`form-input`])
|
||||
},
|
||||
)
|
||||
|
|
@ -109,4 +109,3 @@ export default {
|
|||
],
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,19 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
|
|||
|
||||
customCssResolver = async (id: string, base: string) => {
|
||||
let resolved = await cssResolver(id, base, false, isSSR)
|
||||
if (resolved && !resolved.endsWith('.css')) return undefined
|
||||
if (!resolved) return
|
||||
if (resolved === id) return
|
||||
if (!path.isAbsolute(resolved)) return
|
||||
if (!resolved.endsWith('.css')) return
|
||||
return resolved
|
||||
}
|
||||
customJsResolver = async (id: string, base: string) => {
|
||||
let resolved = await jsResolver(id, base, false, isSSR)
|
||||
if (!resolved) return
|
||||
if (resolved === id) return
|
||||
if (!path.isAbsolute(resolved)) return
|
||||
return resolved
|
||||
}
|
||||
customJsResolver = (id: string, base: string) => jsResolver(id, base, false, isSSR)
|
||||
} else {
|
||||
type ResolveIdFn = (
|
||||
environment: Environment,
|
||||
|
|
@ -111,10 +120,19 @@ export default function tailwindcss(opts: PluginOptions = {}): Plugin[] {
|
|||
|
||||
customCssResolver = async (id: string, base: string) => {
|
||||
let resolved = await cssResolver(env, id, base, false)
|
||||
if (resolved && !resolved.endsWith('.css')) return undefined
|
||||
if (!resolved) return
|
||||
if (resolved === id) return
|
||||
if (!path.isAbsolute(resolved)) return
|
||||
if (!resolved.endsWith('.css')) return
|
||||
return resolved
|
||||
}
|
||||
customJsResolver = async (id: string, base: string) => {
|
||||
let resolved = await jsResolver(env, id, base, false)
|
||||
if (!resolved) return
|
||||
if (resolved === id) return
|
||||
if (!path.isAbsolute(resolved)) return
|
||||
return resolved
|
||||
}
|
||||
customJsResolver = (id: string, base: string) => jsResolver(env, id, base, false)
|
||||
}
|
||||
|
||||
return new Root(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue