@@ -11,6 +11,41 @@ import type { UnifiedApi } from './types'
1111import { loadV3 } from './versions/v3'
1212import { loadV4 } from './versions/v4'
1313
14+ /**
15+ * Cache a value for all directories from `inputDir` up to `targetDir` (inclusive).
16+ * Stops early if an existing cache entry is found.
17+ *
18+ * How it works:
19+ *
20+ * For a file at '/repo/packages/ui/src/Button.tsx' with config at '/repo/package.json'
21+ *
22+ * `cacheForDirs(cache, '/repo/packages/ui/src', '/repo/package.json', '/repo')`
23+ *
24+ * Caches:
25+ * - '/repo/packages/ui/src' -> '/repo/package.json'
26+ * - '/repo/packages/ui' -> '/repo/package.json'
27+ * - '/repo/packages' -> '/repo/package.json'
28+ * - '/repo' -> '/repo/package.json'
29+ */
30+ function cacheForDirs < V > (
31+ cache : { set ( key : string , value : V ) : void , get ( key : string ) : V | undefined } ,
32+ inputDir : string ,
33+ value : V ,
34+ targetDir : string ,
35+ makeKey : ( dir : string ) => string = ( dir ) => dir ,
36+ ) : void {
37+ let dir = inputDir
38+ while ( dir !== path . dirname ( dir ) && dir . length >= targetDir . length ) {
39+ const key = makeKey ( dir ) ;
40+ // Stop caching if we hit an existing entry
41+ if ( cache . get ( key ) !== undefined ) break
42+
43+ cache . set ( key , value )
44+ if ( dir === targetDir ) break
45+ dir = path . dirname ( dir )
46+ }
47+ }
48+
1449let pathToApiMap = expiringMap < string | null , Promise < UnifiedApi > > ( 10_000 )
1550
1651export async function getTailwindConfig ( options : ParserOptions ) : Promise < any > {
@@ -34,7 +69,7 @@ export async function getTailwindConfig(options: ParserOptions): Promise<any> {
3469 //
3570 // These lookups can take a bit so we cache them. This is especially important
3671 // for files with lots of embedded languages (e.g. Vue bindings).
37- let [ configDir , configPath ] = await resolvePrettierConfigPath ( options . filepath )
72+ let [ configDir , configPath ] = await resolvePrettierConfigPath ( options . filepath , inputDir )
3873
3974 // Locate Tailwind CSS itself
4075 //
@@ -120,28 +155,51 @@ export async function getTailwindConfig(options: ParserOptions): Promise<any> {
120155 return pathToApiMap . remember ( `${ pkgDir } :${ stylesheet } ` , ( ) => loadV4 ( mod , stylesheet ) )
121156}
122157
123- let prettierConfigCache = expiringMap < string , Promise < string | null > > ( 10_000 )
158+ let prettierConfigCache = expiringMap < string , string | null > ( 10_000 )
159+
160+ async function resolvePrettierConfigPath ( filePath : string , inputDir : string ) : Promise < [ string , string | null ] > {
161+
162+ // Check cache for this directory
163+ let cached = prettierConfigCache . get ( inputDir )
164+ if ( cached !== undefined ) {
165+ return cached ? [ path . dirname ( cached ) , cached ] : [ process . cwd ( ) , null ]
166+ }
124167
125- async function resolvePrettierConfigPath ( filePath : string ) : Promise < [ string , string | null ] > {
126- let prettierConfig = await prettierConfigCache . remember ( filePath , async ( ) => {
168+ const resolve = async ( ) => {
127169 try {
128170 return await prettier . resolveConfigFile ( filePath )
129171 } catch ( err ) {
130172 console . error ( 'prettier-config-not-found' , 'Failed to resolve Prettier Config' )
131173 console . error ( 'prettier-config-not-found-err' , err )
132174 return null
133175 }
134- } )
176+ }
177+
178+ let prettierConfig = await resolve ( )
179+
180+ // Cache all directories from inputDir up to config location
181+ if ( prettierConfig ) {
182+ cacheForDirs ( prettierConfigCache , inputDir , prettierConfig , path . dirname ( prettierConfig ) )
183+ } else {
184+ prettierConfigCache . set ( inputDir , null )
185+ }
135186
136187 return prettierConfig ? [ path . dirname ( prettierConfig ) , prettierConfig ] : [ process . cwd ( ) , null ]
137188}
138189
139- let resolvedModCache = expiringMap < string , Promise < [ any | null , string | null ] > > ( 10_000 )
190+ let resolvedModCache = expiringMap < string , [ any | null , string | null ] > ( 10_000 )
140191
141192async function resolveTailwindPath ( options : ParserOptions , baseDir : string ) : Promise < [ any | null , string | null ] > {
142193 let pkgName = options . tailwindPackageName ?? 'tailwindcss'
194+ let makeKey = ( dir : string ) => `${ pkgName } :${ dir } `
195+
196+ // Check cache for this directory
197+ let cached = resolvedModCache . get ( makeKey ( baseDir ) )
198+ if ( cached !== undefined ) {
199+ return cached
200+ }
143201
144- return await resolvedModCache . remember ( ` ${ pkgName } : ${ baseDir } ` , async ( ) => {
202+ let resolve = async ( ) => {
145203 let pkgDir : string | null = null
146204 let mod : any | null = null
147205
@@ -153,8 +211,20 @@ async function resolveTailwindPath(options: ParserOptions, baseDir: string): Pro
153211 pkgDir = path . dirname ( pkgFile )
154212 } catch { }
155213
156- return [ mod , pkgDir ] as const
157- } )
214+ return [ mod , pkgDir ] as [ any | null , string | null ]
215+ }
216+
217+ let result = await resolve ( )
218+
219+ // Cache all directories from baseDir up to package location
220+ let [ , pkgDir ] = result
221+ if ( pkgDir ) {
222+ cacheForDirs ( resolvedModCache , baseDir , result , pkgDir , makeKey )
223+ } else {
224+ resolvedModCache . set ( makeKey ( baseDir ) , result )
225+ }
226+
227+ return result
158228}
159229
160230function resolveJsConfigPath ( options : ParserOptions , configDir : string ) : string | null {
@@ -165,23 +235,31 @@ function resolveJsConfigPath(options: ParserOptions, configDir: string): string
165235}
166236
167237let configPathCache = new Map < string , string | null > ( )
168- function findClosestJsConfig ( inputDir : string ) : string | null {
169- let configPath : string | null | undefined = configPathCache . get ( inputDir )
170238
171- if ( configPath === undefined ) {
172- try {
173- let foundPath = escalade ( inputDir , ( _ , names ) => {
174- if ( names . includes ( 'tailwind.config.js' ) ) return 'tailwind.config.js'
175- if ( names . includes ( 'tailwind.config.cjs' ) ) return 'tailwind.config.cjs'
176- if ( names . includes ( 'tailwind.config.mjs' ) ) return 'tailwind.config.mjs'
177- if ( names . includes ( 'tailwind.config.ts' ) ) return 'tailwind.config.ts'
178- } )
179-
180- configPath = foundPath ?? null
181- } catch { }
239+ function findClosestJsConfig ( inputDir : string ) : string | null {
240+ // Check cache for this directory
241+ let cached = configPathCache . get ( inputDir )
242+ if ( cached !== undefined ) {
243+ return cached
244+ }
182245
183- configPath ??= null
184- configPathCache . set ( inputDir , configPath )
246+ // Resolve
247+ let configPath : string | null = null
248+ try {
249+ let foundPath = escalade ( inputDir , ( _ , names ) => {
250+ if ( names . includes ( 'tailwind.config.js' ) ) return 'tailwind.config.js'
251+ if ( names . includes ( 'tailwind.config.cjs' ) ) return 'tailwind.config.cjs'
252+ if ( names . includes ( 'tailwind.config.mjs' ) ) return 'tailwind.config.mjs'
253+ if ( names . includes ( 'tailwind.config.ts' ) ) return 'tailwind.config.ts'
254+ } )
255+ configPath = foundPath ?? null
256+ } catch { }
257+
258+ // Cache all directories from inputDir up to config location
259+ if ( configPath ) {
260+ cacheForDirs ( configPathCache , inputDir , configPath , path . dirname ( configPath ) )
261+ } else {
262+ configPathCache . set ( inputDir , null )
185263 }
186264
187265 return configPath
0 commit comments