Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import loadConfigFallback from 'tailwindcss/loadConfig'
import resolveConfigFallback from 'tailwindcss/resolveConfig'
import type { RequiredConfig } from 'tailwindcss/types/config.js'
import { expiringMap } from './expiring-map.js'
import { resolveIn } from './resolve'
import type { ContextContainer } from './types'

let localRequire = createRequire(import.meta.url)
Expand Down Expand Up @@ -106,10 +107,7 @@ async function loadTailwindConfig(
let tailwindConfig: RequiredConfig = { content: [] }

try {
let pkgFile = localRequire.resolve('tailwindcss/package.json', {
paths: [baseDir],
})

let pkgFile = resolveIn('tailwindcss/package.json', [baseDir])
let pkgDir = path.dirname(pkgFile)

try {
Expand Down Expand Up @@ -155,9 +153,7 @@ async function loadV4(
entryPoint: string | null,
) {
// Import Tailwind — if this is v4 it'll have APIs we can use directly
let pkgPath = localRequire.resolve('tailwindcss', {
paths: [baseDir],
})
let pkgPath = resolveIn('tailwindcss', [baseDir])
let tw = await import(pathToFileURL(pkgPath).toString())

// This is not Tailwind v4
Expand Down
27 changes: 7 additions & 20 deletions src/plugins.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createRequire as req } from 'node:module'
import type { Parser, ParserOptions, Plugin, Printer } from 'prettier'
import './types'
import * as prettierParserAcorn from 'prettier/plugins/acorn'
Expand All @@ -9,26 +8,22 @@ import * as prettierParserHTML from 'prettier/plugins/html'
import * as prettierParserMeriyah from 'prettier/plugins/meriyah'
import * as prettierParserPostCSS from 'prettier/plugins/postcss'
import * as prettierParserTypescript from 'prettier/plugins/typescript'
import { loadIfExists, maybeResolve } from './resolve'

interface PluginDetails {
parsers: Record<string, Parser<any>>
printers: Record<string, Printer<any>>
}

async function loadIfExistsESM(name: string): Promise<Plugin<any>> {
try {
if (req(import.meta.url).resolve(name)) {
let mod = await import(name)
return mod.default ?? mod
}
let mod = await loadIfExists<Plugin<any>>(name)

throw new Error('unreachable')
} catch (e) {
return {
parsers: {},
printers: {},
}
mod = mod ?? {
parsers: {},
printers: {},
}

return mod
}

export async function loadPlugins() {
Expand All @@ -46,14 +41,6 @@ export async function loadPlugins() {
...thirdparty.printers,
}

function maybeResolve(name: string) {
try {
return req(import.meta.url).resolve(name)
} catch (err) {
return null
}
}

function findEnabledPlugin(
options: ParserOptions<any>,
name: string,
Expand Down
47 changes: 47 additions & 0 deletions src/resolve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { createRequire as req } from 'node:module'
import { expiringMap } from './expiring-map'

const localRequire = req(import.meta.url)

// This is a long-lived cache for resolved modules whether they exist or not
// Because we're compatible with a large number of plugins, we need to check
// for the existence of a module before attempting to import it. This cache
// is used to mitigate the cost of that check because Node.js does not cache
// failed module resolutions making repeated checks very expensive.
const resolveCache = expiringMap<string, string | null>(30_000)

export function resolveIn(id: string, paths: string[]) {
return localRequire.resolve(id, {
paths,
})
}

export function maybeResolve(name: string) {
let modpath = resolveCache.get(name)

if (modpath === undefined) {
modpath = freshMaybeResolve(name)
resolveCache.set(name, modpath)
}

return modpath
}

export async function loadIfExists<T>(name: string): Promise<T | null> {
let modpath = maybeResolve(name)

if (modpath) {
let mod = await import(name)
return mod.default ?? mod
}

return null
}

function freshMaybeResolve(name: string) {
try {
return localRequire.resolve(name)
} catch (err) {
return null
}
}