slicejs-web-framework 1.0.33 → 2.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/Slice/Components/Structural/Router/Router.js +240 -315
- package/api/index.js +98 -46
- package/package.json +1 -1
- package/Slice/Components/Structural/Router/EventThrottler.js +0 -110
- package/Slice/Components/Structural/Router/RouteCache.js +0 -245
- package/Slice/Components/Structural/Router/RouteMatcher.js +0 -236
- package/Slice/Components/Structural/Router/RouteRenderer.js +0 -324
|
@@ -1,380 +1,305 @@
|
|
|
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
|
-
*/
|
|
12
1
|
export default class Router {
|
|
13
2
|
constructor(routes) {
|
|
14
3
|
this.routes = routes;
|
|
15
4
|
this.activeRoute = null;
|
|
5
|
+
this.pathToRouteMap = this.createPathToRouteMap(routes);
|
|
16
6
|
|
|
17
|
-
//
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.routeRenderer = new RouteRenderer(this.routeCache);
|
|
7
|
+
// NUEVO: Sistema de caché optimizado
|
|
8
|
+
this.routeContainersCache = new Map();
|
|
9
|
+
this.lastCacheUpdate = 0;
|
|
10
|
+
this.CACHE_DURATION = 100; // ms - caché muy corto pero efectivo
|
|
22
11
|
|
|
23
|
-
// Observer para
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
// Estado del router
|
|
27
|
-
this.isInitialized = false;
|
|
28
|
-
this.isNavigating = false;
|
|
12
|
+
// NUEVO: Observer para invalidar caché automáticamente
|
|
13
|
+
this.setupMutationObserver();
|
|
29
14
|
}
|
|
30
15
|
|
|
31
|
-
/**
|
|
32
|
-
* Inicializar router con observadores optimizados
|
|
33
|
-
*/
|
|
34
16
|
async init() {
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
}
|
|
17
|
+
await this.loadInitialRoute();
|
|
18
|
+
window.addEventListener('popstate', this.onRouteChange.bind(this));
|
|
57
19
|
}
|
|
58
20
|
|
|
59
|
-
|
|
60
|
-
* Configurar observador de mutaciones optimizado
|
|
61
|
-
*/
|
|
21
|
+
// NUEVO: Observer para detectar cambios en el DOM
|
|
62
22
|
setupMutationObserver() {
|
|
63
|
-
if (typeof MutationObserver
|
|
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
|
-
|
|
23
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
24
|
+
this.observer = new MutationObserver((mutations) => {
|
|
25
|
+
let shouldInvalidateCache = false;
|
|
26
|
+
|
|
27
|
+
mutations.forEach((mutation) => {
|
|
28
|
+
if (mutation.type === 'childList') {
|
|
29
|
+
// Solo invalidar si se añadieron/removieron nodos que podrían ser rutas
|
|
30
|
+
const addedNodes = Array.from(mutation.addedNodes);
|
|
31
|
+
const removedNodes = Array.from(mutation.removedNodes);
|
|
32
|
+
|
|
33
|
+
const hasRouteNodes = [...addedNodes, ...removedNodes].some(node =>
|
|
34
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
35
|
+
(node.tagName === 'SLICE-ROUTE' ||
|
|
36
|
+
node.tagName === 'SLICE-MULTI-ROUTE' ||
|
|
37
|
+
node.querySelector?.('slice-route, slice-multi-route'))
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (hasRouteNodes) {
|
|
41
|
+
shouldInvalidateCache = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (shouldInvalidateCache) {
|
|
47
|
+
this.invalidateCache();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.observer.observe(document.body, {
|
|
52
|
+
childList: true,
|
|
53
|
+
subtree: true
|
|
90
54
|
});
|
|
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');
|
|
98
55
|
}
|
|
99
56
|
}
|
|
100
57
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
});
|
|
58
|
+
// NUEVO: Invalidar caché
|
|
59
|
+
invalidateCache() {
|
|
60
|
+
this.routeContainersCache.clear();
|
|
61
|
+
this.lastCacheUpdate = 0;
|
|
117
62
|
}
|
|
118
63
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
64
|
+
createPathToRouteMap(routes, basePath = '', parentRoute = null) {
|
|
65
|
+
const pathToRouteMap = new Map();
|
|
66
|
+
|
|
67
|
+
for (const route of routes) {
|
|
68
|
+
const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
|
|
69
|
+
|
|
70
|
+
const routeWithParent = {
|
|
71
|
+
...route,
|
|
72
|
+
fullPath,
|
|
73
|
+
parentPath: parentRoute ? parentRoute.fullPath : null,
|
|
74
|
+
parentRoute: parentRoute
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
pathToRouteMap.set(fullPath, routeWithParent);
|
|
78
|
+
|
|
79
|
+
if (route.children) {
|
|
80
|
+
const childPathToRouteMap = this.createPathToRouteMap(
|
|
81
|
+
route.children,
|
|
82
|
+
fullPath,
|
|
83
|
+
routeWithParent
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
|
|
87
|
+
pathToRouteMap.set(childPath, childRoute);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
142
90
|
}
|
|
143
|
-
|
|
144
|
-
|
|
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;
|
|
157
|
-
}
|
|
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;
|
|
91
|
+
|
|
92
|
+
return pathToRouteMap;
|
|
166
93
|
}
|
|
167
94
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (this.isNavigating) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
95
|
+
// OPTIMIZADO: Sistema de caché inteligente
|
|
96
|
+
async renderRoutesComponentsInPage(searchContainer = document) {
|
|
97
|
+
let routerContainersFlag = false;
|
|
98
|
+
const routeContainers = this.getCachedRouteContainers(searchContainer);
|
|
175
99
|
|
|
176
|
-
|
|
177
|
-
this.isNavigating = true;
|
|
178
|
-
|
|
100
|
+
for (const routeContainer of routeContainers) {
|
|
179
101
|
try {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (routeContainersFlag) {
|
|
186
|
-
return;
|
|
102
|
+
// Verificar que el componente aún esté conectado al DOM
|
|
103
|
+
if (!routeContainer.isConnected) {
|
|
104
|
+
this.invalidateCache();
|
|
105
|
+
continue;
|
|
187
106
|
}
|
|
188
107
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
108
|
+
let response = await routeContainer.renderIfCurrentRoute();
|
|
109
|
+
if (response) {
|
|
110
|
+
this.activeRoute = routeContainer.props;
|
|
111
|
+
routerContainersFlag = true;
|
|
193
112
|
}
|
|
194
|
-
|
|
195
113
|
} catch (error) {
|
|
196
|
-
slice.logger.logError('Router',
|
|
197
|
-
} finally {
|
|
198
|
-
this.isNavigating = false;
|
|
114
|
+
slice.logger.logError('Router', `Error rendering route container`, error);
|
|
199
115
|
}
|
|
200
|
-
}, 10);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Navegar a una ruta específica
|
|
205
|
-
*/
|
|
206
|
-
async navigate(path, options = {}) {
|
|
207
|
-
if (!path || path === window.location.pathname) {
|
|
208
|
-
return;
|
|
209
116
|
}
|
|
210
117
|
|
|
211
|
-
|
|
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);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Ejecutar cambio de ruta
|
|
222
|
-
await this.onRouteChange();
|
|
223
|
-
|
|
224
|
-
} catch (error) {
|
|
225
|
-
slice.logger.logError('Router', `Error navigating to ${path}`, error);
|
|
226
|
-
}
|
|
118
|
+
return routerContainersFlag;
|
|
227
119
|
}
|
|
228
120
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
121
|
+
// NUEVO: Obtener contenedores con caché
|
|
122
|
+
getCachedRouteContainers(container) {
|
|
123
|
+
const containerKey = container === document ? 'document' : container.sliceId || 'anonymous';
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
|
|
126
|
+
// Verificar si el caché es válido
|
|
127
|
+
if (this.routeContainersCache.has(containerKey) &&
|
|
128
|
+
(now - this.lastCacheUpdate) < this.CACHE_DURATION) {
|
|
129
|
+
return this.routeContainersCache.get(containerKey);
|
|
130
|
+
}
|
|
235
131
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
132
|
+
// Regenerar caché
|
|
133
|
+
const routeContainers = this.findAllRouteContainersOptimized(container);
|
|
134
|
+
this.routeContainersCache.set(containerKey, routeContainers);
|
|
135
|
+
this.lastCacheUpdate = now;
|
|
136
|
+
|
|
137
|
+
return routeContainers;
|
|
241
138
|
}
|
|
242
139
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
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
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
249
158
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
slice.logger.logWarning('Router', `No route found for initial path: ${path}`);
|
|
159
|
+
let node;
|
|
160
|
+
while (node = walker.nextNode()) {
|
|
161
|
+
routeContainers.push(node);
|
|
254
162
|
}
|
|
255
|
-
}
|
|
256
163
|
|
|
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);
|
|
164
|
+
return routeContainers;
|
|
264
165
|
}
|
|
265
166
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
167
|
+
// NUEVO: Método específico para renderizar rutas dentro de un componente
|
|
168
|
+
async renderRoutesInComponent(component) {
|
|
169
|
+
if (!component) {
|
|
170
|
+
slice.logger.logWarning('Router', 'No component provided for route rendering');
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
269
173
|
|
|
270
|
-
|
|
271
|
-
return this.routeMatcher.generateUrl(routePath, params);
|
|
174
|
+
return await this.renderRoutesComponentsInPage(component);
|
|
272
175
|
}
|
|
273
176
|
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
177
|
+
// OPTIMIZADO: Debouncing para evitar múltiples llamadas seguidas
|
|
178
|
+
async onRouteChange() {
|
|
179
|
+
// Cancelar el timeout anterior si existe
|
|
180
|
+
if (this.routeChangeTimeout) {
|
|
181
|
+
clearTimeout(this.routeChangeTimeout);
|
|
182
|
+
}
|
|
278
183
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
184
|
+
// Debounce de 10ms para evitar múltiples llamadas seguidas
|
|
185
|
+
this.routeChangeTimeout = setTimeout(async () => {
|
|
186
|
+
const path = window.location.pathname;
|
|
187
|
+
const routeContainersFlag = await this.renderRoutesComponentsInPage();
|
|
282
188
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
189
|
+
if (routeContainersFlag) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
287
192
|
|
|
288
|
-
|
|
289
|
-
|
|
193
|
+
const { route, params } = this.matchRoute(path);
|
|
194
|
+
if (route) {
|
|
195
|
+
await this.handleRoute(route, params);
|
|
196
|
+
}
|
|
197
|
+
}, 10);
|
|
290
198
|
}
|
|
291
199
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
updateRoutes(newRoutes) {
|
|
296
|
-
this.routes = newRoutes;
|
|
297
|
-
this.routeMatcher.updateRoutes(newRoutes);
|
|
298
|
-
this.invalidateCache();
|
|
200
|
+
async navigate(path) {
|
|
201
|
+
window.history.pushState({}, path, window.location.origin + path);
|
|
202
|
+
await this.onRouteChange();
|
|
299
203
|
}
|
|
300
204
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
205
|
+
async handleRoute(route, params) {
|
|
206
|
+
const targetElement = document.querySelector('#app');
|
|
207
|
+
|
|
208
|
+
const componentName = route.parentRoute ? route.parentRoute.component : route.component;
|
|
209
|
+
const sliceId = `route-${componentName}`;
|
|
210
|
+
|
|
211
|
+
const existingComponent = slice.controller.getComponent(sliceId);
|
|
308
212
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
removeRoute(path) {
|
|
313
|
-
this.routeMatcher.removeRoute(path);
|
|
314
|
-
this.invalidateCache();
|
|
213
|
+
if (slice.loading) {
|
|
214
|
+
slice.loading.start();
|
|
315
215
|
}
|
|
316
216
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
217
|
+
if (existingComponent) {
|
|
218
|
+
targetElement.innerHTML = '';
|
|
219
|
+
if (existingComponent.update) {
|
|
220
|
+
existingComponent.props = { ...existingComponent.props, ...params };
|
|
221
|
+
await existingComponent.update();
|
|
222
|
+
}
|
|
223
|
+
targetElement.appendChild(existingComponent);
|
|
224
|
+
// Renderizar DESPUÉS de insertar (pero antes de mostrar)
|
|
225
|
+
await this.renderRoutesInComponent(existingComponent);
|
|
226
|
+
} else {
|
|
227
|
+
const component = await slice.build(componentName, {
|
|
228
|
+
params,
|
|
229
|
+
sliceId: sliceId,
|
|
230
|
+
});
|
|
323
231
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
337
|
-
}
|
|
338
|
-
};
|
|
232
|
+
targetElement.innerHTML = '';
|
|
233
|
+
targetElement.appendChild(component);
|
|
234
|
+
|
|
235
|
+
// Renderizar INMEDIATAMENTE después de insertar
|
|
236
|
+
await this.renderRoutesInComponent(component);
|
|
339
237
|
}
|
|
340
238
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
*/
|
|
344
|
-
destroy() {
|
|
345
|
-
// Detener observadores
|
|
346
|
-
if (this.mutationObserver) {
|
|
347
|
-
this.mutationObserver.disconnect();
|
|
348
|
-
this.mutationObserver = null;
|
|
349
|
-
}
|
|
239
|
+
// Invalidar caché después de cambios importantes en el DOM
|
|
240
|
+
this.invalidateCache();
|
|
350
241
|
|
|
351
|
-
|
|
352
|
-
|
|
242
|
+
if (slice.loading) {
|
|
243
|
+
slice.loading.stop();
|
|
244
|
+
}
|
|
353
245
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.routeRenderer.destroy();
|
|
246
|
+
slice.router.activeRoute = route;
|
|
247
|
+
}
|
|
357
248
|
|
|
358
|
-
|
|
359
|
-
window.
|
|
249
|
+
async loadInitialRoute() {
|
|
250
|
+
const path = window.location.pathname;
|
|
251
|
+
const { route, params } = this.matchRoute(path);
|
|
360
252
|
|
|
361
|
-
this.
|
|
362
|
-
|
|
363
|
-
slice.logger.logInfo('Router', 'Router destroyed successfully');
|
|
253
|
+
await this.handleRoute(route, params);
|
|
364
254
|
}
|
|
365
255
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
256
|
+
matchRoute(path) {
|
|
257
|
+
const exactMatch = this.pathToRouteMap.get(path);
|
|
258
|
+
if (exactMatch) {
|
|
259
|
+
if (exactMatch.parentRoute) {
|
|
260
|
+
return {
|
|
261
|
+
route: exactMatch.parentRoute,
|
|
262
|
+
params: {},
|
|
263
|
+
childRoute: exactMatch
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return { route: exactMatch, params: {} };
|
|
376
267
|
}
|
|
377
|
-
|
|
378
|
-
|
|
268
|
+
|
|
269
|
+
for (const [routePattern, route] of this.pathToRouteMap.entries()) {
|
|
270
|
+
if (routePattern.includes('${')) {
|
|
271
|
+
const { regex, paramNames } = this.compilePathPattern(routePattern);
|
|
272
|
+
const match = path.match(regex);
|
|
273
|
+
if (match) {
|
|
274
|
+
const params = {};
|
|
275
|
+
paramNames.forEach((name, i) => {
|
|
276
|
+
params[name] = match[i + 1];
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (route.parentRoute) {
|
|
280
|
+
return {
|
|
281
|
+
route: route.parentRoute,
|
|
282
|
+
params: params,
|
|
283
|
+
childRoute: route
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { route, params };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const notFoundRoute = this.pathToRouteMap.get('/404');
|
|
293
|
+
return { route: notFoundRoute, params: {} };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
compilePathPattern(pattern) {
|
|
297
|
+
const paramNames = [];
|
|
298
|
+
const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
|
|
299
|
+
paramNames.push(paramName);
|
|
300
|
+
return '([^/]+)';
|
|
301
|
+
}) + '$';
|
|
302
|
+
|
|
303
|
+
return { regex: new RegExp(regexPattern), paramNames };
|
|
379
304
|
}
|
|
380
305
|
}
|