slicejs-web-framework 1.0.26 → 1.0.30
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/Slice/Components/Structural/Router/EventThrottler.js +110 -0
- package/Slice/Components/Structural/Router/RouteCache.js +245 -0
- package/Slice/Components/Structural/Router/RouteMatcher.js +236 -0
- package/Slice/Components/Structural/Router/RouteRenderer.js +324 -0
- package/Slice/Components/Structural/Router/Router.js +317 -238
- package/package.json +1 -1
|
@@ -1,301 +1,380 @@
|
|
|
1
|
+
// Slice/Components/Structural/Router/Router.js
|
|
2
|
+
|
|
3
|
+
import EventThrottler from './EventThrottler.js';
|
|
4
|
+
import RouteCache from './RouteCache.js';
|
|
5
|
+
import RouteMatcher from './RouteMatcher.js';
|
|
6
|
+
import RouteRenderer from './RouteRenderer.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Router optimizado con separación de responsabilidades
|
|
10
|
+
* Mejoras significativas en performance y mantenibilidad
|
|
11
|
+
*/
|
|
1
12
|
export default class Router {
|
|
2
13
|
constructor(routes) {
|
|
3
14
|
this.routes = routes;
|
|
4
15
|
this.activeRoute = null;
|
|
5
|
-
this.pathToRouteMap = this.createPathToRouteMap(routes);
|
|
6
16
|
|
|
7
|
-
//
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
17
|
+
// Inicializar sistemas especializados
|
|
18
|
+
this.eventThrottler = new EventThrottler();
|
|
19
|
+
this.routeCache = new RouteCache();
|
|
20
|
+
this.routeMatcher = new RouteMatcher(routes);
|
|
21
|
+
this.routeRenderer = new RouteRenderer(this.routeCache);
|
|
11
22
|
|
|
12
|
-
//
|
|
13
|
-
this.
|
|
23
|
+
// Observer para cambios DOM
|
|
24
|
+
this.mutationObserver = null;
|
|
25
|
+
|
|
26
|
+
// Estado del router
|
|
27
|
+
this.isInitialized = false;
|
|
28
|
+
this.isNavigating = false;
|
|
14
29
|
}
|
|
15
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Inicializar router con observadores optimizados
|
|
33
|
+
*/
|
|
16
34
|
async init() {
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
if (this.isInitialized) {
|
|
36
|
+
slice.logger.logWarning('Router', 'Router already initialized');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Configurar observador de mutaciones optimizado
|
|
42
|
+
this.setupMutationObserver();
|
|
43
|
+
|
|
44
|
+
// Cargar ruta inicial
|
|
45
|
+
await this.loadInitialRoute();
|
|
46
|
+
|
|
47
|
+
// Configurar listeners de navegación
|
|
48
|
+
this.setupNavigationListeners();
|
|
49
|
+
|
|
50
|
+
this.isInitialized = true;
|
|
51
|
+
slice.logger.logInfo('Router', 'Router initialized successfully');
|
|
52
|
+
|
|
53
|
+
} catch (error) {
|
|
54
|
+
slice.logger.logError('Router', 'Error initializing router', error);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
19
57
|
}
|
|
20
58
|
|
|
21
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Configurar observador de mutaciones optimizado
|
|
61
|
+
*/
|
|
22
62
|
setupMutationObserver() {
|
|
23
|
-
if (typeof MutationObserver
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this.observer.observe(document.body, {
|
|
52
|
-
childList: true,
|
|
53
|
-
subtree: true
|
|
63
|
+
if (typeof MutationObserver === 'undefined') {
|
|
64
|
+
slice.logger.logWarning('Router', 'MutationObserver not available');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.mutationObserver = new MutationObserver((mutations) => {
|
|
69
|
+
// Usar throttling para evitar múltiples invalidaciones
|
|
70
|
+
this.eventThrottler.throttle('cache-invalidation', () => {
|
|
71
|
+
this.routeCache.invalidateByMutation(mutations);
|
|
72
|
+
}, 50);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.mutationObserver.observe(document.body, {
|
|
76
|
+
childList: true,
|
|
77
|
+
subtree: true,
|
|
78
|
+
attributeFilter: ['slice-route', 'slice-multi-route']
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Configurar listeners de navegación
|
|
84
|
+
*/
|
|
85
|
+
setupNavigationListeners() {
|
|
86
|
+
// Listener para popstate (back/forward)
|
|
87
|
+
window.addEventListener('popstate', (event) => {
|
|
88
|
+
this.eventThrottler.throttle('popstate', () => {
|
|
89
|
+
return this.onRouteChange();
|
|
54
90
|
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Interceptación automática de enlaces (activada por defecto)
|
|
94
|
+
// Para desactivar: agregar disableAutoInterceptLinks: true en la configuración
|
|
95
|
+
if (!this.routes.disableAutoInterceptLinks) {
|
|
96
|
+
this.setupLinkInterception();
|
|
97
|
+
slice.logger.logInfo('Router', 'Auto link interception enabled');
|
|
55
98
|
}
|
|
56
99
|
}
|
|
57
100
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Configurar interceptación de enlaces
|
|
103
|
+
* Convierte todos los <a href="/path"> en slice.router.navigate()
|
|
104
|
+
*/
|
|
105
|
+
setupLinkInterception() {
|
|
106
|
+
document.addEventListener('click', (event) => {
|
|
107
|
+
const link = event.target.closest('a[href]');
|
|
108
|
+
if (link && this.shouldInterceptLink(link)) {
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
|
|
111
|
+
const href = link.getAttribute('href');
|
|
112
|
+
slice.logger.logInfo('Router', `Intercepting link: ${href}`);
|
|
113
|
+
|
|
114
|
+
this.navigate(href);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
62
117
|
}
|
|
63
118
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Verificar si debe interceptar el enlace
|
|
121
|
+
*/
|
|
122
|
+
shouldInterceptLink(link) {
|
|
123
|
+
const href = link.getAttribute('href');
|
|
124
|
+
|
|
125
|
+
// No interceptar si no hay href
|
|
126
|
+
if (!href) return false;
|
|
127
|
+
|
|
128
|
+
// No interceptar enlaces externos (diferentes dominio)
|
|
129
|
+
if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
130
|
+
const linkUrl = new URL(href, window.location.origin);
|
|
131
|
+
if (linkUrl.origin !== window.location.origin) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// No interceptar protocolos especiales
|
|
137
|
+
if (href.startsWith('mailto:') ||
|
|
138
|
+
href.startsWith('tel:') ||
|
|
139
|
+
href.startsWith('sms:') ||
|
|
140
|
+
href.startsWith('ftp:')) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// No interceptar anchors (#hash)
|
|
145
|
+
if (href.startsWith('#')) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// No interceptar si tiene atributos especiales
|
|
150
|
+
if (link.hasAttribute('download') ||
|
|
151
|
+
link.target === '_blank' ||
|
|
152
|
+
link.target === '_top' ||
|
|
153
|
+
link.target === '_parent' ||
|
|
154
|
+
link.hasAttribute('data-no-intercept') ||
|
|
155
|
+
link.hasAttribute('data-external')) {
|
|
156
|
+
return false;
|
|
90
157
|
}
|
|
91
|
-
|
|
92
|
-
|
|
158
|
+
|
|
159
|
+
// No interceptar si está marcado como externo
|
|
160
|
+
if (link.classList.contains('external-link') ||
|
|
161
|
+
link.classList.contains('no-intercept')) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return true;
|
|
93
166
|
}
|
|
94
167
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Manejar cambio de ruta con throttling optimizado
|
|
170
|
+
*/
|
|
171
|
+
async onRouteChange() {
|
|
172
|
+
if (this.isNavigating) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
99
175
|
|
|
100
|
-
|
|
176
|
+
return this.eventThrottler.throttle('route-change', async () => {
|
|
177
|
+
this.isNavigating = true;
|
|
178
|
+
|
|
101
179
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
180
|
+
const path = window.location.pathname;
|
|
181
|
+
|
|
182
|
+
// Intentar renderizar rutas en componentes existentes primero
|
|
183
|
+
const routeContainersFlag = await this.routeRenderer.renderRoutesComponentsInPage();
|
|
184
|
+
|
|
185
|
+
if (routeContainersFlag) {
|
|
186
|
+
return;
|
|
106
187
|
}
|
|
107
188
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
189
|
+
// Si no hay contenedores de rutas, hacer matching tradicional
|
|
190
|
+
const { route, params } = this.routeMatcher.matchRoute(path);
|
|
191
|
+
if (route) {
|
|
192
|
+
await this.routeRenderer.handleRoute(route, params);
|
|
112
193
|
}
|
|
194
|
+
|
|
113
195
|
} catch (error) {
|
|
114
|
-
slice.logger.logError('Router',
|
|
196
|
+
slice.logger.logError('Router', 'Error during route change', error);
|
|
197
|
+
} finally {
|
|
198
|
+
this.isNavigating = false;
|
|
115
199
|
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return routerContainersFlag;
|
|
200
|
+
}, 10);
|
|
119
201
|
}
|
|
120
202
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (this.routeContainersCache.has(containerKey) &&
|
|
128
|
-
(now - this.lastCacheUpdate) < this.CACHE_DURATION) {
|
|
129
|
-
return this.routeContainersCache.get(containerKey);
|
|
203
|
+
/**
|
|
204
|
+
* Navegar a una ruta específica
|
|
205
|
+
*/
|
|
206
|
+
async navigate(path, options = {}) {
|
|
207
|
+
if (!path || path === window.location.pathname) {
|
|
208
|
+
return;
|
|
130
209
|
}
|
|
131
210
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// OPTIMIZADO: Búsqueda más eficiente usando TreeWalker
|
|
141
|
-
findAllRouteContainersOptimized(container) {
|
|
142
|
-
const routeContainers = [];
|
|
143
|
-
|
|
144
|
-
// Usar TreeWalker para una búsqueda más eficiente
|
|
145
|
-
const walker = document.createTreeWalker(
|
|
146
|
-
container,
|
|
147
|
-
NodeFilter.SHOW_ELEMENT,
|
|
148
|
-
{
|
|
149
|
-
acceptNode: (node) => {
|
|
150
|
-
// Solo aceptar nodos que sean slice-route o slice-multi-route
|
|
151
|
-
if (node.tagName === 'SLICE-ROUTE' || node.tagName === 'SLICE-MULTI-ROUTE') {
|
|
152
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
153
|
-
}
|
|
154
|
-
return NodeFilter.FILTER_SKIP;
|
|
155
|
-
}
|
|
211
|
+
try {
|
|
212
|
+
const { replace = false, state = {} } = options;
|
|
213
|
+
|
|
214
|
+
// Actualizar historia del navegador
|
|
215
|
+
if (replace) {
|
|
216
|
+
window.history.replaceState(state, '', window.location.origin + path);
|
|
217
|
+
} else {
|
|
218
|
+
window.history.pushState(state, '', window.location.origin + path);
|
|
156
219
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
220
|
+
|
|
221
|
+
// Ejecutar cambio de ruta
|
|
222
|
+
await this.onRouteChange();
|
|
223
|
+
|
|
224
|
+
} catch (error) {
|
|
225
|
+
slice.logger.logError('Router', `Error navigating to ${path}`, error);
|
|
162
226
|
}
|
|
163
|
-
|
|
164
|
-
return routeContainers;
|
|
165
227
|
}
|
|
166
228
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Navegar hacia atrás
|
|
231
|
+
*/
|
|
232
|
+
back() {
|
|
233
|
+
window.history.back();
|
|
234
|
+
}
|
|
173
235
|
|
|
174
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Navegar hacia adelante
|
|
238
|
+
*/
|
|
239
|
+
forward() {
|
|
240
|
+
window.history.forward();
|
|
175
241
|
}
|
|
176
242
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Cargar ruta inicial
|
|
245
|
+
*/
|
|
246
|
+
async loadInitialRoute() {
|
|
247
|
+
const path = window.location.pathname;
|
|
248
|
+
const { route, params } = this.routeMatcher.matchRoute(path);
|
|
249
|
+
|
|
250
|
+
if (route) {
|
|
251
|
+
await this.routeRenderer.handleRoute(route, params);
|
|
252
|
+
} else {
|
|
253
|
+
slice.logger.logWarning('Router', `No route found for initial path: ${path}`);
|
|
182
254
|
}
|
|
255
|
+
}
|
|
183
256
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Métodos de conveniencia para acceso a subsistemas
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
// Acceso al matcher
|
|
262
|
+
matchRoute(path) {
|
|
263
|
+
return this.routeMatcher.matchRoute(path);
|
|
264
|
+
}
|
|
188
265
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
266
|
+
hasRoute(path) {
|
|
267
|
+
return this.routeMatcher.hasRoute(path);
|
|
268
|
+
}
|
|
192
269
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await this.handleRoute(route, params);
|
|
196
|
-
}
|
|
197
|
-
}, 10);
|
|
270
|
+
generateUrl(routePath, params) {
|
|
271
|
+
return this.routeMatcher.generateUrl(routePath, params);
|
|
198
272
|
}
|
|
199
273
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
274
|
+
// Acceso al caché
|
|
275
|
+
invalidateCache() {
|
|
276
|
+
this.routeCache.invalidateAll();
|
|
203
277
|
}
|
|
204
278
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const componentName = route.parentRoute ? route.parentRoute.component : route.component;
|
|
209
|
-
const sliceId = `route-${componentName}`;
|
|
210
|
-
|
|
211
|
-
const existingComponent = slice.controller.getComponent(sliceId);
|
|
279
|
+
getCacheStats() {
|
|
280
|
+
return this.routeCache.getStats();
|
|
281
|
+
}
|
|
212
282
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
283
|
+
// Acceso al renderer
|
|
284
|
+
async renderRoutesInComponent(component) {
|
|
285
|
+
return this.routeRenderer.renderRoutesInComponent(component);
|
|
286
|
+
}
|
|
216
287
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
existingComponent.props = { ...existingComponent.props, ...params };
|
|
221
|
-
await existingComponent.update();
|
|
222
|
-
}
|
|
223
|
-
targetElement.appendChild(existingComponent);
|
|
224
|
-
} else {
|
|
225
|
-
const component = await slice.build(componentName, {
|
|
226
|
-
params,
|
|
227
|
-
sliceId: sliceId,
|
|
228
|
-
});
|
|
229
|
-
targetElement.innerHTML = '';
|
|
230
|
-
targetElement.appendChild(component);
|
|
231
|
-
}
|
|
288
|
+
getRendererStats() {
|
|
289
|
+
return this.routeRenderer.getStats();
|
|
290
|
+
}
|
|
232
291
|
|
|
233
|
-
|
|
292
|
+
/**
|
|
293
|
+
* Actualizar rutas dinámicamente
|
|
294
|
+
*/
|
|
295
|
+
updateRoutes(newRoutes) {
|
|
296
|
+
this.routes = newRoutes;
|
|
297
|
+
this.routeMatcher.updateRoutes(newRoutes);
|
|
234
298
|
this.invalidateCache();
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (slice.loading) {
|
|
238
|
-
slice.loading.stop();
|
|
239
|
-
}
|
|
299
|
+
}
|
|
240
300
|
|
|
241
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Añadir ruta individual
|
|
303
|
+
*/
|
|
304
|
+
addRoute(route, basePath = '') {
|
|
305
|
+
this.routeMatcher.addRoute(route, basePath);
|
|
306
|
+
this.invalidateCache();
|
|
242
307
|
}
|
|
243
308
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Remover ruta
|
|
311
|
+
*/
|
|
312
|
+
removeRoute(path) {
|
|
313
|
+
this.routeMatcher.removeRoute(path);
|
|
314
|
+
this.invalidateCache();
|
|
315
|
+
}
|
|
247
316
|
|
|
248
|
-
|
|
249
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Obtener todas las rutas
|
|
319
|
+
*/
|
|
320
|
+
getAllRoutes() {
|
|
321
|
+
return this.routeMatcher.getAllRoutes();
|
|
250
322
|
}
|
|
251
323
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
for (const [routePattern, route] of this.pathToRouteMap.entries()) {
|
|
266
|
-
if (routePattern.includes('${')) {
|
|
267
|
-
const { regex, paramNames } = this.compilePathPattern(routePattern);
|
|
268
|
-
const match = path.match(regex);
|
|
269
|
-
if (match) {
|
|
270
|
-
const params = {};
|
|
271
|
-
paramNames.forEach((name, i) => {
|
|
272
|
-
params[name] = match[i + 1];
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
if (route.parentRoute) {
|
|
276
|
-
return {
|
|
277
|
-
route: route.parentRoute,
|
|
278
|
-
params: params,
|
|
279
|
-
childRoute: route
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return { route, params };
|
|
284
|
-
}
|
|
324
|
+
/**
|
|
325
|
+
* Obtener estadísticas completas del router
|
|
326
|
+
*/
|
|
327
|
+
getStats() {
|
|
328
|
+
return {
|
|
329
|
+
isInitialized: this.isInitialized,
|
|
330
|
+
isNavigating: this.isNavigating,
|
|
331
|
+
activeRoute: this.activeRoute,
|
|
332
|
+
matcher: this.routeMatcher.getStats(),
|
|
333
|
+
cache: this.routeCache.getStats(),
|
|
334
|
+
renderer: this.routeRenderer.getStats(),
|
|
335
|
+
eventThrottler: {
|
|
336
|
+
pendingEvents: this.eventThrottler.timeouts.size
|
|
285
337
|
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const notFoundRoute = this.pathToRouteMap.get('/404');
|
|
289
|
-
return { route: notFoundRoute, params: {} };
|
|
338
|
+
};
|
|
290
339
|
}
|
|
291
340
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
341
|
+
/**
|
|
342
|
+
* Destruir router y cleanup
|
|
343
|
+
*/
|
|
344
|
+
destroy() {
|
|
345
|
+
// Detener observadores
|
|
346
|
+
if (this.mutationObserver) {
|
|
347
|
+
this.mutationObserver.disconnect();
|
|
348
|
+
this.mutationObserver = null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Cancelar eventos pendientes
|
|
352
|
+
this.eventThrottler.destroy();
|
|
353
|
+
|
|
354
|
+
// Limpiar subsistemas
|
|
355
|
+
this.routeCache.destroy();
|
|
356
|
+
this.routeRenderer.destroy();
|
|
298
357
|
|
|
299
|
-
|
|
358
|
+
// Remover listeners
|
|
359
|
+
window.removeEventListener('popstate', this.onRouteChange);
|
|
360
|
+
|
|
361
|
+
this.isInitialized = false;
|
|
362
|
+
|
|
363
|
+
slice.logger.logInfo('Router', 'Router destroyed successfully');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Reinicializar router (útil para testing)
|
|
368
|
+
*/
|
|
369
|
+
async reinitialize(newRoutes = null) {
|
|
370
|
+
this.destroy();
|
|
371
|
+
|
|
372
|
+
if (newRoutes) {
|
|
373
|
+
this.routes = newRoutes;
|
|
374
|
+
this.routeMatcher = new RouteMatcher(newRoutes);
|
|
375
|
+
this.routeRenderer = new RouteRenderer(this.routeCache);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
await this.init();
|
|
300
379
|
}
|
|
301
380
|
}
|