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.
@@ -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
+ }