slicejs-web-framework 1.0.25 → 1.0.28
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
|
+
}
|
|
63
131
|
|
|
64
|
-
|
|
65
|
-
|
|
132
|
+
// Regenerar caché
|
|
133
|
+
const routeContainers = this.findAllRouteContainersOptimized(container);
|
|
134
|
+
this.routeContainersCache.set(containerKey, routeContainers);
|
|
135
|
+
this.lastCacheUpdate = now;
|
|
136
|
+
|
|
137
|
+
return routeContainers;
|
|
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
|
+
}
|
|
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) {
|
|
@@ -77,70 +203,69 @@ export default class Router {
|
|
|
77
203
|
}
|
|
78
204
|
|
|
79
205
|
async handleRoute(route, params) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const existingComponent = slice.controller.getComponent(sliceId);
|
|
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);
|
|
87
212
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
213
|
+
if (slice.loading) {
|
|
214
|
+
slice.loading.start();
|
|
215
|
+
}
|
|
91
216
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
await this.renderRoutesComponentsInPage();
|
|
99
|
-
targetElement.appendChild(existingComponent);
|
|
100
|
-
} else {
|
|
101
|
-
const component = await slice.build(componentName, {
|
|
102
|
-
params,
|
|
103
|
-
sliceId: sliceId,
|
|
104
|
-
});
|
|
105
|
-
await this.renderRoutesComponentsInPage();
|
|
106
|
-
targetElement.innerHTML = '';
|
|
107
|
-
targetElement.appendChild(component);
|
|
217
|
+
if (existingComponent) {
|
|
218
|
+
targetElement.innerHTML = '';
|
|
219
|
+
if (existingComponent.update) {
|
|
220
|
+
existingComponent.props = { ...existingComponent.props, ...params };
|
|
221
|
+
await existingComponent.update();
|
|
108
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
|
+
});
|
|
109
231
|
|
|
232
|
+
targetElement.innerHTML = '';
|
|
233
|
+
targetElement.appendChild(component);
|
|
234
|
+
|
|
235
|
+
// Renderizar INMEDIATAMENTE después de insertar
|
|
236
|
+
await this.renderRoutesInComponent(component);
|
|
237
|
+
}
|
|
110
238
|
|
|
239
|
+
// Invalidar caché después de cambios importantes en el DOM
|
|
240
|
+
this.invalidateCache();
|
|
111
241
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
slice.router.activeRoute = route;
|
|
242
|
+
if (slice.loading) {
|
|
243
|
+
slice.loading.stop();
|
|
117
244
|
}
|
|
118
245
|
|
|
246
|
+
slice.router.activeRoute = route;
|
|
247
|
+
}
|
|
248
|
+
|
|
119
249
|
async loadInitialRoute() {
|
|
120
250
|
const path = window.location.pathname;
|
|
121
251
|
const { route, params } = this.matchRoute(path);
|
|
122
252
|
|
|
123
253
|
await this.handleRoute(route, params);
|
|
124
|
-
await this.renderRoutesComponentsInPage();
|
|
125
254
|
}
|
|
126
255
|
|
|
127
256
|
matchRoute(path) {
|
|
128
|
-
// 1. Buscar coincidencia exacta en el mapa
|
|
129
257
|
const exactMatch = this.pathToRouteMap.get(path);
|
|
130
258
|
if (exactMatch) {
|
|
131
|
-
// Si es una ruta hija y tiene un padre definido, devolvemos el padre en su lugar
|
|
132
259
|
if (exactMatch.parentRoute) {
|
|
133
|
-
// Mantenemos la información de parámetros que viene de la ruta actual
|
|
134
260
|
return {
|
|
135
261
|
route: exactMatch.parentRoute,
|
|
136
262
|
params: {},
|
|
137
|
-
childRoute: exactMatch
|
|
263
|
+
childRoute: exactMatch
|
|
138
264
|
};
|
|
139
265
|
}
|
|
140
266
|
return { route: exactMatch, params: {} };
|
|
141
267
|
}
|
|
142
268
|
|
|
143
|
-
// 2. Buscar coincidencias en rutas con parámetros
|
|
144
269
|
for (const [routePattern, route] of this.pathToRouteMap.entries()) {
|
|
145
270
|
if (routePattern.includes('${')) {
|
|
146
271
|
const { regex, paramNames } = this.compilePathPattern(routePattern);
|
|
@@ -151,7 +276,6 @@ export default class Router {
|
|
|
151
276
|
params[name] = match[i + 1];
|
|
152
277
|
});
|
|
153
278
|
|
|
154
|
-
// Si es una ruta hija, devolvemos el padre con los parámetros
|
|
155
279
|
if (route.parentRoute) {
|
|
156
280
|
return {
|
|
157
281
|
route: route.parentRoute,
|
|
@@ -165,7 +289,6 @@ export default class Router {
|
|
|
165
289
|
}
|
|
166
290
|
}
|
|
167
291
|
|
|
168
|
-
// 3. Si no hay coincidencias, retornar la ruta 404
|
|
169
292
|
const notFoundRoute = this.pathToRouteMap.get('/404');
|
|
170
293
|
return { route: notFoundRoute, params: {} };
|
|
171
294
|
}
|