viewlogic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +519 -0
- package/dist/viewlogic-router.js +2476 -0
- package/dist/viewlogic-router.js.map +7 -0
- package/dist/viewlogic-router.min.js +53 -0
- package/dist/viewlogic-router.min.js.map +7 -0
- package/dist/viewlogic-router.umd.js +120 -0
- package/package.json +85 -0
- package/src/core/ComponentLoader.js +182 -0
- package/src/core/ErrorHandler.js +331 -0
- package/src/core/RouteLoader.js +368 -0
- package/src/plugins/AuthManager.js +505 -0
- package/src/plugins/CacheManager.js +352 -0
- package/src/plugins/I18nManager.js +507 -0
- package/src/plugins/QueryManager.js +402 -0
- package/src/viewlogic-router.js +465 -0
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "viewlogic",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, file-based routing system for Vue 3 applications with zero build configuration",
|
|
5
|
+
"main": "dist/viewlogic-router.umd.js",
|
|
6
|
+
"module": "src/viewlogic-router.js",
|
|
7
|
+
"browser": "dist/viewlogic-router.min.js",
|
|
8
|
+
"umd": "dist/viewlogic-router.umd.js",
|
|
9
|
+
"unpkg": "dist/viewlogic-router.umd.js",
|
|
10
|
+
"jsdelivr": "dist/viewlogic-router.umd.js",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/viewlogic-router.js",
|
|
14
|
+
"dist/viewlogic-router.js.map",
|
|
15
|
+
"dist/viewlogic-router.min.js",
|
|
16
|
+
"dist/viewlogic-router.min.js.map",
|
|
17
|
+
"dist/viewlogic-router.umd.js",
|
|
18
|
+
"src/**/*.js",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "node build/build.js",
|
|
24
|
+
"dev": "node build/build.js --watch",
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"test:watch": "jest --watch",
|
|
27
|
+
"test:coverage": "jest --coverage",
|
|
28
|
+
"serve": "npx http-server . -p 8080 -c-1",
|
|
29
|
+
"serve:test": "npx http-server test-app -p 8081 -c-1",
|
|
30
|
+
"link:local": "npm link",
|
|
31
|
+
"link:test": "cd ../viewlogic && npm link viewlogic",
|
|
32
|
+
"prepublishOnly": "npm run build && npm test",
|
|
33
|
+
"version": "npm run build && git add -A dist",
|
|
34
|
+
"postversion": "git push && git push --tags"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"vue",
|
|
38
|
+
"vue3",
|
|
39
|
+
"router",
|
|
40
|
+
"routing",
|
|
41
|
+
"spa",
|
|
42
|
+
"single-page-app",
|
|
43
|
+
"file-based-routing",
|
|
44
|
+
"zero-config",
|
|
45
|
+
"viewlogic",
|
|
46
|
+
"frontend",
|
|
47
|
+
"framework"
|
|
48
|
+
],
|
|
49
|
+
"author": "hopegiver",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/hopegiver/viewlogic.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/hopegiver/viewlogic/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/hopegiver/viewlogic#readme",
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"vue": "^3.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@babel/core": "^7.28.3",
|
|
64
|
+
"@babel/preset-env": "^7.28.3",
|
|
65
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
66
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
67
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
68
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
69
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
70
|
+
"@vue/test-utils": "^2.4.4",
|
|
71
|
+
"babel-jest": "^30.1.2",
|
|
72
|
+
"esbuild": "^0.25.9",
|
|
73
|
+
"http-server": "^14.1.1",
|
|
74
|
+
"jest": "^29.7.0",
|
|
75
|
+
"jest-environment-jsdom": "^30.1.2",
|
|
76
|
+
"rollup": "^4.9.6",
|
|
77
|
+
"vue": "^3.4.21"
|
|
78
|
+
},
|
|
79
|
+
"engines": {
|
|
80
|
+
"node": ">=14.0.0"
|
|
81
|
+
},
|
|
82
|
+
"publishConfig": {
|
|
83
|
+
"access": "public"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComponentLoader
|
|
3
|
+
* 동적으로 컴포넌트를 로드하고 등록하는 시스템
|
|
4
|
+
*/
|
|
5
|
+
export class ComponentLoader {
|
|
6
|
+
constructor(router = null, options = {}) {
|
|
7
|
+
|
|
8
|
+
this.config = {
|
|
9
|
+
basePath: options.basePath || '/src/components',
|
|
10
|
+
debug: options.debug || false,
|
|
11
|
+
environment: options.environment || 'development',
|
|
12
|
+
...options
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
this.router = router;
|
|
16
|
+
this.loadingPromises = new Map();
|
|
17
|
+
this.unifiedComponents = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 로깅 래퍼 메서드
|
|
22
|
+
*/
|
|
23
|
+
log(level, ...args) {
|
|
24
|
+
if (this.router?.errorHandler) {
|
|
25
|
+
this.router.errorHandler.log(level, 'ComponentLoader', ...args);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 컴포넌트를 비동기로 로드
|
|
31
|
+
*/
|
|
32
|
+
async loadComponent(componentName) {
|
|
33
|
+
if (!componentName || typeof componentName !== 'string') {
|
|
34
|
+
throw new Error('Component name must be a non-empty string');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 이미 로딩 중인 경우 기존 Promise 반환
|
|
38
|
+
if (this.loadingPromises.has(componentName)) {
|
|
39
|
+
return this.loadingPromises.get(componentName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const loadPromise = this._loadComponentFromFile(componentName);
|
|
43
|
+
this.loadingPromises.set(componentName, loadPromise);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const component = await loadPromise;
|
|
47
|
+
return component;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw error;
|
|
50
|
+
} finally {
|
|
51
|
+
this.loadingPromises.delete(componentName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 파일에서 컴포넌트 로드
|
|
57
|
+
*/
|
|
58
|
+
async _loadComponentFromFile(componentName) {
|
|
59
|
+
const componentPath = `${this.config.basePath}/${componentName}.js`;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const module = await import(componentPath);
|
|
63
|
+
const component = module.default;
|
|
64
|
+
|
|
65
|
+
if (!component) {
|
|
66
|
+
throw new Error(`Component '${componentName}' has no default export`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!component.name) {
|
|
70
|
+
component.name = componentName;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.log('debug', `Component '${componentName}' loaded successfully`);
|
|
74
|
+
return component;
|
|
75
|
+
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.log('error', `Failed to load component '${componentName}':`, error);
|
|
78
|
+
throw new Error(`Component '${componentName}' not found: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 컴포넌트 모듈 클리어
|
|
84
|
+
*/
|
|
85
|
+
clearComponents() {
|
|
86
|
+
this.loadingPromises.clear();
|
|
87
|
+
this.unifiedComponents = null;
|
|
88
|
+
this.log('debug', 'All components cleared');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 환경에 따른 모든 컴포넌트 로딩 (캐싱 지원)
|
|
93
|
+
*/
|
|
94
|
+
async loadAllComponents() {
|
|
95
|
+
// 이미 로드된 unifiedComponents가 있으면 반환
|
|
96
|
+
if (this.unifiedComponents) {
|
|
97
|
+
this.log('debug', 'Using existing unified components');
|
|
98
|
+
return this.unifiedComponents;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 운영 모드: 통합 컴포넌트 로딩 시도
|
|
102
|
+
if (this.config.environment === 'production') {
|
|
103
|
+
return await this._loadProductionComponents();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 개발 모드: 개별 컴포넌트 로딩
|
|
107
|
+
return await this._loadDevelopmentComponents();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 운영 모드: 통합 컴포넌트 로딩
|
|
112
|
+
*/
|
|
113
|
+
async _loadProductionComponents() {
|
|
114
|
+
try {
|
|
115
|
+
const componentsPath = `${this.config.routesPath}/_components.js`;
|
|
116
|
+
this.log('info', '[PRODUCTION] Loading unified components from:', componentsPath);
|
|
117
|
+
|
|
118
|
+
const componentsModule = await import(componentsPath);
|
|
119
|
+
|
|
120
|
+
if (typeof componentsModule.registerComponents === 'function') {
|
|
121
|
+
this.unifiedComponents = componentsModule.components || {};
|
|
122
|
+
this.log('info', `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
|
|
123
|
+
return this.unifiedComponents;
|
|
124
|
+
} else {
|
|
125
|
+
throw new Error('registerComponents function not found in components module');
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
this.log('warn', '[PRODUCTION] Failed to load unified components:', error.message);
|
|
129
|
+
this.unifiedComponents = {};
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 개발 모드: 개별 컴포넌트 로딩
|
|
136
|
+
*/
|
|
137
|
+
async _loadDevelopmentComponents() {
|
|
138
|
+
const componentNames = this._getComponentNames();
|
|
139
|
+
const components = {};
|
|
140
|
+
|
|
141
|
+
this.log('info', `[DEVELOPMENT] Loading individual components: ${componentNames.join(', ')}`);
|
|
142
|
+
|
|
143
|
+
for (const name of componentNames) {
|
|
144
|
+
try {
|
|
145
|
+
const component = await this.loadComponent(name);
|
|
146
|
+
if (component) {
|
|
147
|
+
components[name] = component;
|
|
148
|
+
}
|
|
149
|
+
} catch (loadError) {
|
|
150
|
+
this.log('warn', `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.unifiedComponents = components;
|
|
155
|
+
this.log('info', `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
|
|
156
|
+
return components;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 컴포넌트 이름 목록 가져오기
|
|
161
|
+
*/
|
|
162
|
+
_getComponentNames() {
|
|
163
|
+
if (Array.isArray(this.config.componentNames) && this.config.componentNames.length > 0) {
|
|
164
|
+
return [...this.config.componentNames];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 폴백: 기존 하드코딩 목록
|
|
168
|
+
return [
|
|
169
|
+
'Button', 'Modal', 'Card', 'Toast', 'Input', 'Tabs',
|
|
170
|
+
'Checkbox', 'Alert', 'DynamicInclude', 'HtmlInclude'
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 메모리 정리
|
|
176
|
+
*/
|
|
177
|
+
dispose() {
|
|
178
|
+
this.clearComponents();
|
|
179
|
+
this.log('debug', 'ComponentLoader disposed');
|
|
180
|
+
this.router = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViewLogic Error Handling System
|
|
3
|
+
* 라우트 및 시스템 에러 처리 시스템
|
|
4
|
+
*/
|
|
5
|
+
export class ErrorHandler {
|
|
6
|
+
constructor(router, options = {}) {
|
|
7
|
+
this.config = {
|
|
8
|
+
enableErrorReporting: options.enableErrorReporting !== false,
|
|
9
|
+
debug: options.debug || false,
|
|
10
|
+
logLevel: options.logLevel || (options.debug ? 'debug' : 'info'),
|
|
11
|
+
environment: options.environment || 'development'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// 라우터 인스턴스 참조
|
|
15
|
+
this.router = router;
|
|
16
|
+
|
|
17
|
+
// 로그 레벨 매핑
|
|
18
|
+
this.logLevels = {
|
|
19
|
+
error: 0,
|
|
20
|
+
warn: 1,
|
|
21
|
+
info: 2,
|
|
22
|
+
debug: 3
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this.log('info', 'ErrorHandler', 'ErrorHandler initialized with config:', this.config);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 라우트 에러 처리
|
|
30
|
+
*/
|
|
31
|
+
async handleRouteError(routeName, error) {
|
|
32
|
+
let errorCode = 500;
|
|
33
|
+
let errorMessage = '페이지를 로드할 수 없습니다.';
|
|
34
|
+
|
|
35
|
+
this.debug('ErrorHandler', '에러 상세:', error.message, error.name);
|
|
36
|
+
|
|
37
|
+
// 에러 타입 분석 (더 정확한 404 감지)
|
|
38
|
+
if (error.message.includes('not found') ||
|
|
39
|
+
error.message.includes('404') ||
|
|
40
|
+
error.message.includes('Failed to resolve') ||
|
|
41
|
+
error.message.includes('Failed to fetch') ||
|
|
42
|
+
(error.name === 'TypeError' && error.message.includes('resolve'))) {
|
|
43
|
+
errorCode = 404;
|
|
44
|
+
errorMessage = `'${routeName}' 페이지를 찾을 수 없습니다.`;
|
|
45
|
+
} else if (error.message.includes('network') && !error.message.includes('not found')) {
|
|
46
|
+
errorCode = 503;
|
|
47
|
+
errorMessage = '네트워크 연결을 확인해 주세요.';
|
|
48
|
+
} else if (error.message.includes('permission') || error.message.includes('403')) {
|
|
49
|
+
errorCode = 403;
|
|
50
|
+
errorMessage = '페이지에 접근할 권한이 없습니다.';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.debug('ErrorHandler', `에러 코드 결정: ${errorCode} (라우트: ${routeName})`);
|
|
54
|
+
|
|
55
|
+
// 에러 리포팅
|
|
56
|
+
if (this.config.enableErrorReporting) {
|
|
57
|
+
this.reportError(routeName, error, errorCode);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// 404 페이지 전용 처리
|
|
62
|
+
if (errorCode === 404) {
|
|
63
|
+
await this.load404Page();
|
|
64
|
+
} else {
|
|
65
|
+
// 일반 에러 페이지
|
|
66
|
+
await this.loadErrorPage(errorCode, errorMessage);
|
|
67
|
+
}
|
|
68
|
+
} catch (fallbackError) {
|
|
69
|
+
this.error('ErrorHandler', '에러 페이지 로딩 실패:', fallbackError);
|
|
70
|
+
// 모든 에러 페이지가 실패했을 때 최후의 폴백 페이지 표시
|
|
71
|
+
this.showFallbackErrorPage(errorCode, errorMessage);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 404 페이지 로딩
|
|
77
|
+
*/
|
|
78
|
+
async load404Page() {
|
|
79
|
+
try {
|
|
80
|
+
this.info('ErrorHandler', 'Loading 404 page...');
|
|
81
|
+
const component = await this.createVueComponent('404');
|
|
82
|
+
await this.renderComponentWithTransition(component, '404');
|
|
83
|
+
this.info('ErrorHandler', '404 page loaded successfully');
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.error('ErrorHandler', '404 page loading failed:', error);
|
|
86
|
+
// 404 페이지도 없으면 간단한 에러 메시지 표시
|
|
87
|
+
this.showFallbackErrorPage('404', '페이지를 찾을 수 없습니다.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 에러 페이지 로딩
|
|
93
|
+
*/
|
|
94
|
+
async loadErrorPage(errorCode, errorMessage) {
|
|
95
|
+
try {
|
|
96
|
+
this.info('ErrorHandler', `Loading error page for ${errorCode}...`);
|
|
97
|
+
|
|
98
|
+
// 에러 컴포넌트 생성
|
|
99
|
+
const errorComponent = await this.createErrorComponent(errorCode, errorMessage);
|
|
100
|
+
await this.renderComponentWithTransition(errorComponent, 'error');
|
|
101
|
+
this.info('ErrorHandler', `Error page ${errorCode} loaded successfully`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this.error('ErrorHandler', `Error page ${errorCode} loading failed:`, error);
|
|
104
|
+
// 에러 페이지도 로딩 실패하면 폴백 표시
|
|
105
|
+
this.showFallbackErrorPage(errorCode, errorMessage);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 에러 컴포넌트 생성
|
|
111
|
+
*/
|
|
112
|
+
async createErrorComponent(errorCode, errorMessage) {
|
|
113
|
+
try {
|
|
114
|
+
// 에러 컴포넌트를 동적으로 로드
|
|
115
|
+
const component = await this.createVueComponent('error');
|
|
116
|
+
|
|
117
|
+
// 에러 정보를 props로 전달
|
|
118
|
+
const errorComponent = {
|
|
119
|
+
...component,
|
|
120
|
+
data() {
|
|
121
|
+
const originalData = component.data ? component.data() : {};
|
|
122
|
+
return {
|
|
123
|
+
...originalData,
|
|
124
|
+
errorCode,
|
|
125
|
+
errorMessage,
|
|
126
|
+
showRetry: true,
|
|
127
|
+
showGoHome: true
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return errorComponent;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// 에러 컴포넌트도 로드할 수 없는 경우 간단한 에러 표시
|
|
135
|
+
this.error('ErrorHandler', 'Error component load failed:', error);
|
|
136
|
+
throw new Error(`Cannot load error page: ${error.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 폴백 에러 페이지 표시 (모든 에러 페이지가 실패했을 때)
|
|
142
|
+
*/
|
|
143
|
+
showFallbackErrorPage(errorCode, errorMessage) {
|
|
144
|
+
const appElement = document.getElementById('app');
|
|
145
|
+
if (!appElement) return;
|
|
146
|
+
|
|
147
|
+
const fallbackHTML = `
|
|
148
|
+
<div class="fallback-error-page" style="
|
|
149
|
+
display: flex;
|
|
150
|
+
flex-direction: column;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
min-height: 100vh;
|
|
154
|
+
padding: 2rem;
|
|
155
|
+
text-align: center;
|
|
156
|
+
background: #f8f9fa;
|
|
157
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
158
|
+
">
|
|
159
|
+
<div style="
|
|
160
|
+
background: white;
|
|
161
|
+
padding: 3rem;
|
|
162
|
+
border-radius: 12px;
|
|
163
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
|
164
|
+
max-width: 500px;
|
|
165
|
+
">
|
|
166
|
+
<h1 style="
|
|
167
|
+
font-size: 4rem;
|
|
168
|
+
margin: 0;
|
|
169
|
+
color: #dc3545;
|
|
170
|
+
font-weight: 300;
|
|
171
|
+
">${errorCode}</h1>
|
|
172
|
+
<h2 style="
|
|
173
|
+
margin: 1rem 0;
|
|
174
|
+
color: #495057;
|
|
175
|
+
font-weight: 400;
|
|
176
|
+
">${errorMessage}</h2>
|
|
177
|
+
<p style="
|
|
178
|
+
color: #6c757d;
|
|
179
|
+
margin-bottom: 2rem;
|
|
180
|
+
line-height: 1.5;
|
|
181
|
+
">요청하신 페이지를 찾을 수 없습니다.</p>
|
|
182
|
+
<button onclick="window.location.hash = '#/'" style="
|
|
183
|
+
background: #007bff;
|
|
184
|
+
color: white;
|
|
185
|
+
border: none;
|
|
186
|
+
padding: 12px 24px;
|
|
187
|
+
border-radius: 6px;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
font-size: 1rem;
|
|
190
|
+
transition: background 0.2s;
|
|
191
|
+
" onmouseover="this.style.background='#0056b3'" onmouseout="this.style.background='#007bff'">
|
|
192
|
+
홈으로 돌아가기
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
// 기존 컨테이너들 정리
|
|
199
|
+
appElement.innerHTML = fallbackHTML;
|
|
200
|
+
|
|
201
|
+
this.info('ErrorHandler', `Fallback error page displayed for ${errorCode}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 에러 리포팅
|
|
206
|
+
*/
|
|
207
|
+
reportError(routeName, error, errorCode) {
|
|
208
|
+
const errorReport = {
|
|
209
|
+
route: routeName,
|
|
210
|
+
errorCode,
|
|
211
|
+
errorMessage: error.message,
|
|
212
|
+
stack: error.stack,
|
|
213
|
+
url: window.location.href,
|
|
214
|
+
userAgent: navigator.userAgent,
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
routerConfig: {
|
|
217
|
+
environment: this.router.config.environment,
|
|
218
|
+
mode: this.router.config.mode
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
this.error('ErrorHandler', '라우터 에러 리포트:', errorReport);
|
|
223
|
+
|
|
224
|
+
// 추후 에러 추적 서비스로 전송할 수 있음
|
|
225
|
+
// 예: analytics.track('router_error', errorReport);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Vue 컴포넌트 생성 (RouteLoader 위임)
|
|
230
|
+
*/
|
|
231
|
+
async createVueComponent(routeName) {
|
|
232
|
+
if (this.router.routeLoader) {
|
|
233
|
+
return await this.router.routeLoader.createVueComponent(routeName);
|
|
234
|
+
}
|
|
235
|
+
throw new Error('RouteLoader not available');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 컴포넌트 렌더링 (ViewManager 위임)
|
|
240
|
+
*/
|
|
241
|
+
async renderComponentWithTransition(component, routeName) {
|
|
242
|
+
if (this.router.renderComponentWithTransition) {
|
|
243
|
+
return await this.router.renderComponentWithTransition(component, routeName);
|
|
244
|
+
}
|
|
245
|
+
throw new Error('Render function not available');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 통합 로깅 시스템
|
|
250
|
+
* @param {string} level - 로그 레벨 (error, warn, info, debug)
|
|
251
|
+
* @param {string} component - 컴포넌트 이름 (선택적)
|
|
252
|
+
* @param {...any} args - 로그 메시지
|
|
253
|
+
*/
|
|
254
|
+
log(level, component, ...args) {
|
|
255
|
+
// 하위 호환성: 기존 방식도 지원
|
|
256
|
+
if (typeof level !== 'string' || !this.logLevels.hasOwnProperty(level)) {
|
|
257
|
+
args = [component, ...args];
|
|
258
|
+
component = level;
|
|
259
|
+
level = this.config.debug ? 'debug' : 'info';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 로그 레벨 확인
|
|
263
|
+
const currentLevelValue = this.logLevels[this.config.logLevel] || this.logLevels.info;
|
|
264
|
+
const messageLevelValue = this.logLevels[level] || this.logLevels.info;
|
|
265
|
+
|
|
266
|
+
if (messageLevelValue > currentLevelValue) {
|
|
267
|
+
return; // 현재 설정된 레벨보다 높으면 출력 안함
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 프로덕션 환경에서는 error와 warn만 출력
|
|
271
|
+
if (this.config.environment === 'production' && messageLevelValue > this.logLevels.warn) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const prefix = component ? `[${component}]` : '[ViewLogic]';
|
|
276
|
+
const timestamp = new Date().toISOString().substring(11, 23); // HH:MM:SS.mmm
|
|
277
|
+
|
|
278
|
+
switch (level) {
|
|
279
|
+
case 'error':
|
|
280
|
+
console.error(`${timestamp} ${prefix}`, ...args);
|
|
281
|
+
break;
|
|
282
|
+
case 'warn':
|
|
283
|
+
console.warn(`${timestamp} ${prefix}`, ...args);
|
|
284
|
+
break;
|
|
285
|
+
case 'info':
|
|
286
|
+
console.info(`${timestamp} ${prefix}`, ...args);
|
|
287
|
+
break;
|
|
288
|
+
case 'debug':
|
|
289
|
+
console.log(`${timestamp} ${prefix}`, ...args);
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
console.log(`${timestamp} ${prefix}`, ...args);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 에러 로그 (항상 출력)
|
|
298
|
+
*/
|
|
299
|
+
error(component, ...args) {
|
|
300
|
+
this.log('error', component, ...args);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 경고 로그
|
|
305
|
+
*/
|
|
306
|
+
warn(component, ...args) {
|
|
307
|
+
this.log('warn', component, ...args);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 정보 로그
|
|
312
|
+
*/
|
|
313
|
+
info(component, ...args) {
|
|
314
|
+
this.log('info', component, ...args);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 디버그 로그
|
|
319
|
+
*/
|
|
320
|
+
debug(component, ...args) {
|
|
321
|
+
this.log('debug', component, ...args);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 정리 (메모리 누수 방지)
|
|
326
|
+
*/
|
|
327
|
+
destroy() {
|
|
328
|
+
this.router = null;
|
|
329
|
+
this.info('ErrorHandler', 'ErrorHandler destroyed');
|
|
330
|
+
}
|
|
331
|
+
}
|