slicejs-web-framework 1.0.25 → 1.0.26
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.
|
@@ -3,6 +3,14 @@ export default class Router {
|
|
|
3
3
|
this.routes = routes;
|
|
4
4
|
this.activeRoute = null;
|
|
5
5
|
this.pathToRouteMap = this.createPathToRouteMap(routes);
|
|
6
|
+
|
|
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
|
|
11
|
+
|
|
12
|
+
// NUEVO: Observer para invalidar caché automáticamente
|
|
13
|
+
this.setupMutationObserver();
|
|
6
14
|
}
|
|
7
15
|
|
|
8
16
|
async init() {
|
|
@@ -10,13 +18,55 @@ export default class Router {
|
|
|
10
18
|
window.addEventListener('popstate', this.onRouteChange.bind(this));
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
// NUEVO: Observer para detectar cambios en el DOM
|
|
22
|
+
setupMutationObserver() {
|
|
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
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// NUEVO: Invalidar caché
|
|
59
|
+
invalidateCache() {
|
|
60
|
+
this.routeContainersCache.clear();
|
|
61
|
+
this.lastCacheUpdate = 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
13
64
|
createPathToRouteMap(routes, basePath = '', parentRoute = null) {
|
|
14
65
|
const pathToRouteMap = new Map();
|
|
15
66
|
|
|
16
67
|
for (const route of routes) {
|
|
17
68
|
const fullPath = `${basePath}${route.path}`.replace(/\/+/g, '/');
|
|
18
69
|
|
|
19
|
-
// Guardar la referencia a la ruta padre
|
|
20
70
|
const routeWithParent = {
|
|
21
71
|
...route,
|
|
22
72
|
fullPath,
|
|
@@ -25,13 +75,12 @@ export default class Router {
|
|
|
25
75
|
};
|
|
26
76
|
|
|
27
77
|
pathToRouteMap.set(fullPath, routeWithParent);
|
|
28
|
-
|
|
29
|
-
// Si tiene rutas secundarias, también agregarlas al mapa
|
|
78
|
+
|
|
30
79
|
if (route.children) {
|
|
31
80
|
const childPathToRouteMap = this.createPathToRouteMap(
|
|
32
81
|
route.children,
|
|
33
82
|
fullPath,
|
|
34
|
-
routeWithParent
|
|
83
|
+
routeWithParent
|
|
35
84
|
);
|
|
36
85
|
|
|
37
86
|
for (const [childPath, childRoute] of childPathToRouteMap.entries()) {
|
|
@@ -43,32 +92,109 @@ export default class Router {
|
|
|
43
92
|
return pathToRouteMap;
|
|
44
93
|
}
|
|
45
94
|
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
// OPTIMIZADO: Sistema de caché inteligente
|
|
96
|
+
async renderRoutesComponentsInPage(searchContainer = document) {
|
|
48
97
|
let routerContainersFlag = false;
|
|
98
|
+
const routeContainers = this.getCachedRouteContainers(searchContainer);
|
|
49
99
|
|
|
50
100
|
for (const routeContainer of routeContainers) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
101
|
+
try {
|
|
102
|
+
// Verificar que el componente aún esté conectado al DOM
|
|
103
|
+
if (!routeContainer.isConnected) {
|
|
104
|
+
this.invalidateCache();
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let response = await routeContainer.renderIfCurrentRoute();
|
|
109
|
+
if (response) {
|
|
110
|
+
this.activeRoute = routeContainer.props;
|
|
111
|
+
routerContainersFlag = true;
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
slice.logger.logError('Router', `Error rendering route container`, error);
|
|
55
115
|
}
|
|
56
116
|
}
|
|
117
|
+
|
|
57
118
|
return routerContainersFlag;
|
|
58
119
|
}
|
|
59
120
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
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
|
+
}
|
|
131
|
+
|
|
132
|
+
// Regenerar caché
|
|
133
|
+
const routeContainers = this.findAllRouteContainersOptimized(container);
|
|
134
|
+
this.routeContainersCache.set(containerKey, routeContainers);
|
|
135
|
+
this.lastCacheUpdate = now;
|
|
136
|
+
|
|
137
|
+
return routeContainers;
|
|
138
|
+
}
|
|
63
139
|
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
);
|
|
158
|
+
|
|
159
|
+
let node;
|
|
160
|
+
while (node = walker.nextNode()) {
|
|
161
|
+
routeContainers.push(node);
|
|
66
162
|
}
|
|
67
163
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
164
|
+
return routeContainers;
|
|
165
|
+
}
|
|
166
|
+
|
|
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
|
+
}
|
|
173
|
+
|
|
174
|
+
return await this.renderRoutesComponentsInPage(component);
|
|
175
|
+
}
|
|
176
|
+
|
|
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);
|
|
71
182
|
}
|
|
183
|
+
|
|
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();
|
|
188
|
+
|
|
189
|
+
if (routeContainersFlag) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const { route, params } = this.matchRoute(path);
|
|
194
|
+
if (route) {
|
|
195
|
+
await this.handleRoute(route, params);
|
|
196
|
+
}
|
|
197
|
+
}, 10);
|
|
72
198
|
}
|
|
73
199
|
|
|
74
200
|
async navigate(path) {
|
|
@@ -79,7 +205,6 @@ export default class Router {
|
|
|
79
205
|
async handleRoute(route, params) {
|
|
80
206
|
const targetElement = document.querySelector('#app');
|
|
81
207
|
|
|
82
|
-
// Si tenemos una ruta con parentRoute, usamos el componente del padre
|
|
83
208
|
const componentName = route.parentRoute ? route.parentRoute.component : route.component;
|
|
84
209
|
const sliceId = `route-${componentName}`;
|
|
85
210
|
|
|
@@ -95,19 +220,19 @@ export default class Router {
|
|
|
95
220
|
existingComponent.props = { ...existingComponent.props, ...params };
|
|
96
221
|
await existingComponent.update();
|
|
97
222
|
}
|
|
98
|
-
await this.renderRoutesComponentsInPage();
|
|
99
223
|
targetElement.appendChild(existingComponent);
|
|
100
224
|
} else {
|
|
101
225
|
const component = await slice.build(componentName, {
|
|
102
226
|
params,
|
|
103
227
|
sliceId: sliceId,
|
|
104
228
|
});
|
|
105
|
-
await this.renderRoutesComponentsInPage();
|
|
106
229
|
targetElement.innerHTML = '';
|
|
107
230
|
targetElement.appendChild(component);
|
|
108
231
|
}
|
|
109
232
|
|
|
110
|
-
|
|
233
|
+
// Invalidar caché después de cambios importantes en el DOM
|
|
234
|
+
this.invalidateCache();
|
|
235
|
+
await this.renderRoutesComponentsInPage();
|
|
111
236
|
|
|
112
237
|
if (slice.loading) {
|
|
113
238
|
slice.loading.stop();
|
|
@@ -125,22 +250,18 @@ export default class Router {
|
|
|
125
250
|
}
|
|
126
251
|
|
|
127
252
|
matchRoute(path) {
|
|
128
|
-
// 1. Buscar coincidencia exacta en el mapa
|
|
129
253
|
const exactMatch = this.pathToRouteMap.get(path);
|
|
130
254
|
if (exactMatch) {
|
|
131
|
-
// Si es una ruta hija y tiene un padre definido, devolvemos el padre en su lugar
|
|
132
255
|
if (exactMatch.parentRoute) {
|
|
133
|
-
// Mantenemos la información de parámetros que viene de la ruta actual
|
|
134
256
|
return {
|
|
135
257
|
route: exactMatch.parentRoute,
|
|
136
258
|
params: {},
|
|
137
|
-
childRoute: exactMatch
|
|
259
|
+
childRoute: exactMatch
|
|
138
260
|
};
|
|
139
261
|
}
|
|
140
262
|
return { route: exactMatch, params: {} };
|
|
141
263
|
}
|
|
142
264
|
|
|
143
|
-
// 2. Buscar coincidencias en rutas con parámetros
|
|
144
265
|
for (const [routePattern, route] of this.pathToRouteMap.entries()) {
|
|
145
266
|
if (routePattern.includes('${')) {
|
|
146
267
|
const { regex, paramNames } = this.compilePathPattern(routePattern);
|
|
@@ -151,7 +272,6 @@ export default class Router {
|
|
|
151
272
|
params[name] = match[i + 1];
|
|
152
273
|
});
|
|
153
274
|
|
|
154
|
-
// Si es una ruta hija, devolvemos el padre con los parámetros
|
|
155
275
|
if (route.parentRoute) {
|
|
156
276
|
return {
|
|
157
277
|
route: route.parentRoute,
|
|
@@ -165,7 +285,6 @@ export default class Router {
|
|
|
165
285
|
}
|
|
166
286
|
}
|
|
167
287
|
|
|
168
|
-
// 3. Si no hay coincidencias, retornar la ruta 404
|
|
169
288
|
const notFoundRoute = this.pathToRouteMap.get('/404');
|
|
170
289
|
return { route: notFoundRoute, params: {} };
|
|
171
290
|
}
|