viewlogic 1.0.2 → 1.0.4
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/dist/viewlogic-router.js +24 -10
- package/dist/viewlogic-router.js.map +2 -2
- package/dist/viewlogic-router.min.js +3 -3
- package/dist/viewlogic-router.min.js.map +3 -3
- package/dist/viewlogic-router.umd.js +1 -1
- package/package.json +1 -2
- package/src/core/ComponentLoader.js +0 -182
- package/src/core/ErrorHandler.js +0 -331
- package/src/core/RouteLoader.js +0 -368
- package/src/plugins/AuthManager.js +0 -505
- package/src/plugins/CacheManager.js +0 -352
- package/src/plugins/I18nManager.js +0 -507
- package/src/plugins/QueryManager.js +0 -402
- package/src/viewlogic-router.js +0 -469
package/src/core/RouteLoader.js
DELETED
|
@@ -1,368 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ViewLogic Route Loading System
|
|
3
|
-
* 라우트 로딩 및 컴포넌트 관리 시스템
|
|
4
|
-
*/
|
|
5
|
-
export class RouteLoader {
|
|
6
|
-
constructor(router, options = {}) {
|
|
7
|
-
this.config = {
|
|
8
|
-
basePath: options.basePath || '/src',
|
|
9
|
-
routesPath: options.routesPath || '/routes',
|
|
10
|
-
environment: options.environment || 'development',
|
|
11
|
-
useLayout: options.useLayout !== false,
|
|
12
|
-
defaultLayout: options.defaultLayout || 'default',
|
|
13
|
-
useComponents: options.useComponents !== false,
|
|
14
|
-
debug: options.debug || false
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// 라우터 인스턴스 참조
|
|
18
|
-
this.router = router;
|
|
19
|
-
this.log('debug', 'RouteLoader initialized with config:', this.config);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 스크립트 파일 로드
|
|
24
|
-
*/
|
|
25
|
-
async loadScript(routeName) {
|
|
26
|
-
let script;
|
|
27
|
-
try {
|
|
28
|
-
if (this.config.environment === 'production') {
|
|
29
|
-
// 프로덕션 모드: routes 폴더에서 빌드된 JS 로드 (절대 경로)
|
|
30
|
-
const importPath = `${this.config.routesPath}/${routeName}.js`;
|
|
31
|
-
this.log('debug', `Loading production route: ${importPath}`);
|
|
32
|
-
const module = await import(importPath);
|
|
33
|
-
script = module.default;
|
|
34
|
-
} else {
|
|
35
|
-
// 개발 모드: src 폴더에서 로드 (절대 경로)
|
|
36
|
-
const importPath = `${this.config.basePath}/logic/${routeName}.js`;
|
|
37
|
-
this.log('debug', `Loading development route: ${importPath}`);
|
|
38
|
-
const module = await import(importPath);
|
|
39
|
-
script = module.default;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!script) {
|
|
43
|
-
throw new Error(`Route '${routeName}' not found - no default export`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
} catch (error) {
|
|
47
|
-
// import 에러를 404로 분류
|
|
48
|
-
if (error.message.includes('Failed to resolve') ||
|
|
49
|
-
error.message.includes('Failed to fetch') ||
|
|
50
|
-
error.message.includes('not found') ||
|
|
51
|
-
error.name === 'TypeError') {
|
|
52
|
-
throw new Error(`Route '${routeName}' not found - 404`);
|
|
53
|
-
}
|
|
54
|
-
// 다른 에러는 그대로 전파
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return script;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 템플릿 파일 로드 (실패시 기본값 반환)
|
|
63
|
-
*/
|
|
64
|
-
async loadTemplate(routeName) {
|
|
65
|
-
try {
|
|
66
|
-
const response = await fetch(`${this.config.basePath}/views/${routeName}.html`);
|
|
67
|
-
if (!response.ok) throw new Error(`Template not found: ${response.status}`);
|
|
68
|
-
const template = await response.text();
|
|
69
|
-
this.log('debug', `Template '${routeName}' loaded successfully`);
|
|
70
|
-
return template;
|
|
71
|
-
} catch (error) {
|
|
72
|
-
this.log('warn', `Template '${routeName}' not found, using default:`, error.message);
|
|
73
|
-
// 기본 템플릿 반환
|
|
74
|
-
return this.generateDefaultTemplate(routeName);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 스타일 파일 로드 (실패시 빈 문자열 반환)
|
|
80
|
-
*/
|
|
81
|
-
async loadStyle(routeName) {
|
|
82
|
-
try {
|
|
83
|
-
const response = await fetch(`${this.config.basePath}/styles/${routeName}.css`);
|
|
84
|
-
if (!response.ok) throw new Error(`Style not found: ${response.status}`);
|
|
85
|
-
const style = await response.text();
|
|
86
|
-
this.log('debug', `Style '${routeName}' loaded successfully`);
|
|
87
|
-
return style;
|
|
88
|
-
} catch (error) {
|
|
89
|
-
this.log('debug', `Style '${routeName}' not found, no styles applied:`, error.message);
|
|
90
|
-
// 스타일이 없으면 빈 문자열 반환
|
|
91
|
-
return '';
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* 레이아웃 파일 로드 (실패시 null 반환)
|
|
97
|
-
*/
|
|
98
|
-
async loadLayout(layoutName) {
|
|
99
|
-
try {
|
|
100
|
-
const response = await fetch(`${this.config.basePath}/layouts/${layoutName}.html`);
|
|
101
|
-
if (!response.ok) throw new Error(`Layout not found: ${response.status}`);
|
|
102
|
-
const layout = await response.text();
|
|
103
|
-
|
|
104
|
-
this.log('debug', `Layout '${layoutName}' loaded successfully`);
|
|
105
|
-
return layout;
|
|
106
|
-
} catch (error) {
|
|
107
|
-
this.log('debug', `Layout '${layoutName}' not found, no layout applied:`, error.message);
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* 레이아웃과 템플릿 병합
|
|
114
|
-
*/
|
|
115
|
-
mergeLayoutWithTemplate(routeName, layout, template) {
|
|
116
|
-
let result;
|
|
117
|
-
// 레이아웃에서 <slot name="content"> 부분을 템플릿으로 교체
|
|
118
|
-
if (layout.includes('{{ content }}')) {
|
|
119
|
-
result = layout.replace(
|
|
120
|
-
/{{ content }}/s,
|
|
121
|
-
template
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
// slot이 없으면 main-content 클래스 내용 교체
|
|
125
|
-
else if (layout.includes('class="main-content"')) {
|
|
126
|
-
this.log('debug', 'Using main-content replacement');
|
|
127
|
-
result = layout.replace(
|
|
128
|
-
/(<div class="container">).*?(<\/div>\s*<\/main>)/s,
|
|
129
|
-
`$1${template}$2`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
// 마지막 대안: 전체 레이아웃을 템플릿으로 감싸기
|
|
133
|
-
else {
|
|
134
|
-
this.log('debug', 'Wrapping template with layout');
|
|
135
|
-
result = `${layout}\n${template}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return result;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Vue 컴포넌트 생성
|
|
144
|
-
*/
|
|
145
|
-
async createVueComponent(routeName) {
|
|
146
|
-
// 캐시된 Vue 컴포넌트가 있는지 확인
|
|
147
|
-
const cacheKey = `component_${routeName}`;
|
|
148
|
-
const cached = this.router.cacheManager?.getFromCache(cacheKey);
|
|
149
|
-
if (cached) {
|
|
150
|
-
return cached;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const script = await this.loadScript(routeName);
|
|
154
|
-
const router = this.router;
|
|
155
|
-
const isProduction = this.config.environment === 'production';
|
|
156
|
-
|
|
157
|
-
// 환경별 리소스 로딩
|
|
158
|
-
let template, style = '', layout = null;
|
|
159
|
-
|
|
160
|
-
if (isProduction) {
|
|
161
|
-
// 프로덕션: 스크립트에 있는 템플릿 사용 또는 기본값
|
|
162
|
-
template = script.template || this.generateDefaultTemplate(routeName);
|
|
163
|
-
} else {
|
|
164
|
-
// 개발: 개별 파일들 로드
|
|
165
|
-
template = await this.loadTemplate(routeName);
|
|
166
|
-
style = await this.loadStyle(routeName);
|
|
167
|
-
layout = this.config.useLayout && script.layout !== null ?
|
|
168
|
-
await this.loadLayout(script.layout || this.config.defaultLayout) : null;
|
|
169
|
-
|
|
170
|
-
// 레이아웃과 템플릿 병합
|
|
171
|
-
if (layout) {
|
|
172
|
-
template = this.mergeLayoutWithTemplate(routeName, layout, template);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 단일 컴포넌트 생성
|
|
177
|
-
const component = {
|
|
178
|
-
...script,
|
|
179
|
-
name: script.name || this.toPascalCase(routeName),
|
|
180
|
-
template,
|
|
181
|
-
components: this.config.useComponents && router.componentLoader ?
|
|
182
|
-
await router.componentLoader.loadAllComponents() : {},
|
|
183
|
-
data() {
|
|
184
|
-
const originalData = script.data ? script.data() : {};
|
|
185
|
-
const commonData = {
|
|
186
|
-
...originalData,
|
|
187
|
-
currentRoute: routeName,
|
|
188
|
-
pageTitle: script.pageTitle || router.routeLoader.generatePageTitle(routeName),
|
|
189
|
-
showHeader: script.showHeader !== false,
|
|
190
|
-
headerTitle: script.headerTitle || router.routeLoader.generatePageTitle(routeName),
|
|
191
|
-
headerSubtitle: script.headerSubtitle,
|
|
192
|
-
$query: router.queryManager?.getQueryParams() || {},
|
|
193
|
-
$lang: router.i18nManager?.getCurrentLanguage() || router.config.i18nDefaultLanguage,
|
|
194
|
-
$dataLoading: false
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
return commonData;
|
|
198
|
-
},
|
|
199
|
-
computed: {
|
|
200
|
-
...(script.computed || {}),
|
|
201
|
-
params() {
|
|
202
|
-
return router.queryManager?.getQueryParams() || {};
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
async mounted() {
|
|
206
|
-
if (script.mounted) {
|
|
207
|
-
await script.mounted.call(this);
|
|
208
|
-
}
|
|
209
|
-
if (script.dataURL) {
|
|
210
|
-
await this.$fetchData();
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
methods: {
|
|
214
|
-
...script.methods,
|
|
215
|
-
// 라우팅 관련
|
|
216
|
-
navigateTo: (route, params) => router.navigateTo(route, params),
|
|
217
|
-
getCurrentRoute: () => router.getCurrentRoute(),
|
|
218
|
-
getQueryParams: () => router.queryManager?.getQueryParams() || {},
|
|
219
|
-
getQueryParam: (key) => router.queryManager?.getQueryParam(key),
|
|
220
|
-
setQueryParams: (params, replace) => router.queryManager?.setQueryParams(params, replace),
|
|
221
|
-
removeQueryParams: (keys) => router.queryManager?.removeQueryParams(keys),
|
|
222
|
-
// i18n 관련
|
|
223
|
-
$t: (key, params) => router.i18nManager?.t(key, params) || key,
|
|
224
|
-
$i18n: () => router.i18nManager || null,
|
|
225
|
-
// 인증 관련
|
|
226
|
-
$isAuthenticated: () => router.authManager?.isUserAuthenticated() || false,
|
|
227
|
-
$logout: () => router.authManager ? router.navigateTo(router.authManager.handleLogout()) : null,
|
|
228
|
-
$loginSuccess: (target) => router.authManager ? router.navigateTo(router.authManager.handleLoginSuccess(target)) : null,
|
|
229
|
-
$checkAuth: (route) => router.authManager ? router.authManager.checkAuthentication(route) : Promise.resolve({ allowed: true, reason: 'auth_disabled' }),
|
|
230
|
-
$getToken: () => router.authManager?.getAccessToken() || null,
|
|
231
|
-
$setToken: (token, options) => router.authManager?.setAccessToken(token, options) || false,
|
|
232
|
-
$removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
|
|
233
|
-
$getAuthCookie: () => router.authManager?.getAuthCookie() || null,
|
|
234
|
-
$getCookie: (name) => router.authManager?.getCookieValue(name) || null,
|
|
235
|
-
// 데이터 fetch
|
|
236
|
-
async $fetchData() {
|
|
237
|
-
if (!script.dataURL) return;
|
|
238
|
-
|
|
239
|
-
this.$dataLoading = true;
|
|
240
|
-
try {
|
|
241
|
-
const data = await router.routeLoader.fetchComponentData(script.dataURL);
|
|
242
|
-
if (router.errorHandler) router.errorHandler.debug('RouteLoader', `Data fetched for ${routeName}:`, data);
|
|
243
|
-
Object.assign(this, data);
|
|
244
|
-
this.$emit('data-loaded', data);
|
|
245
|
-
} catch (error) {
|
|
246
|
-
if (router.errorHandler) router.errorHandler.warn('RouteLoader', `Failed to fetch data for ${routeName}:`, error);
|
|
247
|
-
this.$emit('data-error', error);
|
|
248
|
-
} finally {
|
|
249
|
-
this.$dataLoading = false;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
_routeName: routeName
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
// 개발 모드에서만 스타일 메타데이터 저장 (렌더링 시 주입용)
|
|
257
|
-
if (!isProduction && style) {
|
|
258
|
-
component._style = style;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// 캐시에 저장
|
|
262
|
-
this.router.cacheManager?.setCache(cacheKey, component);
|
|
263
|
-
|
|
264
|
-
return component;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* 문자열을 PascalCase로 변환
|
|
269
|
-
*/
|
|
270
|
-
toPascalCase(str) {
|
|
271
|
-
return str
|
|
272
|
-
.split(/[-_\s]+/) // 하이픈, 언더스코어, 공백으로 분리
|
|
273
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
274
|
-
.join('');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* 기본 템플릿 생성
|
|
279
|
-
*/
|
|
280
|
-
generateDefaultTemplate(routeName) {
|
|
281
|
-
return `<div class="route-${routeName}"><h1>Route: ${routeName}</h1></div>`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* 컴포넌트 데이터 가져오기
|
|
286
|
-
*/
|
|
287
|
-
async fetchComponentData(dataURL) {
|
|
288
|
-
try {
|
|
289
|
-
// 현재 쿼리 파라미터를 URL에 추가
|
|
290
|
-
const queryString = this.router.queryManager?.buildQueryString(this.router.queryManager?.getQueryParams()) || '';
|
|
291
|
-
const fullURL = queryString ? `${dataURL}?${queryString}` : dataURL;
|
|
292
|
-
|
|
293
|
-
this.log('debug', `Fetching data from: ${fullURL}`);
|
|
294
|
-
|
|
295
|
-
const response = await fetch(fullURL, {
|
|
296
|
-
method: 'GET',
|
|
297
|
-
headers: {
|
|
298
|
-
'Content-Type': 'application/json',
|
|
299
|
-
'Accept': 'application/json'
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
if (!response.ok) {
|
|
304
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const data = await response.json();
|
|
308
|
-
|
|
309
|
-
// 데이터 유효성 검사
|
|
310
|
-
if (typeof data !== 'object' || data === null) {
|
|
311
|
-
throw new Error('Invalid data format: expected object');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return data;
|
|
315
|
-
|
|
316
|
-
} catch (error) {
|
|
317
|
-
this.log('error', 'Failed to fetch component data:', error);
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 캐시 무효화
|
|
324
|
-
*/
|
|
325
|
-
invalidateCache(routeName) {
|
|
326
|
-
if (this.router.cacheManager) {
|
|
327
|
-
this.router.cacheManager.invalidateComponentCache(routeName);
|
|
328
|
-
}
|
|
329
|
-
this.log('debug', `Cache invalidated for route: ${routeName}`);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* 통계 정보 반환
|
|
334
|
-
*/
|
|
335
|
-
getStats() {
|
|
336
|
-
return {
|
|
337
|
-
environment: this.config.environment,
|
|
338
|
-
basePath: this.config.basePath,
|
|
339
|
-
routesPath: this.config.routesPath,
|
|
340
|
-
useLayout: this.config.useLayout,
|
|
341
|
-
useComponents: this.config.useComponents
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* 페이지 제목 생성
|
|
347
|
-
*/
|
|
348
|
-
generatePageTitle(routeName) {
|
|
349
|
-
return this.toPascalCase(routeName).replace(/([A-Z])/g, ' $1').trim();
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* 로깅 래퍼 메서드
|
|
354
|
-
*/
|
|
355
|
-
log(level, ...args) {
|
|
356
|
-
if (this.router?.errorHandler) {
|
|
357
|
-
this.router.errorHandler.log(level, 'RouteLoader', ...args);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* 정리 (메모리 누수 방지)
|
|
363
|
-
*/
|
|
364
|
-
destroy() {
|
|
365
|
-
this.log('debug', 'RouteLoader destroyed');
|
|
366
|
-
this.router = null;
|
|
367
|
-
}
|
|
368
|
-
}
|