slicejs-web-framework 1.0.28 → 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 +315 -240
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Slice/Components/Structural/Router/EventThrottler.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de throttling optimizado para eventos del router
|
|
5
|
+
* Mejora significativa sobre el setTimeout básico actual
|
|
6
|
+
*/
|
|
7
|
+
export default class EventThrottler {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.timeouts = new Map();
|
|
10
|
+
this.executingCallbacks = new Set();
|
|
11
|
+
this.defaultDelay = 10;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Throttle con memoria de callbacks en ejecución
|
|
16
|
+
* Evita race conditions y callbacks duplicados
|
|
17
|
+
*/
|
|
18
|
+
throttle(key, callback, delay = this.defaultDelay) {
|
|
19
|
+
// Si ya se está ejecutando este callback, ignorar
|
|
20
|
+
if (this.executingCallbacks.has(key)) {
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Cancelar timeout anterior si existe
|
|
25
|
+
if (this.timeouts.has(key)) {
|
|
26
|
+
clearTimeout(this.timeouts.get(key));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const timeoutId = setTimeout(async () => {
|
|
31
|
+
this.executingCallbacks.add(key);
|
|
32
|
+
this.timeouts.delete(key);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await callback();
|
|
36
|
+
resolve(result);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
reject(error);
|
|
39
|
+
} finally {
|
|
40
|
+
this.executingCallbacks.delete(key);
|
|
41
|
+
}
|
|
42
|
+
}, delay);
|
|
43
|
+
|
|
44
|
+
this.timeouts.set(key, timeoutId);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Debounce mejorado con cancelación manual
|
|
50
|
+
*/
|
|
51
|
+
debounce(key, callback, delay = this.defaultDelay) {
|
|
52
|
+
if (this.timeouts.has(key)) {
|
|
53
|
+
clearTimeout(this.timeouts.get(key));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const timeoutId = setTimeout(async () => {
|
|
58
|
+
this.timeouts.delete(key);
|
|
59
|
+
const result = await callback();
|
|
60
|
+
resolve(result);
|
|
61
|
+
}, delay);
|
|
62
|
+
|
|
63
|
+
this.timeouts.set(key, timeoutId);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Cancelar un throttle/debounce específico
|
|
69
|
+
*/
|
|
70
|
+
cancel(key) {
|
|
71
|
+
if (this.timeouts.has(key)) {
|
|
72
|
+
clearTimeout(this.timeouts.get(key));
|
|
73
|
+
this.timeouts.delete(key);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Cancelar todos los throttles/debounces pendientes
|
|
81
|
+
*/
|
|
82
|
+
cancelAll() {
|
|
83
|
+
for (const timeoutId of this.timeouts.values()) {
|
|
84
|
+
clearTimeout(timeoutId);
|
|
85
|
+
}
|
|
86
|
+
this.timeouts.clear();
|
|
87
|
+
this.executingCallbacks.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Verificar si hay un throttle/debounce pendiente
|
|
92
|
+
*/
|
|
93
|
+
isPending(key) {
|
|
94
|
+
return this.timeouts.has(key);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Verificar si un callback se está ejecutando
|
|
99
|
+
*/
|
|
100
|
+
isExecuting(key) {
|
|
101
|
+
return this.executingCallbacks.has(key);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Cleanup para evitar memory leaks
|
|
106
|
+
*/
|
|
107
|
+
destroy() {
|
|
108
|
+
this.cancelAll();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Slice/Components/Structural/Router/RouteCache.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de caché optimizado para contenedores de rutas
|
|
5
|
+
* Caché individual por contenedor con timestamps y cleanup automático
|
|
6
|
+
*/
|
|
7
|
+
export default class RouteCache {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.containerCaches = new Map();
|
|
10
|
+
this.CACHE_DURATION = 100; // ms
|
|
11
|
+
this.processedContainers = new WeakMap();
|
|
12
|
+
this.cleanupInterval = null;
|
|
13
|
+
|
|
14
|
+
// Auto-cleanup cada 5 segundos
|
|
15
|
+
this.startCleanupScheduler();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Obtener contenedores de rutas con caché inteligente
|
|
20
|
+
*/
|
|
21
|
+
getCachedRouteContainers(container) {
|
|
22
|
+
const containerKey = this.getContainerKey(container);
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
|
|
25
|
+
// Verificar caché individual por contenedor
|
|
26
|
+
if (this.containerCaches.has(containerKey)) {
|
|
27
|
+
const cached = this.containerCaches.get(containerKey);
|
|
28
|
+
|
|
29
|
+
// Verificar si el caché es válido y el contenedor sigue conectado
|
|
30
|
+
if ((now - cached.timestamp) < this.CACHE_DURATION &&
|
|
31
|
+
this.isContainerValid(container, cached.containers)) {
|
|
32
|
+
return cached.containers;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Regenerar caché para este contenedor específico
|
|
37
|
+
const routeContainers = this.findAllRouteContainersOptimized(container);
|
|
38
|
+
this.containerCaches.set(containerKey, {
|
|
39
|
+
containers: routeContainers,
|
|
40
|
+
timestamp: now,
|
|
41
|
+
containerRef: new WeakRef(container)
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return routeContainers;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Búsqueda optimizada con WeakMap para evitar recorridos repetidos
|
|
49
|
+
*/
|
|
50
|
+
findAllRouteContainersOptimized(container) {
|
|
51
|
+
// Si ya procesamos este contenedor recientemente, usar WeakMap
|
|
52
|
+
if (this.processedContainers.has(container)) {
|
|
53
|
+
const cached = this.processedContainers.get(container);
|
|
54
|
+
if (Date.now() - cached.timestamp < 50) { // Cache ultra corto para WeakMap
|
|
55
|
+
return cached.containers.filter(c => c.isConnected);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const routeContainers = [];
|
|
60
|
+
|
|
61
|
+
// TreeWalker optimizado con filtro más específico
|
|
62
|
+
const walker = document.createTreeWalker(
|
|
63
|
+
container,
|
|
64
|
+
NodeFilter.SHOW_ELEMENT,
|
|
65
|
+
{
|
|
66
|
+
acceptNode: (node) => {
|
|
67
|
+
// Filtro más eficiente
|
|
68
|
+
const tagName = node.tagName;
|
|
69
|
+
if (tagName === 'SLICE-ROUTE' || tagName === 'SLICE-MULTI-ROUTE') {
|
|
70
|
+
// Verificar que esté conectado y visible
|
|
71
|
+
if (node.isConnected && !node.hasAttribute('disabled')) {
|
|
72
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return NodeFilter.FILTER_SKIP;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
let node;
|
|
81
|
+
while (node = walker.nextNode()) {
|
|
82
|
+
routeContainers.push(node);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Guardar en WeakMap para acceso ultra-rápido
|
|
86
|
+
this.processedContainers.set(container, {
|
|
87
|
+
containers: routeContainers,
|
|
88
|
+
timestamp: Date.now()
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
return routeContainers;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generar clave única para contenedor
|
|
96
|
+
*/
|
|
97
|
+
getContainerKey(container) {
|
|
98
|
+
if (container === document) {
|
|
99
|
+
return 'document';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Usar sliceId si existe, si no, crear una clave basada en posición
|
|
103
|
+
return container.sliceId ||
|
|
104
|
+
container.id ||
|
|
105
|
+
`${container.tagName}-${this.getElementIndex(container)}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Obtener índice del elemento para identificación única
|
|
110
|
+
*/
|
|
111
|
+
getElementIndex(element) {
|
|
112
|
+
if (!element.parentNode) return 0;
|
|
113
|
+
|
|
114
|
+
const siblings = Array.from(element.parentNode.children);
|
|
115
|
+
return siblings.indexOf(element);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Verificar si los contenedores en caché siguen siendo válidos
|
|
120
|
+
*/
|
|
121
|
+
isContainerValid(container, cachedContainers) {
|
|
122
|
+
// Verificar que el contenedor principal siga conectado
|
|
123
|
+
if (!container.isConnected) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Verificar que al menos el 80% de los contenedores cached siguen conectados
|
|
128
|
+
const connectedCount = cachedContainers.filter(c => c.isConnected).length;
|
|
129
|
+
const validityThreshold = Math.max(1, Math.floor(cachedContainers.length * 0.8));
|
|
130
|
+
|
|
131
|
+
return connectedCount >= validityThreshold;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Invalidar caché específico de un contenedor
|
|
136
|
+
*/
|
|
137
|
+
invalidateContainer(container) {
|
|
138
|
+
const containerKey = this.getContainerKey(container);
|
|
139
|
+
this.containerCaches.delete(containerKey);
|
|
140
|
+
|
|
141
|
+
if (this.processedContainers.has(container)) {
|
|
142
|
+
this.processedContainers.delete(container);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Invalidar todo el caché
|
|
148
|
+
*/
|
|
149
|
+
invalidateAll() {
|
|
150
|
+
this.containerCaches.clear();
|
|
151
|
+
// No podemos limpiar WeakMap directamente, pero se limpiará automáticamente
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Invalidar caché selectivo basado en cambios DOM
|
|
156
|
+
*/
|
|
157
|
+
invalidateByMutation(mutations) {
|
|
158
|
+
const affectedContainers = new Set();
|
|
159
|
+
|
|
160
|
+
mutations.forEach((mutation) => {
|
|
161
|
+
if (mutation.type === 'childList') {
|
|
162
|
+
const addedNodes = Array.from(mutation.addedNodes);
|
|
163
|
+
const removedNodes = Array.from(mutation.removedNodes);
|
|
164
|
+
|
|
165
|
+
// Buscar contenedores afectados
|
|
166
|
+
[...addedNodes, ...removedNodes].forEach(node => {
|
|
167
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
168
|
+
if (node.tagName === 'SLICE-ROUTE' || node.tagName === 'SLICE-MULTI-ROUTE') {
|
|
169
|
+
affectedContainers.add(mutation.target);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Buscar contenedores padre que podrían estar afectados
|
|
173
|
+
let parent = mutation.target;
|
|
174
|
+
while (parent && parent !== document) {
|
|
175
|
+
if (parent.tagName && (
|
|
176
|
+
parent.tagName.includes('SLICE') ||
|
|
177
|
+
parent.hasAttribute('slice-component'))) {
|
|
178
|
+
affectedContainers.add(parent);
|
|
179
|
+
}
|
|
180
|
+
parent = parent.parentElement;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Invalidar solo los contenedores afectados
|
|
188
|
+
affectedContainers.forEach(container => {
|
|
189
|
+
this.invalidateContainer(container);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Cleanup automático de cachés obsoletos
|
|
195
|
+
*/
|
|
196
|
+
startCleanupScheduler() {
|
|
197
|
+
this.cleanupInterval = setInterval(() => {
|
|
198
|
+
this.cleanupStaleCache();
|
|
199
|
+
}, 5000); // Cada 5 segundos
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Limpiar caché obsoleto
|
|
204
|
+
*/
|
|
205
|
+
cleanupStaleCache() {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
const maxAge = this.CACHE_DURATION * 10; // 10x la duración normal para cleanup
|
|
208
|
+
|
|
209
|
+
for (const [key, cached] of this.containerCaches.entries()) {
|
|
210
|
+
// Eliminar cachés muy antiguos
|
|
211
|
+
if ((now - cached.timestamp) > maxAge) {
|
|
212
|
+
this.containerCaches.delete(key);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Eliminar cachés de contenedores que ya no existen
|
|
217
|
+
if (cached.containerRef && !cached.containerRef.deref()) {
|
|
218
|
+
this.containerCaches.delete(key);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Obtener estadísticas del caché para debugging
|
|
225
|
+
*/
|
|
226
|
+
getStats() {
|
|
227
|
+
return {
|
|
228
|
+
totalCaches: this.containerCaches.size,
|
|
229
|
+
oldestCache: Math.min(...Array.from(this.containerCaches.values()).map(c => c.timestamp)),
|
|
230
|
+
newestCache: Math.max(...Array.from(this.containerCaches.values()).map(c => c.timestamp))
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Destruir el caché y cleanup
|
|
236
|
+
*/
|
|
237
|
+
destroy() {
|
|
238
|
+
if (this.cleanupInterval) {
|
|
239
|
+
clearInterval(this.cleanupInterval);
|
|
240
|
+
this.cleanupInterval = null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.invalidateAll();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Slice/Components/Structural/Router/RouteMatcher.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de matching de rutas optimizado
|
|
5
|
+
* Separa la lógica de matching del Router principal
|
|
6
|
+
*/
|
|
7
|
+
export default class RouteMatcher {
|
|
8
|
+
constructor(routes) {
|
|
9
|
+
this.routes = routes;
|
|
10
|
+
this.pathToRouteMap = new Map();
|
|
11
|
+
this.compiledPatterns = new Map();
|
|
12
|
+
this.staticRoutes = new Map();
|
|
13
|
+
this.dynamicRoutes = new Map();
|
|
14
|
+
|
|
15
|
+
this.buildOptimizedMaps();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Construir mapas optimizados para matching rápido
|
|
20
|
+
*/
|
|
21
|
+
buildOptimizedMaps() {
|
|
22
|
+
this.pathToRouteMap = this.createPathToRouteMap(this.routes);
|
|
23
|
+
|
|
24
|
+
// Separar rutas estáticas de dinámicas para matching más rápido
|
|
25
|
+
for (const [path, route] of this.pathToRouteMap.entries()) {
|
|
26
|
+
if (path.includes('${')) {
|
|
27
|
+
// Ruta dinámica - pre-compilar el patrón
|
|
28
|
+
const compiled = this.compilePathPattern(path);
|
|
29
|
+
this.compiledPatterns.set(path, compiled);
|
|
30
|
+
this.dynamicRoutes.set(path, route);
|
|
31
|
+
} else {
|
|
32
|
+
// Ruta estática - acceso directo
|
|
33
|
+
this.staticRoutes.set(path, route);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Crear mapa de rutas con optimizaciones
|
|
40
|
+
*/
|
|
41
|
+
createPathToRouteMap(routes, basePath = '', parentRoute = null) {
|
|
42
|
+
const pathToRouteMap = new Map();
|
|
43
|
+
|
|
44
|
+
for (const route of routes) {
|
|
45
|
+
const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
|
|
46
|
+
|
|
47
|
+
const routeWithParent = {
|
|
48
|
+
...route,
|
|
49
|
+
fullPath,
|
|
50
|
+
parentPath: parentRoute ? parentRoute.fullPath : null,
|
|
51
|
+
parentRoute: parentRoute
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
pathToRouteMap.set(fullPath, routeWithParent);
|
|
55
|
+
|
|
56
|
+
if (route.children) {
|
|
57
|
+
const childPathToRouteMap = this.createPathToRouteMap(
|
|
58
|
+
route.children,
|
|
59
|
+
fullPath,
|
|
60
|
+
routeWithParent
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
|
|
64
|
+
pathToRouteMap.set(childPath, childRoute);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return pathToRouteMap;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Matching optimizado con separación de rutas estáticas/dinámicas
|
|
74
|
+
*/
|
|
75
|
+
matchRoute(path) {
|
|
76
|
+
// 1. Buscar primero en rutas estáticas (más rápido)
|
|
77
|
+
const staticMatch = this.staticRoutes.get(path);
|
|
78
|
+
if (staticMatch) {
|
|
79
|
+
return this.formatMatchResult(staticMatch, {});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 2. Buscar en rutas dinámicas solo si no hay match estático
|
|
83
|
+
for (const [routePattern, route] of this.dynamicRoutes.entries()) {
|
|
84
|
+
const compiled = this.compiledPatterns.get(routePattern);
|
|
85
|
+
const match = path.match(compiled.regex);
|
|
86
|
+
|
|
87
|
+
if (match) {
|
|
88
|
+
const params = {};
|
|
89
|
+
compiled.paramNames.forEach((name, i) => {
|
|
90
|
+
params[name] = match[i + 1];
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return this.formatMatchResult(route, params);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 3. Ruta 404 si no hay matches
|
|
98
|
+
const notFoundRoute = this.staticRoutes.get('/404');
|
|
99
|
+
return this.formatMatchResult(notFoundRoute, {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Formatear resultado del match consistentemente
|
|
104
|
+
*/
|
|
105
|
+
formatMatchResult(route, params) {
|
|
106
|
+
if (!route) {
|
|
107
|
+
return { route: null, params: {} };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (route.parentRoute) {
|
|
111
|
+
return {
|
|
112
|
+
route: route.parentRoute,
|
|
113
|
+
params: params,
|
|
114
|
+
childRoute: route
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { route, params };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compilar patrón de ruta con caché
|
|
123
|
+
*/
|
|
124
|
+
compilePathPattern(pattern) {
|
|
125
|
+
// Verificar si ya está compilado
|
|
126
|
+
if (this.compiledPatterns.has(pattern)) {
|
|
127
|
+
return this.compiledPatterns.get(pattern);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const paramNames = [];
|
|
131
|
+
const regexPattern = '^' + pattern.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
|
|
132
|
+
paramNames.push(paramName);
|
|
133
|
+
return '([^/]+)';
|
|
134
|
+
}) + '$';
|
|
135
|
+
|
|
136
|
+
const compiled = {
|
|
137
|
+
regex: new RegExp(regexPattern),
|
|
138
|
+
paramNames
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
this.compiledPatterns.set(pattern, compiled);
|
|
142
|
+
return compiled;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Obtener todas las rutas disponibles
|
|
147
|
+
*/
|
|
148
|
+
getAllRoutes() {
|
|
149
|
+
return Array.from(this.pathToRouteMap.values());
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Obtener rutas por componente
|
|
154
|
+
*/
|
|
155
|
+
getRoutesByComponent(componentName) {
|
|
156
|
+
return Array.from(this.pathToRouteMap.values())
|
|
157
|
+
.filter(route => route.component === componentName);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Verificar si una ruta existe
|
|
162
|
+
*/
|
|
163
|
+
hasRoute(path) {
|
|
164
|
+
return this.staticRoutes.has(path) ||
|
|
165
|
+
Array.from(this.dynamicRoutes.keys()).some(pattern => {
|
|
166
|
+
const compiled = this.compiledPatterns.get(pattern);
|
|
167
|
+
return compiled.regex.test(path);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generar URL con parámetros
|
|
173
|
+
*/
|
|
174
|
+
generateUrl(routePath, params = {}) {
|
|
175
|
+
if (!routePath.includes('${')) {
|
|
176
|
+
return routePath;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return routePath.replace(/\$\{([^}]+)\}/g, (_, paramName) => {
|
|
180
|
+
return params[paramName] || paramName;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Actualizar rutas dinámicamente
|
|
186
|
+
*/
|
|
187
|
+
updateRoutes(newRoutes) {
|
|
188
|
+
this.routes = newRoutes;
|
|
189
|
+
this.pathToRouteMap.clear();
|
|
190
|
+
this.compiledPatterns.clear();
|
|
191
|
+
this.staticRoutes.clear();
|
|
192
|
+
this.dynamicRoutes.clear();
|
|
193
|
+
|
|
194
|
+
this.buildOptimizedMaps();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Añadir ruta individual
|
|
199
|
+
*/
|
|
200
|
+
addRoute(route, basePath = '') {
|
|
201
|
+
const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
|
|
202
|
+
const routeWithPath = { ...route, fullPath };
|
|
203
|
+
|
|
204
|
+
this.pathToRouteMap.set(fullPath, routeWithPath);
|
|
205
|
+
|
|
206
|
+
if (fullPath.includes('${')) {
|
|
207
|
+
const compiled = this.compilePathPattern(fullPath);
|
|
208
|
+
this.compiledPatterns.set(fullPath, compiled);
|
|
209
|
+
this.dynamicRoutes.set(fullPath, routeWithPath);
|
|
210
|
+
} else {
|
|
211
|
+
this.staticRoutes.set(fullPath, routeWithPath);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Remover ruta
|
|
217
|
+
*/
|
|
218
|
+
removeRoute(path) {
|
|
219
|
+
this.pathToRouteMap.delete(path);
|
|
220
|
+
this.staticRoutes.delete(path);
|
|
221
|
+
this.dynamicRoutes.delete(path);
|
|
222
|
+
this.compiledPatterns.delete(path);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Obtener estadísticas del matcher
|
|
227
|
+
*/
|
|
228
|
+
getStats() {
|
|
229
|
+
return {
|
|
230
|
+
totalRoutes: this.pathToRouteMap.size,
|
|
231
|
+
staticRoutes: this.staticRoutes.size,
|
|
232
|
+
dynamicRoutes: this.dynamicRoutes.size,
|
|
233
|
+
compiledPatterns: this.compiledPatterns.size
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|