@@ -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,31 +155,56 @@ 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 )
124159
125- async function resolvePrettierConfigPath ( filePath : string ) : Promise < [ string , string | null ] > {
126- let prettierConfig = await prettierConfigCache . remember ( filePath , async ( ) => {
160+ async function resolvePrettierConfigPath (
161+ filePath : string ,
162+ inputDir : string ,
163+ ) : Promise < [ string , string | null ] > {
164+ // Check cache for this directory
165+ let cached = prettierConfigCache . get ( inputDir )
166+ if ( cached !== undefined ) {
167+ return cached ? [ path . dirname ( cached ) , cached ] : [ process . cwd ( ) , null ]
168+ }
169+
170+ const resolve = async ( ) => {
127171 try {
128172 return await prettier . resolveConfigFile ( filePath )
129173 } catch ( err ) {
130174 console . error ( 'prettier-config-not-found' , 'Failed to resolve Prettier Config' )
131175 console . error ( 'prettier-config-not-found-err' , err )
132176 return null
133177 }
134- } )
178+ }
179+
180+ let prettierConfig = await resolve ( )
181+
182+ // Cache all directories from inputDir up to config location
183+ if ( prettierConfig ) {
184+ cacheForDirs ( prettierConfigCache , inputDir , prettierConfig , path . dirname ( prettierConfig ) )
185+ } else {
186+ prettierConfigCache . set ( inputDir , null )
187+ }
135188
136189 return prettierConfig ? [ path . dirname ( prettierConfig ) , prettierConfig ] : [ process . cwd ( ) , null ]
137190}
138191
139- let resolvedModCache = expiringMap < string , Promise < [ any | null , string | null ] > > ( 10_000 )
192+ let resolvedModCache = expiringMap < string , [ any | null , string | null ] > ( 10_000 )
140193
141194async function resolveTailwindPath (
142195 options : ParserOptions ,
143196 baseDir : string ,
144197) : Promise < [ any | null , string | null ] > {
145198 let pkgName = options . tailwindPackageName ?? 'tailwindcss'
199+ let makeKey = ( dir : string ) => `${ pkgName } :${ dir } `
200+
201+ // Check cache for this directory
202+ let cached = resolvedModCache . get ( makeKey ( baseDir ) )
203+ if ( cached !== undefined ) {
204+ return cached
205+ }
146206
147- return await resolvedModCache . remember ( ` ${ pkgName } : ${ baseDir } ` , async ( ) => {
207+ let resolve = async ( ) => {
148208 let pkgDir : string | null = null
149209 let mod : any | null = null
150210
@@ -156,8 +216,20 @@ async function resolveTailwindPath(
156216 pkgDir = path . dirname ( pkgFile )
157217 } catch { }
158218
159- return [ mod , pkgDir ] as const
160- } )
219+ return [ mod , pkgDir ] as [ any | null , string | null ]
220+ }
221+
222+ let result = await resolve ( )
223+
224+ // Cache all directories from baseDir up to package location
225+ let [ , pkgDir ] = result
226+ if ( pkgDir ) {
227+ cacheForDirs ( resolvedModCache , baseDir , result , pkgDir , makeKey )
228+ } else {
229+ resolvedModCache . set ( makeKey ( baseDir ) , result )
230+ }
231+
232+ return result
161233}
162234
163235function resolveJsConfigPath ( options : ParserOptions , configDir : string ) : string | null {
@@ -168,23 +240,31 @@ function resolveJsConfigPath(options: ParserOptions, configDir: string): string
168240}
169241
170242let configPathCache = new Map < string , string | null > ( )
171- function findClosestJsConfig ( inputDir : string ) : string | null {
172- let configPath : string | null | undefined = configPathCache . get ( inputDir )
173243
174- if ( configPath === undefined ) {
175- try {
176- let foundPath = escalade ( inputDir , ( _ , names ) => {
177- if ( names . includes ( 'tailwind.config.js' ) ) return 'tailwind.config.js'
178- if ( names . includes ( 'tailwind.config.cjs' ) ) return 'tailwind.config.cjs'
179- if ( names . includes ( 'tailwind.config.mjs' ) ) return 'tailwind.config.mjs'
180- if ( names . includes ( 'tailwind.config.ts' ) ) return 'tailwind.config.ts'
181- } )
182-
183- configPath = foundPath ?? null
184- } catch { }
244+ function findClosestJsConfig ( inputDir : string ) : string | null {
245+ // Check cache for this directory
246+ let cached = configPathCache . get ( inputDir )
247+ if ( cached !== undefined ) {
248+ return cached
249+ }
185250
186- configPath ??= null
187- configPathCache . set ( inputDir , configPath )
251+ // Resolve
252+ let configPath : string | null = null
253+ try {
254+ let foundPath = escalade ( inputDir , ( _ , names ) => {
255+ if ( names . includes ( 'tailwind.config.js' ) ) return 'tailwind.config.js'
256+ if ( names . includes ( 'tailwind.config.cjs' ) ) return 'tailwind.config.cjs'
257+ if ( names . includes ( 'tailwind.config.mjs' ) ) return 'tailwind.config.mjs'
258+ if ( names . includes ( 'tailwind.config.ts' ) ) return 'tailwind.config.ts'
259+ } )
260+ configPath = foundPath ?? null
261+ } catch { }
262+
263+ // Cache all directories from inputDir up to config location
264+ if ( configPath ) {
265+ cacheForDirs ( configPathCache , inputDir , configPath , path . dirname ( configPath ) )
266+ } else {
267+ configPathCache . set ( inputDir , null )
188268 }
189269
190270 return configPath
0 commit comments