Skip to content

Commit d914f3a

Browse files
committed
feat(stx): walk componentsDir recursively when resolving by tag name
The component tag resolver previously only descended one level into componentsDir, so a component nested at e.g. `components/A/B/Foo.stx` couldn't be auto-resolved by writing `<Foo />` — every page had to add an explicit `import Foo from '...'` line in `<script server>`. Replace the one-shot subdir walk with a depth-limited DFS (cap 8, skip `node_modules`, skip dotdirs) so any depth of nesting under componentsDir is reachable. Same first-match-wins semantics as before; only the search frontier widens. This unblocks auto-import for component trees like the Stacks dashboard, where shared UI lives under `resources/components/Dashboard/UI/...`.
1 parent a49aaad commit d914f3a

1 file changed

Lines changed: 27 additions & 7 deletions

File tree

packages/stx/src/utils.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -438,18 +438,37 @@ export async function renderComponentWithSlot(
438438
}
439439
if (componentFilePath) break
440440

441-
// Also search subdirectories (one level deep)
441+
// Also search subdirectories recursively. Components routinely live
442+
// in nested folders (e.g. `components/Dashboard/UI/PageLayout.stx`),
443+
// and limiting the walk to a single level meant only direct children
444+
// of `componentsDir` could be auto-resolved by tag name. Walking the
445+
// tree (with a depth cap to keep pathological layouts from blowing
446+
// up startup time) lets `<PageLayout />` find the right file without
447+
// forcing every page to add an explicit import.
442448
if (!componentFilePath) {
443449
try {
444450
const fs = await import('node:fs')
445-
// Check if directory exists before reading
446451
const dirStat = fs.statSync(dir, { throwIfNoEntry: false })
447452
if (!dirStat?.isDirectory()) continue
448453

449-
const entries = fs.readdirSync(dir, { withFileTypes: true })
450-
for (const entry of entries) {
451-
if (entry.isDirectory()) {
452-
const subDir = path.join(dir, entry.name)
454+
const MAX_DEPTH = 8
455+
const stack: Array<{ path: string, depth: number }> = [{ path: dir, depth: 0 }]
456+
while (stack.length > 0 && !componentFilePath) {
457+
const current = stack.pop()!
458+
if (current.depth >= MAX_DEPTH) continue
459+
let entries: import('node:fs').Dirent[]
460+
try {
461+
entries = fs.readdirSync(current.path, { withFileTypes: true })
462+
}
463+
catch {
464+
continue
465+
}
466+
for (const entry of entries) {
467+
if (!entry.isDirectory()) continue
468+
// Skip dirs that are unlikely to hold STX components and that
469+
// can be huge (node_modules) or hidden (dotdirs).
470+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
471+
const subDir = path.join(current.path, entry.name)
453472
for (const variant of uniqueVariants) {
454473
const tryPath = path.join(subDir, variant)
455474
triedPaths.push(tryPath)
@@ -459,10 +478,11 @@ export async function renderComponentWithSlot(
459478
}
460479
}
461480
if (componentFilePath) break
481+
stack.push({ path: subDir, depth: current.depth + 1 })
462482
}
463483
}
464484
}
465-
catch {
485+
catch {
466486
// Ignore directory read errors
467487
}
468488
}

0 commit comments

Comments
 (0)