diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts index 426882a7..fb4b901d 100644 --- a/packages/tailwindcss-language-server/src/projects.ts +++ b/packages/tailwindcss-language-server/src/projects.ts @@ -442,16 +442,24 @@ export async function createProjectService( let applyComplexClasses: any try { - let tailwindcssPath = await resolver.resolveJsId('tailwindcss', configDir) - let tailwindcssPkgPath = await resolver.resolveJsId('tailwindcss/package.json', configDir) + let tailwindcssPkgPath = await resolver.resolveCjsId('tailwindcss/package.json', configDir) let tailwindDir = path.dirname(tailwindcssPkgPath) tailwindcssVersion = require(tailwindcssPkgPath).version let features = supportedFeatures(tailwindcssVersion) log(`supported features: ${JSON.stringify(features)}`) - tailwindcssPath = pathToFileURL(tailwindcssPath).href - tailwindcss = await import(tailwindcssPath) + // Loading via `await import(…)` with the Yarn PnP API is not possible + if (await resolver.hasPnP()) { + let tailwindcssPath = await resolver.resolveCjsId('tailwindcss', configDir) + + tailwindcss = require(tailwindcssPath) + } else { + let tailwindcssPath = await resolver.resolveJsId('tailwindcss', configDir) + let tailwindcssURL = pathToFileURL(tailwindcssPath).href + + tailwindcss = await import(tailwindcssURL) + } if (!features.includes('css-at-theme')) { tailwindcss = tailwindcss.default ?? tailwindcss @@ -484,10 +492,13 @@ export async function createProjectService( return } - const postcssPath = resolveFrom(tailwindDir, 'postcss') - const postcssPkgPath = resolveFrom(tailwindDir, 'postcss/package.json') + const postcssPath = await resolver.resolveCjsId('postcss', tailwindDir) + const postcssPkgPath = await resolver.resolveCjsId('postcss/package.json', tailwindDir) const postcssDir = path.dirname(postcssPkgPath) - const postcssSelectorParserPath = resolveFrom(tailwindDir, 'postcss-selector-parser') + const postcssSelectorParserPath = await resolver.resolveCjsId( + 'postcss-selector-parser', + tailwindDir, + ) postcssVersion = require(postcssPkgPath).version diff --git a/packages/tailwindcss-language-server/src/resolver/index.ts b/packages/tailwindcss-language-server/src/resolver/index.ts index b5c6db94..cc894552 100644 --- a/packages/tailwindcss-language-server/src/resolver/index.ts +++ b/packages/tailwindcss-language-server/src/resolver/index.ts @@ -8,6 +8,7 @@ import { } from 'enhanced-resolve' import { loadPnPApi, type PnpApi } from './pnp' import { loadTsConfig, type TSConfigApi } from './tsconfig' +import { normalizeYarnPnPDriveLetter } from '../utils' export interface ResolverOptions { /** @@ -42,15 +43,6 @@ export interface ResolverOptions { } export interface Resolver { - /** - * Sets up the PnP API if it is available such that globals like `require` - * have been monkey-patched to use PnP resolution. - * - * This function does nothing if PnP resolution is not enabled or if the PnP - * API is not available. - */ - setupPnP(): Promise - /** * Resolves a JavaScript module to a file path. * @@ -63,6 +55,16 @@ export interface Resolver { */ resolveJsId(id: string, base: string): Promise + /** + * Resolves a CJS module to a file path. + * + * Assumes ESM-captable mechanisms are not available. + * + * @param id The module or file to resolve + * @param base The base directory to resolve the module from + */ + resolveCjsId(id: string, base: string): Promise + /** * Resolves a CSS module to a file path. * @@ -97,6 +99,11 @@ export interface Resolver { */ child(opts: Partial): Promise + /** + * Whether or not the PnP API is being used by the resolver + */ + hasPnP(): Promise + /** * Refresh information the resolver may have cached * @@ -106,17 +113,18 @@ export interface Resolver { } export async function createResolver(opts: ResolverOptions): Promise { - let fileSystem = opts.fileSystem ? opts.fileSystem : new CachedInputFileSystem(fs, 4000) - let pnpApi: PnpApi | null = null // Load PnP API if requested + // This MUST be done before `CachedInputFileSystem` is created if (typeof opts.pnp === 'object') { pnpApi = opts.pnp } else if (opts.pnp) { pnpApi = await loadPnPApi(opts.root) } + let fileSystem = opts.fileSystem ? opts.fileSystem : new CachedInputFileSystem(fs, 4000) + let tsconfig: TSConfigApi | null = null // Load TSConfig path mappings @@ -183,6 +191,10 @@ export async function createResolver(opts: ResolverOptions): Promise { if (match) id = match } + // 2. Normalize the drive letters to the case that the PnP API expects + id = normalizeYarnPnPDriveLetter(id) + base = normalizeYarnPnPDriveLetter(base) + return new Promise((resolve, reject) => { resolver.resolve({}, base, id, {}, (err, res) => { if (err) { @@ -202,6 +214,10 @@ export async function createResolver(opts: ResolverOptions): Promise { } } + async function resolveCjsId(id: string, base: string): Promise { + return (await resolveId(cjsResolver, id, base)) || id + } + async function resolveCssId(id: string, base: string): Promise { return (await resolveId(cssResolver, id, base)) || id } @@ -212,10 +228,6 @@ export async function createResolver(opts: ResolverOptions): Promise { return (await tsconfig?.substituteId(id, base)) ?? id } - async function setupPnP() { - pnpApi?.setup() - } - async function aliases(base: string) { if (!tsconfig) return {} @@ -226,12 +238,17 @@ export async function createResolver(opts: ResolverOptions): Promise { await tsconfig?.refresh() } + async function hasPnP() { + return !!pnpApi + } + return { - setupPnP, resolveJsId, + resolveCjsId, resolveCssId, substituteId, refresh, + hasPnP, aliases, diff --git a/packages/tailwindcss-language-server/src/resolver/pnp.ts b/packages/tailwindcss-language-server/src/resolver/pnp.ts index 543db0fa..fd7c290d 100644 --- a/packages/tailwindcss-language-server/src/resolver/pnp.ts +++ b/packages/tailwindcss-language-server/src/resolver/pnp.ts @@ -1,8 +1,8 @@ import findUp from 'find-up' import * as path from 'node:path' +import { pathToFileURL } from '../utils' export interface PnpApi { - setup(): void resolveToUnqualified: (arg0: string, arg1: string, arg2: object) => null | string } @@ -25,8 +25,10 @@ export async function loadPnPApi(root: string): Promise { return null } - let mod = await import(pnpPath) + let pnpUrl = pathToFileURL(pnpPath).href + let mod = await import(pnpUrl) let api = mod.default + api.setup() cache.set(root, api) return api } diff --git a/packages/tailwindcss-language-server/src/tw.ts b/packages/tailwindcss-language-server/src/tw.ts index e5d6522f..587a5000 100644 --- a/packages/tailwindcss-language-server/src/tw.ts +++ b/packages/tailwindcss-language-server/src/tw.ts @@ -36,7 +36,6 @@ import normalizePath from 'normalize-path' import * as path from 'node:path' import type * as chokidar from 'chokidar' import picomatch from 'picomatch' -import { resolveFrom } from './util/resolveFrom' import * as parcel from './watcher/index.js' import { equal } from '@tailwindcss/language-service/src/util/array' import { CONFIG_GLOB, CSS_GLOB, PACKAGE_LOCK_GLOB, TSCONFIG_GLOB } from './lib/constants' @@ -321,9 +320,9 @@ export class TW { let twVersion = require('tailwindcss/package.json').version try { let v = require( - resolveFrom( - path.dirname(project.projectConfig.configPath), + await resolver.resolveCjsId( 'tailwindcss/package.json', + path.dirname(project.projectConfig.configPath), ), ).version if (typeof v === 'string') { diff --git a/packages/tailwindcss-language-server/src/utils.ts b/packages/tailwindcss-language-server/src/utils.ts index 8e1f6303..e679d213 100644 --- a/packages/tailwindcss-language-server/src/utils.ts +++ b/packages/tailwindcss-language-server/src/utils.ts @@ -74,6 +74,7 @@ export function dirContains(dir: string, file: string): boolean { } const WIN_DRIVE_LETTER = /^([a-zA-Z]):/ +const POSIX_DRIVE_LETTER = /^\/([a-zA-Z]):/ /** * Windows drive letters are case-insensitive and we may get them as either @@ -81,7 +82,34 @@ const WIN_DRIVE_LETTER = /^([a-zA-Z]):/ * to be consistent with the rest of the codebase. */ export function normalizeDriveLetter(filepath: string) { - return filepath.replace(WIN_DRIVE_LETTER, (_, letter) => letter.toUpperCase() + ':') + return filepath + .replace(WIN_DRIVE_LETTER, (_, letter) => `${letter.toUpperCase()}:`) + .replace(POSIX_DRIVE_LETTER, (_, letter) => `/${letter.toUpperCase()}:`) +} + +/** + * Windows drive letters are case-insensitive and we may get them as either + * lower or upper case. + * + * Yarn PnP only works when requests have the correct case for the drive letter + * that matches the drive letter of the current working directory. + * + * Even using makeApi with a custom base path doesn't work around this. + */ +export function normalizeYarnPnPDriveLetter(filepath: string) { + let cwdDriveLetter = process.cwd().match(WIN_DRIVE_LETTER)?.[1] + + return filepath + .replace(WIN_DRIVE_LETTER, (_, letter) => { + return letter.toUpperCase() === cwdDriveLetter.toUpperCase() + ? `${cwdDriveLetter}:` + : `${letter.toUpperCase()}:` + }) + .replace(POSIX_DRIVE_LETTER, (_, letter) => { + return letter.toUpperCase() === cwdDriveLetter.toUpperCase() + ? `/${cwdDriveLetter}:` + : `/${letter.toUpperCase()}:` + }) } export function changeAffectsFile(change: string, files: Iterable): boolean { @@ -115,7 +143,7 @@ export function pathToFileURL(filepath: string) { } catch (err) { if (process.platform !== 'win32') throw err - // If `pathToFileURL` failsed on windows it's probably because the path was + // If `pathToFileURL` failed on windows it's probably because the path was // a windows network share path and there were mixed slashes. // Fix the path and try again. filepath = URI.file(filepath).fsPath diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md index b0425ba2..10f4404b 100644 --- a/packages/vscode-tailwindcss/CHANGELOG.md +++ b/packages/vscode-tailwindcss/CHANGELOG.md @@ -3,6 +3,7 @@ ## Prerelease - Don't suggest `--font-size-*` theme keys in v4.0 ([#1150](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1150)) +- Fix detection of Tailwind CSS version when using Yarn PnP ([#1151](https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1151)) ## 0.14.1