diff --git a/CHANGELOG.md b/CHANGELOG.md
index c790cdccf292..164d01e06b6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- Ensure the Vite plugin resolves CSS and JS files according to the configured resolver conditions ([#15173])(https://github.com/tailwindlabs/tailwindcss/pull/15173)
- _Upgrade (experimental)_: Migrate prefixes for `.group` and `.peer` classes ([#15208](https://github.com/tailwindlabs/tailwindcss/pull/15208))
### Fixed
diff --git a/integrations/vite/resolvers.test.ts b/integrations/vite/resolvers.test.ts
new file mode 100644
index 000000000000..b7098d47fc68
--- /dev/null
+++ b/integrations/vite/resolvers.test.ts
@@ -0,0 +1,143 @@
+import { describe, expect } from 'vitest'
+import { candidate, css, fetchStyles, html, js, retryAssertion, test, ts, txt } from '../utils'
+
+for (let transformer of ['postcss', 'lightningcss']) {
+ describe(transformer, () => {
+ test(
+ `resolves aliases in production build`,
+ {
+ fs: {
+ 'package.json': txt`
+ {
+ "type": "module",
+ "dependencies": {
+ "@tailwindcss/vite": "workspace:^",
+ "tailwindcss": "workspace:^"
+ },
+ "devDependencies": {
+ ${transformer === 'lightningcss' ? `"lightningcss": "^1.26.0",` : ''}
+ "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`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @import '#css-alias';
+ @plugin '#js-alias';
+ `,
+ 'src/alias.css': css`
+ @import 'tailwindcss/theme' theme(reference);
+ @import 'tailwindcss/utilities';
+ `,
+ 'src/plugin.js': js`
+ export default function ({ addUtilities }) {
+ addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
+ }
+ `,
+ },
+ },
+ async ({ fs, exec }) => {
+ 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.26.0",` : ''}
+ "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`
+
+
+
+
+ Hello, world!
+
+ `,
+ 'src/index.css': css`
+ @import '#css-alias';
+ @plugin '#js-alias';
+ `,
+ 'src/alias.css': css`
+ @import 'tailwindcss/theme' theme(reference);
+ @import 'tailwindcss/utilities';
+ `,
+ 'src/plugin.js': js`
+ export default function ({ addUtilities }) {
+ addUtilities({ '.custom-underline': { 'border-bottom': '1px solid green' } })
+ }
+ `,
+ },
+ },
+ async ({ root, spawn, getFreePort, fs }) => {
+ let port = await getFreePort()
+ await spawn(`pnpm vite dev --port ${port}`)
+
+ await retryAssertion(async () => {
+ let styles = await fetchStyles(port, '/index.html')
+ expect(styles).toContain(candidate`underline`)
+ expect(styles).toContain(candidate`custom-underline`)
+ })
+ },
+ )
+ })
+}
diff --git a/packages/@tailwindcss-node/src/compile.ts b/packages/@tailwindcss-node/src/compile.ts
index 396b7116e54a..a7aaf3e64849 100644
--- a/packages/@tailwindcss-node/src/compile.ts
+++ b/packages/@tailwindcss-node/src/compile.ts
@@ -11,25 +11,33 @@ import {
import { getModuleDependencies } from './get-module-dependencies'
import { rewriteUrls } from './urls'
+export type Resolver = (id: string, base: string) => Promise
+
export async function compile(
css: string,
{
base,
onDependency,
shouldRewriteUrls,
+
+ customCssResolver,
+ customJsResolver,
}: {
base: string
onDependency: (path: string) => void
shouldRewriteUrls?: boolean
+
+ customCssResolver?: Resolver
+ customJsResolver?: Resolver
},
) {
let compiler = await _compile(css, {
base,
async loadModule(id, base) {
- return loadModule(id, base, onDependency)
+ return loadModule(id, base, onDependency, customJsResolver)
},
async loadStylesheet(id, base) {
- let sheet = await loadStylesheet(id, base, onDependency)
+ let sheet = await loadStylesheet(id, base, onDependency, customCssResolver)
if (shouldRewriteUrls) {
sheet.content = await rewriteUrls({
@@ -80,9 +88,14 @@ export async function __unstable__loadDesignSystem(css: string, { base }: { base
})
}
-export async function loadModule(id: string, base: string, onDependency: (path: string) => void) {
+export async function loadModule(
+ id: string,
+ base: string,
+ onDependency: (path: string) => void,
+ customJsResolver?: Resolver,
+) {
if (id[0] !== '.') {
- let resolvedPath = await resolveJsId(id, base)
+ let resolvedPath = await resolveJsId(id, base, customJsResolver)
if (!resolvedPath) {
throw new Error(`Could not resolve '${id}' from '${base}'`)
}
@@ -94,7 +107,7 @@ export async function loadModule(id: string, base: string, onDependency: (path:
}
}
- let resolvedPath = await resolveJsId(id, base)
+ let resolvedPath = await resolveJsId(id, base, customJsResolver)
if (!resolvedPath) {
throw new Error(`Could not resolve '${id}' from '${base}'`)
}
@@ -113,8 +126,13 @@ export async function loadModule(id: string, base: string, onDependency: (path:
}
}
-async function loadStylesheet(id: string, base: string, onDependency: (path: string) => void) {
- let resolvedPath = await resolveCssId(id, base)
+async function loadStylesheet(
+ id: string,
+ base: string,
+ onDependency: (path: string) => void,
+ cssResolver?: Resolver,
+) {
+ let resolvedPath = await resolveCssId(id, base, cssResolver)
if (!resolvedPath) throw new Error(`Could not resolve '${id}' from '${base}'`)
onDependency(resolvedPath)
@@ -163,7 +181,11 @@ const cssResolver = EnhancedResolve.ResolverFactory.createResolver({
mainFields: ['style'],
conditionNames: ['style'],
})
-async function resolveCssId(id: string, base: string): Promise {
+async function resolveCssId(
+ id: string,
+ base: string,
+ customCssResolver?: Resolver,
+): Promise {
if (typeof globalThis.__tw_resolve === 'function') {
let resolved = globalThis.__tw_resolve(id, base)
if (resolved) {
@@ -171,6 +193,13 @@ async function resolveCssId(id: string, base: string): Promise {
+async function resolveJsId(
+ id: string,
+ base: string,
+ customJsResolver?: Resolver,
+): Promise {
if (typeof globalThis.__tw_resolve === 'function') {
let resolved = globalThis.__tw_resolve(id, base)
if (resolved) {
return Promise.resolve(resolved)
}
}
+
+ if (customJsResolver) {
+ let customResolution = await customJsResolver(id, base)
+ if (customResolution) {
+ return customResolution
+ }
+ }
+
return runResolver(esmResolver, id, base).catch(() => runResolver(cjsResolver, id, base))
}
diff --git a/packages/@tailwindcss-vite/src/index.ts b/packages/@tailwindcss-vite/src/index.ts
index 2c08920bb2e6..e14b84cf7f2d 100644
--- a/packages/@tailwindcss-vite/src/index.ts
+++ b/packages/@tailwindcss-vite/src/index.ts
@@ -35,9 +35,31 @@ export default function tailwindcss(): Plugin[] {
let moduleGraphCandidates = new DefaultMap>(() => new Set())
let moduleGraphScanner = new Scanner({})
- let roots: DefaultMap = new DefaultMap(
- (id) => new Root(id, () => moduleGraphCandidates, config!.base),
- )
+ let roots: DefaultMap = new DefaultMap((id) => {
+ let cssResolver = config!.createResolver({
+ ...config!.resolve,
+ extensions: ['.css'],
+ mainFields: ['style'],
+ conditions: ['style', 'development|production'],
+ tryIndex: false,
+ preferRelative: true,
+ })
+ function customCssResolver(id: string, base: string) {
+ return cssResolver(id, base, false, isSSR)
+ }
+
+ let jsResolver = config!.createResolver(config!.resolve)
+ function customJsResolver(id: string, base: string) {
+ return jsResolver(id, base, true, isSSR)
+ }
+ return new Root(
+ id,
+ () => moduleGraphCandidates,
+ config!.base,
+ customCssResolver,
+ customJsResolver,
+ )
+ })
function scanFile(id: string, content: string, extension: string, isSSR: boolean) {
let updated = false
@@ -423,6 +445,9 @@ class Root {
private id: string,
private getSharedCandidates: () => Map>,
private base: string,
+
+ private customCssResolver: (id: string, base: string) => Promise,
+ private customJsResolver: (id: string, base: string) => Promise,
) {}
// Generate the CSS for the root file. This can return false if the file is
@@ -448,6 +473,9 @@ class Root {
addWatchFile(path)
this.dependencies.add(path)
},
+
+ customCssResolver: this.customCssResolver,
+ customJsResolver: this.customJsResolver,
})
env.DEBUG && console.timeEnd('[@tailwindcss/vite] Setup compiler')
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a9452199b853..0acd31faa44c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2958,8 +2958,8 @@ packages:
magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
- magic-string@0.30.12:
- resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==}
+ magic-string@0.30.13:
+ resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
@@ -6447,7 +6447,7 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
- magic-string@0.30.12:
+ magic-string@0.30.13:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
@@ -7129,7 +7129,7 @@ snapshots:
estree-walker: 3.0.3
is-reference: 3.0.3
locate-character: 3.0.0
- magic-string: 0.30.12
+ magic-string: 0.30.13
periscopic: 3.1.0
tailwindcss@3.4.14:
@@ -7392,8 +7392,8 @@ snapshots:
vite@5.4.0(@types/node@20.14.13)(lightningcss@1.26.0(patch_hash=5hwfyehqvg5wjb7mwtdvubqbl4))(terser@5.31.6):
dependencies:
esbuild: 0.21.5
- postcss: 8.4.47
- rollup: 4.20.0
+ postcss: 8.4.49
+ rollup: 4.27.4
optionalDependencies:
'@types/node': 20.14.13
fsevents: 2.3.3