slicejs-web-framework 1.0.24 → 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 // Pasar la ruta actual como padre
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
- async renderRoutesComponentsInPage() {
47
- const routeContainers = document.querySelectorAll('slice-route, slice-multi-route');
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
- let response = await routeContainer.renderIfCurrentRoute();
52
- if (response) {
53
- this.activeRoute = routeContainer.props;
54
- routerContainersFlag = true;
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
- async onRouteChange() {
61
- const path = window.location.pathname;
62
- const routeContainersFlag = await this.renderRoutesComponentsInPage();
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
- if (routeContainersFlag) {
65
- return;
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
- const { route, params } = this.matchRoute(path);
69
- if (route) {
70
- await this.handleRoute(route, params);
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;
71
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);
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
 
@@ -105,9 +230,10 @@ export default class Router {
105
230
  targetElement.appendChild(component);
106
231
  }
107
232
 
233
+ // Invalidar caché después de cambios importantes en el DOM
234
+ this.invalidateCache();
108
235
  await this.renderRoutesComponentsInPage();
109
236
 
110
-
111
237
  if (slice.loading) {
112
238
  slice.loading.stop();
113
239
  }
@@ -124,22 +250,18 @@ export default class Router {
124
250
  }
125
251
 
126
252
  matchRoute(path) {
127
- // 1. Buscar coincidencia exacta en el mapa
128
253
  const exactMatch = this.pathToRouteMap.get(path);
129
254
  if (exactMatch) {
130
- // Si es una ruta hija y tiene un padre definido, devolvemos el padre en su lugar
131
255
  if (exactMatch.parentRoute) {
132
- // Mantenemos la información de parámetros que viene de la ruta actual
133
256
  return {
134
257
  route: exactMatch.parentRoute,
135
258
  params: {},
136
- childRoute: exactMatch // Guardamos la referencia a la ruta hija original
259
+ childRoute: exactMatch
137
260
  };
138
261
  }
139
262
  return { route: exactMatch, params: {} };
140
263
  }
141
264
 
142
- // 2. Buscar coincidencias en rutas con parámetros
143
265
  for (const [routePattern, route] of this.pathToRouteMap.entries()) {
144
266
  if (routePattern.includes('${')) {
145
267
  const { regex, paramNames } = this.compilePathPattern(routePattern);
@@ -150,7 +272,6 @@ export default class Router {
150
272
  params[name] = match[i + 1];
151
273
  });
152
274
 
153
- // Si es una ruta hija, devolvemos el padre con los parámetros
154
275
  if (route.parentRoute) {
155
276
  return {
156
277
  route: route.parentRoute,
@@ -164,7 +285,6 @@ export default class Router {
164
285
  }
165
286
  }
166
287
 
167
- // 3. Si no hay coincidencias, retornar la ruta 404
168
288
  const notFoundRoute = this.pathToRouteMap.get('/404');
169
289
  return { route: notFoundRoute, params: {} };
170
290
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-web-framework",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=20"