viewlogic 1.0.3 → 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.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 -371
- package/src/plugins/AuthManager.js +0 -505
- package/src/plugins/CacheManager.js +0 -352
- package/src/plugins/I18nManager.js +0 -508
- package/src/plugins/QueryManager.js +0 -402
- package/src/viewlogic-router.js +0 -481
package/src/viewlogic-router.js
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
// ViewLogic Router - ES6 Module
|
|
2
|
-
import { I18nManager } from './plugins/I18nManager.js';
|
|
3
|
-
import { AuthManager } from './plugins/AuthManager.js';
|
|
4
|
-
import { CacheManager } from './plugins/CacheManager.js';
|
|
5
|
-
import { QueryManager } from './plugins/QueryManager.js';
|
|
6
|
-
import { RouteLoader } from './core/RouteLoader.js';
|
|
7
|
-
import { ErrorHandler } from './core/ErrorHandler.js';
|
|
8
|
-
import { ComponentLoader } from './core/ComponentLoader.js';
|
|
9
|
-
|
|
10
|
-
export class ViewLogicRouter {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
// 버전 정보
|
|
13
|
-
this.version = options.version || '1.0.0';
|
|
14
|
-
|
|
15
|
-
// 기본 환경설정 최적화
|
|
16
|
-
this.config = this._buildConfig(options);
|
|
17
|
-
|
|
18
|
-
this.currentHash = '';
|
|
19
|
-
this.currentVueApp = null;
|
|
20
|
-
this.previousVueApp = null; // 이전 Vue 앱 (전환 효과를 위해 보관)
|
|
21
|
-
this.componentLoader = null; // 컴포넌트 로더 인스턴스
|
|
22
|
-
|
|
23
|
-
// LoadingManager가 없을 때를 위한 기본 전환 상태
|
|
24
|
-
this.transitionInProgress = false;
|
|
25
|
-
|
|
26
|
-
// 초기화 준비 상태
|
|
27
|
-
this.isReady = false;
|
|
28
|
-
this.readyPromise = null;
|
|
29
|
-
|
|
30
|
-
// 이벤트 리스너 바인딩 최적화
|
|
31
|
-
this._boundHandleRouteChange = this.handleRouteChange.bind(this);
|
|
32
|
-
|
|
33
|
-
// 모든 초기화를 한번에 처리
|
|
34
|
-
this.readyPromise = this.initialize();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 설정 빌드 (분리하여 가독성 향상)
|
|
39
|
-
*/
|
|
40
|
-
_buildConfig(options) {
|
|
41
|
-
const currentOrigin = window.location.origin;
|
|
42
|
-
|
|
43
|
-
const defaults = {
|
|
44
|
-
basePath: `${currentOrigin}/src`,
|
|
45
|
-
mode: 'hash',
|
|
46
|
-
cacheMode: 'memory',
|
|
47
|
-
cacheTTL: 300000,
|
|
48
|
-
maxCacheSize: 50,
|
|
49
|
-
useLayout: true,
|
|
50
|
-
defaultLayout: 'default',
|
|
51
|
-
environment: 'development',
|
|
52
|
-
routesPath: `${currentOrigin}/routes`,
|
|
53
|
-
enableErrorReporting: true,
|
|
54
|
-
useComponents: true,
|
|
55
|
-
componentNames: ['Button', 'Modal', 'Card', 'Toast', 'Input', 'Tabs', 'Checkbox', 'Alert', 'DynamicInclude', 'HtmlInclude'],
|
|
56
|
-
useI18n: true,
|
|
57
|
-
defaultLanguage: 'ko',
|
|
58
|
-
i18nPath: `${currentOrigin}/i18n`,
|
|
59
|
-
logLevel: 'info',
|
|
60
|
-
authEnabled: false,
|
|
61
|
-
loginRoute: 'login',
|
|
62
|
-
protectedRoutes: [],
|
|
63
|
-
protectedPrefixes: [],
|
|
64
|
-
publicRoutes: ['login', 'register', 'home'],
|
|
65
|
-
checkAuthFunction: null,
|
|
66
|
-
redirectAfterLogin: 'home',
|
|
67
|
-
authCookieName: 'authToken',
|
|
68
|
-
authFallbackCookieNames: ['accessToken', 'token', 'jwt'],
|
|
69
|
-
authStorage: 'cookie',
|
|
70
|
-
authCookieOptions: {},
|
|
71
|
-
authSkipValidation: false,
|
|
72
|
-
enableParameterValidation: true,
|
|
73
|
-
maxParameterLength: 1000,
|
|
74
|
-
maxParameterCount: 50,
|
|
75
|
-
maxArraySize: 100,
|
|
76
|
-
allowedKeyPattern: /^[a-zA-Z0-9_-]+$/,
|
|
77
|
-
logSecurityWarnings: true
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const config = { ...defaults, ...options };
|
|
81
|
-
|
|
82
|
-
// 사용자가 제공한 basePath와 routesPath에 origin이 없으면 추가
|
|
83
|
-
if (options.basePath && !options.basePath.startsWith('http')) {
|
|
84
|
-
config.basePath = `${currentOrigin}${options.basePath}`;
|
|
85
|
-
}
|
|
86
|
-
if (options.routesPath && !options.routesPath.startsWith('http')) {
|
|
87
|
-
config.routesPath = `${currentOrigin}${options.routesPath}`;
|
|
88
|
-
}
|
|
89
|
-
if (options.i18nPath && !options.i18nPath.startsWith('http')) {
|
|
90
|
-
config.i18nPath = `${currentOrigin}${options.i18nPath}`;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return config;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 로깅 래퍼 메서드
|
|
99
|
-
*/
|
|
100
|
-
log(level, ...args) {
|
|
101
|
-
if (this.errorHandler) {
|
|
102
|
-
this.errorHandler.log(level, 'Router', ...args);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 통합 초기화 - 매니저 생성 → 비동기 로딩 → 라우터 시작
|
|
108
|
-
*/
|
|
109
|
-
async initialize() {
|
|
110
|
-
try {
|
|
111
|
-
// 1. 매니저 초기화 (동기)
|
|
112
|
-
// 항상 필요한 매니저들
|
|
113
|
-
this.cacheManager = new CacheManager(this, this.config);
|
|
114
|
-
this.routeLoader = new RouteLoader(this, this.config);
|
|
115
|
-
this.queryManager = new QueryManager(this, this.config);
|
|
116
|
-
this.errorHandler = new ErrorHandler(this, this.config);
|
|
117
|
-
|
|
118
|
-
// 조건부 매니저들
|
|
119
|
-
if (this.config.useI18n) {
|
|
120
|
-
this.i18nManager = new I18nManager(this, this.config);
|
|
121
|
-
if (this.i18nManager.initPromise) {
|
|
122
|
-
await this.i18nManager.initPromise;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (this.config.authEnabled) {
|
|
127
|
-
this.authManager = new AuthManager(this, this.config);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (this.config.useComponents) {
|
|
131
|
-
this.componentLoader = new ComponentLoader(this, {
|
|
132
|
-
...this.config,
|
|
133
|
-
basePath: `${this.config.basePath}/components`,
|
|
134
|
-
cache: true,
|
|
135
|
-
componentNames: this.config.componentNames
|
|
136
|
-
});
|
|
137
|
-
await this.componentLoader.loadAllComponents();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 2. 라우터 시작
|
|
141
|
-
this.isReady = true;
|
|
142
|
-
this.init();
|
|
143
|
-
|
|
144
|
-
} catch (error) {
|
|
145
|
-
this.log('error', 'Router initialization failed:', error);
|
|
146
|
-
// 실패해도 라우터는 시작 (graceful degradation)
|
|
147
|
-
this.isReady = true;
|
|
148
|
-
this.init();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* 라우터가 준비될 때까지 대기
|
|
154
|
-
*/
|
|
155
|
-
async waitForReady() {
|
|
156
|
-
if (this.isReady) return true;
|
|
157
|
-
if (this.readyPromise) {
|
|
158
|
-
await this.readyPromise;
|
|
159
|
-
}
|
|
160
|
-
return this.isReady;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
init() {
|
|
165
|
-
const isHashMode = this.config.mode === 'hash';
|
|
166
|
-
|
|
167
|
-
// 이벤트 리스너 등록 (메모리 최적화)
|
|
168
|
-
window.addEventListener(
|
|
169
|
-
isHashMode ? 'hashchange' : 'popstate',
|
|
170
|
-
this._boundHandleRouteChange
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// DOM 로드 처리 통합
|
|
174
|
-
const initRoute = () => {
|
|
175
|
-
if (isHashMode && !window.location.hash) {
|
|
176
|
-
window.location.hash = '#/';
|
|
177
|
-
} else if (!isHashMode && window.location.pathname === '/') {
|
|
178
|
-
this.navigateTo('home');
|
|
179
|
-
} else {
|
|
180
|
-
this.handleRouteChange();
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
if (document.readyState === 'loading') {
|
|
185
|
-
document.addEventListener('DOMContentLoaded', initRoute);
|
|
186
|
-
} else {
|
|
187
|
-
// requestAnimationFrame으로 성능 개선
|
|
188
|
-
requestAnimationFrame(initRoute);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
handleRouteChange() {
|
|
193
|
-
const { route, queryParams } = this._parseCurrentLocation();
|
|
194
|
-
|
|
195
|
-
// Store current query parameters in QueryManager
|
|
196
|
-
this.queryManager?.setCurrentQueryParams(queryParams);
|
|
197
|
-
|
|
198
|
-
// 변경사항이 있을 때만 로드 (성능 최적화)
|
|
199
|
-
if (route !== this.currentHash || this.queryManager?.hasQueryParamsChanged(queryParams)) {
|
|
200
|
-
this.currentHash = route;
|
|
201
|
-
this.loadRoute(route);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* 현재 위치 파싱 (분리하여 가독성 향상)
|
|
207
|
-
*/
|
|
208
|
-
_parseCurrentLocation() {
|
|
209
|
-
if (this.config.mode === 'hash') {
|
|
210
|
-
const hashPath = window.location.hash.slice(1) || '/';
|
|
211
|
-
const [pathPart, queryPart] = hashPath.split('?');
|
|
212
|
-
|
|
213
|
-
// 경로 파싱 최적화
|
|
214
|
-
let route = 'home';
|
|
215
|
-
if (pathPart && pathPart !== '/') {
|
|
216
|
-
route = pathPart.startsWith('/') ? pathPart.slice(1) : pathPart;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
route: route || 'home',
|
|
221
|
-
queryParams: this.queryManager?.parseQueryString(queryPart || window.location.search.slice(1)) || {}
|
|
222
|
-
};
|
|
223
|
-
} else {
|
|
224
|
-
return {
|
|
225
|
-
route: window.location.pathname.slice(1) || 'home',
|
|
226
|
-
queryParams: this.queryManager?.parseQueryString(window.location.search.slice(1)) || {}
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async loadRoute(routeName) {
|
|
232
|
-
// 전환이 진행 중이면 무시
|
|
233
|
-
const inProgress = this.transitionInProgress;
|
|
234
|
-
|
|
235
|
-
if (inProgress) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
this.transitionInProgress = true;
|
|
241
|
-
|
|
242
|
-
// 인증 체크
|
|
243
|
-
const authResult = this.authManager ?
|
|
244
|
-
await this.authManager.checkAuthentication(routeName) :
|
|
245
|
-
{ allowed: true, reason: 'auth_disabled' };
|
|
246
|
-
if (!authResult.allowed) {
|
|
247
|
-
// 인증 실패 시 로그인 페이지로 리다이렉트
|
|
248
|
-
if (this.authManager) {
|
|
249
|
-
this.authManager.emitAuthEvent('auth_required', {
|
|
250
|
-
originalRoute: routeName,
|
|
251
|
-
loginRoute: this.config.loginRoute
|
|
252
|
-
});
|
|
253
|
-
const redirectUrl = routeName !== this.config.loginRoute ?
|
|
254
|
-
`${this.config.loginRoute}?redirect=${encodeURIComponent(routeName)}` :
|
|
255
|
-
this.config.loginRoute;
|
|
256
|
-
this.navigateTo(redirectUrl);
|
|
257
|
-
}
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const appElement = document.getElementById('app');
|
|
262
|
-
if (!appElement) {
|
|
263
|
-
throw new Error('App element not found');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Vue 컴포넌트 생성 (백그라운드에서)
|
|
267
|
-
const component = await this.routeLoader.createVueComponent(routeName);
|
|
268
|
-
|
|
269
|
-
// 새로운 페이지를 오버레이로 렌더링
|
|
270
|
-
await this.renderComponentWithTransition(component, routeName);
|
|
271
|
-
|
|
272
|
-
// 로딩 완료
|
|
273
|
-
|
|
274
|
-
} catch (error) {
|
|
275
|
-
this.log('error', `Route loading failed [${routeName}]:`, error.message);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// 에러 타입에 따른 처리
|
|
279
|
-
if (this.errorHandler) {
|
|
280
|
-
await this.errorHandler.handleRouteError(routeName, error);
|
|
281
|
-
} else {
|
|
282
|
-
console.error('[Router] No error handler available');
|
|
283
|
-
}
|
|
284
|
-
} finally {
|
|
285
|
-
// 모든 처리가 완료된 후 전환 상태 리셋
|
|
286
|
-
this.transitionInProgress = false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async renderComponentWithTransition(vueComponent, routeName) {
|
|
291
|
-
const appElement = document.getElementById('app');
|
|
292
|
-
if (!appElement) return;
|
|
293
|
-
|
|
294
|
-
// 새로운 페이지 컨테이너 생성
|
|
295
|
-
const newPageContainer = document.createElement('div');
|
|
296
|
-
newPageContainer.className = 'page-container page-entered';
|
|
297
|
-
newPageContainer.id = `page-${routeName}-${Date.now()}`;
|
|
298
|
-
|
|
299
|
-
// 기존 컨테이너가 있다면 즉시 숨기기
|
|
300
|
-
const existingContainers = appElement.querySelectorAll('.page-container');
|
|
301
|
-
existingContainers.forEach(container => {
|
|
302
|
-
container.classList.remove('page-entered');
|
|
303
|
-
container.classList.add('page-exiting');
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// 새 컨테이너를 앱에 추가
|
|
307
|
-
appElement.appendChild(newPageContainer);
|
|
308
|
-
|
|
309
|
-
// 개발 모드에서만 스타일 적용 (프로덕션 모드는 빌드된 JS에서 자동 처리)
|
|
310
|
-
if (this.config.environment === 'development' && vueComponent._style) {
|
|
311
|
-
this.applyStyle(vueComponent._style, routeName);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 새로운 Vue 앱을 새 컨테이너에 마운트
|
|
315
|
-
const { createApp } = Vue;
|
|
316
|
-
const newVueApp = createApp(vueComponent);
|
|
317
|
-
|
|
318
|
-
// Vue 3 전역 속성 설정
|
|
319
|
-
newVueApp.config.globalProperties.$router = {
|
|
320
|
-
navigateTo: (route, params) => this.navigateTo(route, params),
|
|
321
|
-
getCurrentRoute: () => this.getCurrentRoute(),
|
|
322
|
-
getQueryParams: () => this.queryManager?.getQueryParams() || {},
|
|
323
|
-
getQueryParam: (key) => this.queryManager?.getQueryParam(key),
|
|
324
|
-
setQueryParams: (params, replace) => this.queryManager?.setQueryParams(params, replace),
|
|
325
|
-
removeQueryParams: (keys) => this.queryManager?.removeQueryParams(keys),
|
|
326
|
-
currentRoute: this.currentHash,
|
|
327
|
-
currentQuery: this.queryManager?.getQueryParams() || {}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// 모바일 메뉴 전역 함수 추가
|
|
331
|
-
|
|
332
|
-
newVueApp.mount(`#${newPageContainer.id}`);
|
|
333
|
-
|
|
334
|
-
// requestAnimationFrame으로 성능 개선
|
|
335
|
-
requestAnimationFrame(() => {
|
|
336
|
-
this.cleanupPreviousPages();
|
|
337
|
-
this.transitionInProgress = false;
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// 이전 앱 정리 준비
|
|
341
|
-
if (this.currentVueApp) {
|
|
342
|
-
this.previousVueApp = this.currentVueApp;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
this.currentVueApp = newVueApp;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
cleanupPreviousPages() {
|
|
349
|
-
const appElement = document.getElementById('app');
|
|
350
|
-
if (!appElement) return;
|
|
351
|
-
|
|
352
|
-
// 배치 DOM 조작으로 성능 개선
|
|
353
|
-
const fragment = document.createDocumentFragment();
|
|
354
|
-
const exitingContainers = appElement.querySelectorAll('.page-container.page-exiting');
|
|
355
|
-
|
|
356
|
-
// 한번에 제거
|
|
357
|
-
exitingContainers.forEach(container => container.remove());
|
|
358
|
-
|
|
359
|
-
// 이전 Vue 앱 정리
|
|
360
|
-
if (this.previousVueApp) {
|
|
361
|
-
try {
|
|
362
|
-
this.previousVueApp.unmount();
|
|
363
|
-
} catch (error) {
|
|
364
|
-
// 무시 (이미 언마운트된 경우)
|
|
365
|
-
}
|
|
366
|
-
this.previousVueApp = null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// 로딩 엘리먼트 제거
|
|
370
|
-
|
|
371
|
-
appElement.querySelector('.loading')?.remove();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
applyStyle(css, routeName) {
|
|
375
|
-
// 기존 스타일 제거
|
|
376
|
-
const existing = document.querySelector(`style[data-route="${routeName}"]`);
|
|
377
|
-
if (existing) existing.remove();
|
|
378
|
-
|
|
379
|
-
if (css) {
|
|
380
|
-
const style = document.createElement('style');
|
|
381
|
-
style.textContent = css;
|
|
382
|
-
style.setAttribute('data-route', routeName);
|
|
383
|
-
document.head.appendChild(style);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
navigateTo(routeName, params = null) {
|
|
389
|
-
// If routeName is an object, treat it as {route, params}
|
|
390
|
-
if (typeof routeName === 'object') {
|
|
391
|
-
params = routeName.params || null;
|
|
392
|
-
routeName = routeName.route;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// Clear current query params if navigating to a different route
|
|
396
|
-
if (routeName !== this.currentHash && this.queryManager) {
|
|
397
|
-
this.queryManager.clearQueryParams();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Update URL with new route and params
|
|
401
|
-
this.updateURL(routeName, params);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
getCurrentRoute() {
|
|
405
|
-
return this.currentHash;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
updateURL(route, params = null) {
|
|
410
|
-
const queryParams = params || this.queryManager?.getQueryParams() || {};
|
|
411
|
-
const queryString = this.queryManager?.buildQueryString(queryParams) || '';
|
|
412
|
-
|
|
413
|
-
// URL 빌드 최적화
|
|
414
|
-
const buildURL = (route, queryString, isHash = true) => {
|
|
415
|
-
const base = route === 'home' ? '/' : `/${route}`;
|
|
416
|
-
const url = queryString ? `${base}?${queryString}` : base;
|
|
417
|
-
return isHash ? `#${url}` : url;
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
if (this.config.mode === 'hash') {
|
|
421
|
-
const newHash = buildURL(route, queryString);
|
|
422
|
-
|
|
423
|
-
// 동일한 URL이면 업데이트하지 않음 (성능 최적화)
|
|
424
|
-
if (window.location.hash !== newHash) {
|
|
425
|
-
window.location.hash = newHash;
|
|
426
|
-
}
|
|
427
|
-
} else {
|
|
428
|
-
const newPath = buildURL(route, queryString, false);
|
|
429
|
-
const isSameRoute = window.location.pathname === (route === 'home' ? '/' : `/${route}`);
|
|
430
|
-
|
|
431
|
-
if (isSameRoute) {
|
|
432
|
-
window.history.replaceState({}, '', newPath);
|
|
433
|
-
} else {
|
|
434
|
-
window.history.pushState({}, '', newPath);
|
|
435
|
-
}
|
|
436
|
-
this.handleRouteChange();
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* 라우터 정리 (메모리 누수 방지)
|
|
442
|
-
*/
|
|
443
|
-
destroy() {
|
|
444
|
-
// 이벤트 리스너 제거
|
|
445
|
-
window.removeEventListener(
|
|
446
|
-
this.config.mode === 'hash' ? 'hashchange' : 'popstate',
|
|
447
|
-
this._boundHandleRouteChange
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
// 현재 Vue 앱 언마운트
|
|
451
|
-
if (this.currentVueApp) {
|
|
452
|
-
this.currentVueApp.unmount();
|
|
453
|
-
this.currentVueApp = null;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// 이전 Vue 앱 언마운트
|
|
457
|
-
if (this.previousVueApp) {
|
|
458
|
-
this.previousVueApp.unmount();
|
|
459
|
-
this.previousVueApp = null;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// 매니저 정리
|
|
463
|
-
Object.values(this).forEach(manager => {
|
|
464
|
-
if (manager && typeof manager.destroy === 'function') {
|
|
465
|
-
manager.destroy();
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// 캐시 클리어
|
|
470
|
-
this.cacheManager?.clearAll();
|
|
471
|
-
|
|
472
|
-
// DOM 정리
|
|
473
|
-
const appElement = document.getElementById('app');
|
|
474
|
-
if (appElement) {
|
|
475
|
-
appElement.innerHTML = '';
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
this.log('info', 'Router destroyed');
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
// 전역 라우터는 index.html에서 환경설정과 함께 생성됨
|