레이아웃 추가기능
This commit is contained in:
288
frontend/lib/registry/utils/autoDiscovery.ts
Normal file
288
frontend/lib/registry/utils/autoDiscovery.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
"use client";
|
||||
|
||||
import { LayoutRegistry } from "../LayoutRegistry";
|
||||
|
||||
/**
|
||||
* 자동 디스커버리 옵션
|
||||
*/
|
||||
export interface AutoDiscoveryOptions {
|
||||
/** 스캔할 디렉토리 패턴 */
|
||||
pattern?: string;
|
||||
/** 개발 모드에서 상세 로그 출력 */
|
||||
verbose?: boolean;
|
||||
/** 에러 시 계속 진행할지 여부 */
|
||||
continueOnError?: boolean;
|
||||
/** 최대 대기 시간 (ms) */
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 모듈 정보
|
||||
*/
|
||||
export interface LayoutModuleInfo {
|
||||
path: string;
|
||||
id: string;
|
||||
name: string;
|
||||
loaded: boolean;
|
||||
error?: Error;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 디스커버리 결과
|
||||
*/
|
||||
export interface DiscoveryResult {
|
||||
success: boolean;
|
||||
totalFound: number;
|
||||
successfullyLoaded: number;
|
||||
failed: number;
|
||||
modules: LayoutModuleInfo[];
|
||||
errors: Error[];
|
||||
duration: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 자동 디스커버리 클래스
|
||||
*/
|
||||
export class LayoutAutoDiscovery {
|
||||
private static instance: LayoutAutoDiscovery;
|
||||
private discoveryResults: DiscoveryResult[] = [];
|
||||
private isDiscovering = false;
|
||||
|
||||
static getInstance(): LayoutAutoDiscovery {
|
||||
if (!this.instance) {
|
||||
this.instance = new LayoutAutoDiscovery();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 자동 디스커버리 실행
|
||||
*/
|
||||
async discover(options: AutoDiscoveryOptions = {}): Promise<DiscoveryResult> {
|
||||
const startTime = Date.now();
|
||||
const {
|
||||
pattern = "./**/*LayoutRenderer.tsx",
|
||||
verbose = process.env.NODE_ENV === "development",
|
||||
continueOnError = true,
|
||||
timeout = 10000,
|
||||
} = options;
|
||||
|
||||
if (this.isDiscovering) {
|
||||
throw new Error("디스커버리가 이미 진행 중입니다.");
|
||||
}
|
||||
|
||||
this.isDiscovering = true;
|
||||
|
||||
try {
|
||||
if (verbose) {
|
||||
console.log("🔍 레이아웃 자동 디스커버리 시작...");
|
||||
console.log(`📁 스캔 패턴: ${pattern}`);
|
||||
}
|
||||
|
||||
const modules: LayoutModuleInfo[] = [];
|
||||
const errors: Error[] = [];
|
||||
|
||||
// 현재는 정적 import를 사용 (Vite/Next.js 환경에서)
|
||||
const layoutModules = await this.discoverLayoutModules(pattern);
|
||||
|
||||
for (const [path, moduleFactory] of Object.entries(layoutModules)) {
|
||||
const moduleInfo: LayoutModuleInfo = {
|
||||
path,
|
||||
id: this.extractLayoutId(path),
|
||||
name: this.extractLayoutName(path),
|
||||
loaded: false,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
try {
|
||||
// 모듈 로드 시도
|
||||
await this.loadLayoutModule(moduleFactory, moduleInfo);
|
||||
moduleInfo.loaded = true;
|
||||
|
||||
if (verbose) {
|
||||
console.log(`✅ 로드 성공: ${moduleInfo.name} (${moduleInfo.id})`);
|
||||
}
|
||||
} catch (error) {
|
||||
const err = error as Error;
|
||||
moduleInfo.error = err;
|
||||
errors.push(err);
|
||||
|
||||
if (verbose) {
|
||||
console.error(`❌ 로드 실패: ${moduleInfo.name}`, err);
|
||||
}
|
||||
|
||||
if (!continueOnError) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
modules.push(moduleInfo);
|
||||
}
|
||||
|
||||
const result: DiscoveryResult = {
|
||||
success: errors.length === 0,
|
||||
totalFound: modules.length,
|
||||
successfullyLoaded: modules.filter((m) => m.loaded).length,
|
||||
failed: errors.length,
|
||||
modules,
|
||||
errors,
|
||||
duration: Date.now() - startTime,
|
||||
};
|
||||
|
||||
this.discoveryResults.push(result);
|
||||
|
||||
if (verbose) {
|
||||
this.logDiscoveryResult(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
this.isDiscovering = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 모듈 발견
|
||||
*/
|
||||
private async discoverLayoutModules(pattern: string): Promise<Record<string, () => Promise<any>>> {
|
||||
try {
|
||||
// Vite의 import.meta.glob 사용
|
||||
if (typeof import.meta !== "undefined" && import.meta.glob) {
|
||||
return import.meta.glob(pattern, { eager: false });
|
||||
}
|
||||
|
||||
// Next.js의 경우 또는 fallback
|
||||
return await this.discoverModulesViaWebpack(pattern);
|
||||
} catch (error) {
|
||||
console.warn("자동 디스커버리 실패, 수동 import로 전환:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Webpack 기반 모듈 디스커버리 (Next.js용)
|
||||
*/
|
||||
private async discoverModulesViaWebpack(pattern: string): Promise<Record<string, () => Promise<any>>> {
|
||||
// Next.js 환경에서는 require.context 사용
|
||||
if (typeof require !== "undefined" && require.context) {
|
||||
const context = require.context("../layouts", true, /.*LayoutRenderer\.tsx$/);
|
||||
const modules: Record<string, () => Promise<any>> = {};
|
||||
|
||||
context.keys().forEach((key: string) => {
|
||||
modules[key] = () => Promise.resolve(context(key));
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃 모듈 로드
|
||||
*/
|
||||
private async loadLayoutModule(moduleFactory: () => Promise<any>, moduleInfo: LayoutModuleInfo): Promise<void> {
|
||||
const module = await moduleFactory();
|
||||
|
||||
// default export가 있는 경우
|
||||
if (module.default && typeof module.default.registerSelf === "function") {
|
||||
module.default.registerSelf();
|
||||
return;
|
||||
}
|
||||
|
||||
// named export 중에 레이아웃 렌더러가 있는 경우
|
||||
for (const [exportName, exportValue] of Object.entries(module)) {
|
||||
if (exportValue && typeof (exportValue as any).registerSelf === "function") {
|
||||
(exportValue as any).registerSelf();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`레이아웃 렌더러를 찾을 수 없습니다: ${moduleInfo.path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 경로에서 레이아웃 ID 추출
|
||||
*/
|
||||
private extractLayoutId(path: string): string {
|
||||
const match = path.match(/\/([^/]+)LayoutRenderer\.tsx$/);
|
||||
return match ? match[1].toLowerCase() : "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* 경로에서 레이아웃 이름 추출
|
||||
*/
|
||||
private extractLayoutName(path: string): string {
|
||||
const id = this.extractLayoutId(path);
|
||||
return id.charAt(0).toUpperCase() + id.slice(1) + " Layout";
|
||||
}
|
||||
|
||||
/**
|
||||
* 디스커버리 결과 로그 출력
|
||||
*/
|
||||
private logDiscoveryResult(result: DiscoveryResult): void {
|
||||
console.group("📊 레이아웃 디스커버리 결과");
|
||||
console.log(`⏱️ 소요 시간: ${result.duration}ms`);
|
||||
console.log(`📦 발견된 모듈: ${result.totalFound}개`);
|
||||
console.log(`✅ 성공적으로 로드: ${result.successfullyLoaded}개`);
|
||||
console.log(`❌ 실패: ${result.failed}개`);
|
||||
|
||||
if (result.modules.length > 0) {
|
||||
console.table(
|
||||
result.modules.map((m) => ({
|
||||
ID: m.id,
|
||||
Name: m.name,
|
||||
Loaded: m.loaded ? "✅" : "❌",
|
||||
Path: m.path,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.group("❌ 오류 상세:");
|
||||
result.errors.forEach((error) => console.error(error));
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* 이전 디스커버리 결과 조회
|
||||
*/
|
||||
getDiscoveryHistory(): DiscoveryResult[] {
|
||||
return [...this.discoveryResults];
|
||||
}
|
||||
|
||||
/**
|
||||
* 통계 정보 조회
|
||||
*/
|
||||
getStats(): { totalAttempts: number; successRate: number; avgDuration: number } {
|
||||
const attempts = this.discoveryResults.length;
|
||||
const successful = this.discoveryResults.filter((r) => r.success).length;
|
||||
const avgDuration = attempts > 0 ? this.discoveryResults.reduce((sum, r) => sum + r.duration, 0) / attempts : 0;
|
||||
|
||||
return {
|
||||
totalAttempts: attempts,
|
||||
successRate: attempts > 0 ? (successful / attempts) * 100 : 0,
|
||||
avgDuration: Math.round(avgDuration),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 편의 함수: 레이아웃 자동 디스커버리 실행
|
||||
*/
|
||||
export async function discoverLayouts(options?: AutoDiscoveryOptions): Promise<DiscoveryResult> {
|
||||
const discovery = LayoutAutoDiscovery.getInstance();
|
||||
return discovery.discover(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 편의 함수: 통계 정보 조회
|
||||
*/
|
||||
export function getDiscoveryStats() {
|
||||
const discovery = LayoutAutoDiscovery.getInstance();
|
||||
return discovery.getStats();
|
||||
}
|
||||
Reference in New Issue
Block a user