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