diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d616d340..f6fbd9bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts index 79ce57553..f2f4b8a0b 100644 --- a/integrations/vite/resolvers.test.ts +++ b/integrations/vite/resolvers.test.ts @@ -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` + + + + +
Hello, world!
+ + `, + '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` + + + + +
Hello, world!
+ + `, + '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', diff --git a/integrations/vite/sveltekit.test.ts b/integrations/vite/sveltekit.test.ts new file mode 100644 index 000000000..92e855da9 --- /dev/null +++ b/integrations/vite/sveltekit.test.ts @@ -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` + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + + `, + 'src/routes/+layout.svelte': html` + + + + `, + 'src/routes/+page.svelte': html` +
Hello, world!
+ `, + '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`]) + }, +) diff --git a/packages/@tailwindcss-postcss/README.md b/packages/@tailwindcss-postcss/README.md index 981ffc42d..3facf2887 100644 --- a/packages/@tailwindcss-postcss/README.md +++ b/packages/@tailwindcss-postcss/README.md @@ -109,4 +109,3 @@ export default { ], } ``` - diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts index ac3e1c33c..5b376937c 100644 --- a/packages/@tailwindcss-vite/src/index.ts +++ b/packages/@tailwindcss-vite/src/index.ts @@ -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(