Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cache for known plugins #301

Merged
merged 3 commits into from
Jun 29, 2024
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])
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
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: {},
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
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)
thecrypticace marked this conversation as resolved.
Show resolved Hide resolved
}

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
}
}
Loading