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,324 @@
|
|
|
1
|
+
// Slice/Components/Structural/Router/RouteRenderer.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de renderizado de rutas optimizado
|
|
5
|
+
* Maneja la lógica de renderizado y gestión de componentes
|
|
6
|
+
*/
|
|
7
|
+
export default class RouteRenderer {
|
|
8
|
+
constructor(routeCache) {
|
|
9
|
+
this.routeCache = routeCache;
|
|
10
|
+
this.activeComponents = new Map();
|
|
11
|
+
this.componentPool = new Map();
|
|
12
|
+
this.renderQueue = new Set();
|
|
13
|
+
|
|
14
|
+
// Configuración de pool de componentes
|
|
15
|
+
this.maxPoolSize = 5;
|
|
16
|
+
this.componentCleanupDelay = 30000; // 30 segundos
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Renderizar rutas en página con optimizaciones
|
|
21
|
+
*/
|
|
22
|
+
async renderRoutesComponentsInPage(searchContainer = document) {
|
|
23
|
+
let routerContainersFlag = false;
|
|
24
|
+
const routeContainers = this.routeCache.getCachedRouteContainers(searchContainer);
|
|
25
|
+
|
|
26
|
+
// Usar Promise.allSettled para renderizado paralelo
|
|
27
|
+
const renderPromises = routeContainers.map(async (routeContainer) => {
|
|
28
|
+
try {
|
|
29
|
+
// Verificar que el componente aún esté conectado al DOM
|
|
30
|
+
if (!routeContainer.isConnected) {
|
|
31
|
+
this.routeCache.invalidateContainer(searchContainer);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Evitar renderizado duplicado
|
|
36
|
+
const containerId = this.getContainerId(routeContainer);
|
|
37
|
+
if (this.renderQueue.has(containerId)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.renderQueue.add(containerId);
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await routeContainer.renderIfCurrentRoute();
|
|
45
|
+
if (response) {
|
|
46
|
+
routerContainersFlag = true;
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
this.renderQueue.delete(containerId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
slice.logger.logError('RouteRenderer', `Error rendering route container`, error);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await Promise.allSettled(renderPromises);
|
|
61
|
+
return routerContainersFlag;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Renderizar rutas dentro de un componente específico
|
|
66
|
+
*/
|
|
67
|
+
async renderRoutesInComponent(component) {
|
|
68
|
+
if (!component) {
|
|
69
|
+
slice.logger.logWarning('RouteRenderer', 'No component provided for route rendering');
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return await this.renderRoutesComponentsInPage(component);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Manejar renderizado de ruta con pool de componentes
|
|
78
|
+
*/
|
|
79
|
+
async handleRoute(route, params) {
|
|
80
|
+
const targetElement = document.querySelector('#app');
|
|
81
|
+
|
|
82
|
+
if (!targetElement) {
|
|
83
|
+
slice.logger.logError('RouteRenderer', 'Target element #app not found');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const componentName = route.parentRoute ?
|
|
88
|
+
route.parentRoute.component : route.component;
|
|
89
|
+
const sliceId = `route-${componentName}`;
|
|
90
|
+
|
|
91
|
+
// Mostrar loading si está disponible
|
|
92
|
+
if (slice.loading) {
|
|
93
|
+
slice.loading.start();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Intentar reutilizar componente existente
|
|
98
|
+
let component = await this.getOrCreateComponent(componentName, sliceId, params);
|
|
99
|
+
|
|
100
|
+
if (!component) {
|
|
101
|
+
slice.logger.logError('RouteRenderer', `Failed to create component ${componentName}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Limpiar y renderizar
|
|
106
|
+
await this.renderComponent(targetElement, component);
|
|
107
|
+
|
|
108
|
+
// Renderizar rutas anidadas después de insertar
|
|
109
|
+
await this.renderRoutesInComponent(component);
|
|
110
|
+
|
|
111
|
+
// Actualizar ruta activa
|
|
112
|
+
slice.router.activeRoute = route;
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
slice.logger.logError('RouteRenderer', `Error handling route ${route.path}`, error);
|
|
116
|
+
} finally {
|
|
117
|
+
// Ocultar loading
|
|
118
|
+
if (slice.loading) {
|
|
119
|
+
slice.loading.stop();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Obtener o crear componente con pool
|
|
126
|
+
*/
|
|
127
|
+
async getOrCreateComponent(componentName, sliceId, params) {
|
|
128
|
+
// Verificar si ya existe
|
|
129
|
+
const existingComponent = slice.controller.getComponent(sliceId);
|
|
130
|
+
|
|
131
|
+
if (existingComponent && existingComponent.isConnected) {
|
|
132
|
+
// Actualizar props si es necesario
|
|
133
|
+
if (existingComponent.update) {
|
|
134
|
+
existingComponent.props = { ...existingComponent.props, ...params };
|
|
135
|
+
await existingComponent.update();
|
|
136
|
+
}
|
|
137
|
+
return existingComponent;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Intentar obtener del pool
|
|
141
|
+
const pooledComponent = this.getFromPool(componentName);
|
|
142
|
+
if (pooledComponent) {
|
|
143
|
+
pooledComponent.sliceId = sliceId;
|
|
144
|
+
pooledComponent.props = { ...pooledComponent.props, ...params };
|
|
145
|
+
|
|
146
|
+
if (pooledComponent.update) {
|
|
147
|
+
await pooledComponent.update();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return pooledComponent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Crear nuevo componente
|
|
154
|
+
const component = await slice.build(componentName, {
|
|
155
|
+
params,
|
|
156
|
+
sliceId: sliceId,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return component;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Renderizar componente en el elemento objetivo
|
|
164
|
+
*/
|
|
165
|
+
async renderComponent(targetElement, component) {
|
|
166
|
+
// Guardar componente anterior para pool
|
|
167
|
+
const previousComponent = targetElement.firstElementChild;
|
|
168
|
+
if (previousComponent && previousComponent !== component) {
|
|
169
|
+
this.addToPool(previousComponent);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Usar DocumentFragment para renderizado más eficiente
|
|
173
|
+
const fragment = document.createDocumentFragment();
|
|
174
|
+
fragment.appendChild(component);
|
|
175
|
+
|
|
176
|
+
// Limpiar y insertar
|
|
177
|
+
targetElement.innerHTML = '';
|
|
178
|
+
targetElement.appendChild(fragment);
|
|
179
|
+
|
|
180
|
+
// Registrar componente activo
|
|
181
|
+
this.activeComponents.set(component.sliceId || 'anonymous', component);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Obtener componente del pool
|
|
186
|
+
*/
|
|
187
|
+
getFromPool(componentName) {
|
|
188
|
+
const pool = this.componentPool.get(componentName);
|
|
189
|
+
if (pool && pool.length > 0) {
|
|
190
|
+
return pool.pop();
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Añadir componente al pool
|
|
197
|
+
*/
|
|
198
|
+
addToPool(component) {
|
|
199
|
+
if (!component || !component.tagName) return;
|
|
200
|
+
|
|
201
|
+
const componentName = component.tagName.toLowerCase();
|
|
202
|
+
|
|
203
|
+
if (!this.componentPool.has(componentName)) {
|
|
204
|
+
this.componentPool.set(componentName, []);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const pool = this.componentPool.get(componentName);
|
|
208
|
+
|
|
209
|
+
// Limitar tamaño del pool
|
|
210
|
+
if (pool.length < this.maxPoolSize) {
|
|
211
|
+
// Limpiar el componente antes de añadirlo al pool
|
|
212
|
+
this.cleanupComponent(component);
|
|
213
|
+
pool.push(component);
|
|
214
|
+
|
|
215
|
+
// Programar cleanup automático
|
|
216
|
+
this.scheduleComponentCleanup(componentName, component);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Limpiar componente para reutilización
|
|
222
|
+
*/
|
|
223
|
+
cleanupComponent(component) {
|
|
224
|
+
// Remover event listeners específicos del contexto anterior
|
|
225
|
+
if (component.cleanup) {
|
|
226
|
+
component.cleanup();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Resetear propiedades específicas
|
|
230
|
+
if (component.props) {
|
|
231
|
+
component.props = {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Remover del DOM si está conectado
|
|
235
|
+
if (component.isConnected) {
|
|
236
|
+
component.remove();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Programar cleanup automático de componentes del pool
|
|
242
|
+
*/
|
|
243
|
+
scheduleComponentCleanup(componentName, component) {
|
|
244
|
+
setTimeout(() => {
|
|
245
|
+
const pool = this.componentPool.get(componentName);
|
|
246
|
+
if (pool) {
|
|
247
|
+
const index = pool.indexOf(component);
|
|
248
|
+
if (index > -1) {
|
|
249
|
+
pool.splice(index, 1);
|
|
250
|
+
|
|
251
|
+
// Cleanup final del componente
|
|
252
|
+
this.destroyComponent(component);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}, this.componentCleanupDelay);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Destruir componente completamente
|
|
260
|
+
*/
|
|
261
|
+
destroyComponent(component) {
|
|
262
|
+
if (component.destroy) {
|
|
263
|
+
component.destroy();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Remover referencias
|
|
267
|
+
this.activeComponents.delete(component.sliceId);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Obtener ID único del contenedor
|
|
272
|
+
*/
|
|
273
|
+
getContainerId(container) {
|
|
274
|
+
return container.sliceId ||
|
|
275
|
+
container.id ||
|
|
276
|
+
`${container.tagName}-${Math.random().toString(36).substr(2, 9)}`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Limpiar pool de componentes
|
|
281
|
+
*/
|
|
282
|
+
clearPool(componentName = null) {
|
|
283
|
+
if (componentName) {
|
|
284
|
+
const pool = this.componentPool.get(componentName);
|
|
285
|
+
if (pool) {
|
|
286
|
+
pool.forEach(component => this.destroyComponent(component));
|
|
287
|
+
this.componentPool.delete(componentName);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
// Limpiar todo el pool
|
|
291
|
+
for (const [name, pool] of this.componentPool.entries()) {
|
|
292
|
+
pool.forEach(component => this.destroyComponent(component));
|
|
293
|
+
}
|
|
294
|
+
this.componentPool.clear();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Obtener estadísticas del renderer
|
|
300
|
+
*/
|
|
301
|
+
getStats() {
|
|
302
|
+
const poolStats = {};
|
|
303
|
+
for (const [name, pool] of this.componentPool.entries()) {
|
|
304
|
+
poolStats[name] = pool.length;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
activeComponents: this.activeComponents.size,
|
|
309
|
+
renderQueue: this.renderQueue.size,
|
|
310
|
+
componentPool: poolStats,
|
|
311
|
+
totalPooledComponents: Array.from(this.componentPool.values())
|
|
312
|
+
.reduce((total, pool) => total + pool.length, 0)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Destruir el renderer y cleanup
|
|
318
|
+
*/
|
|
319
|
+
destroy() {
|
|
320
|
+
this.clearPool();
|
|
321
|
+
this.activeComponents.clear();
|
|
322
|
+
this.renderQueue.clear();
|
|
323
|
+
}
|
|
324
|
+
}
|