wu-framework 1.0.6 → 1.1.0

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,642 @@
1
+ /**
2
+ * 🚀 WU-FRAMEWORK ANGULAR ADAPTER
3
+ *
4
+ * Simplifica la integración de Angular con Wu Framework.
5
+ * Convierte componentes Angular en microfrontends con configuración mínima.
6
+ *
7
+ * @example
8
+ * // Microfrontend (main.ts)
9
+ * import { wuAngular } from 'wu-framework/adapters/angular';
10
+ * import { AppModule } from './app/app.module';
11
+ * // o para standalone components
12
+ * import { AppComponent } from './app/app.component';
13
+ *
14
+ * wuAngular.register('my-app', AppModule);
15
+ * // o
16
+ * wuAngular.registerStandalone('my-app', AppComponent);
17
+ *
18
+ * @example
19
+ * // Shell (cargar microfrontend)
20
+ * import { WuSlotComponent } from 'wu-framework/adapters/angular';
21
+ *
22
+ * <wu-slot name="my-app" url="http://localhost:3001"></wu-slot>
23
+ */
24
+
25
+ // Estado global del adapter
26
+ const adapterState = {
27
+ apps: new Map(),
28
+ platformRef: null,
29
+ initialized: false
30
+ };
31
+
32
+ /**
33
+ * Obtiene la instancia de Wu Framework
34
+ */
35
+ function getWuInstance() {
36
+ if (typeof window === 'undefined') return null;
37
+
38
+ return window.wu
39
+ || window.parent?.wu
40
+ || window.top?.wu
41
+ || null;
42
+ }
43
+
44
+ /**
45
+ * Espera a que Wu Framework esté disponible
46
+ */
47
+ function waitForWu(timeout = 5000) {
48
+ return new Promise((resolve, reject) => {
49
+ const wu = getWuInstance();
50
+ if (wu) {
51
+ resolve(wu);
52
+ return;
53
+ }
54
+
55
+ const startTime = Date.now();
56
+
57
+ const handleWuReady = () => {
58
+ cleanup();
59
+ resolve(getWuInstance());
60
+ };
61
+
62
+ window.addEventListener('wu:ready', handleWuReady);
63
+ window.addEventListener('wu:app:ready', handleWuReady);
64
+
65
+ const checkInterval = setInterval(() => {
66
+ const wu = getWuInstance();
67
+ if (wu) {
68
+ cleanup();
69
+ resolve(wu);
70
+ return;
71
+ }
72
+
73
+ if (Date.now() - startTime > timeout) {
74
+ cleanup();
75
+ reject(new Error(`Wu Framework not found after ${timeout}ms`));
76
+ }
77
+ }, 200);
78
+
79
+ function cleanup() {
80
+ clearInterval(checkInterval);
81
+ window.removeEventListener('wu:ready', handleWuReady);
82
+ window.removeEventListener('wu:app:ready', handleWuReady);
83
+ }
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Registra un módulo Angular como microfrontend
89
+ *
90
+ * @param {string} appName - Nombre único del microfrontend
91
+ * @param {Type<any>} AppModule - Módulo Angular principal (AppModule)
92
+ * @param {Object} options - Opciones adicionales
93
+ * @param {Function} options.platformFactory - Factory del platform (platformBrowserDynamic por defecto)
94
+ * @param {Array} options.providers - Providers adicionales para bootstrap
95
+ * @param {Function} options.onMount - Callback después de montar
96
+ * @param {Function} options.onUnmount - Callback antes de desmontar
97
+ * @param {boolean} options.standalone - Permitir ejecución standalone (default: true)
98
+ * @param {string} options.standaloneContainer - Selector para modo standalone (default: '#root')
99
+ * @param {string} options.rootSelector - Selector del componente root (default: 'app-root')
100
+ *
101
+ * @example
102
+ * // Básico
103
+ * wuAngular.register('my-app', AppModule);
104
+ *
105
+ * @example
106
+ * // Con opciones
107
+ * wuAngular.register('my-app', AppModule, {
108
+ * providers: [{ provide: 'API_URL', useValue: 'https://api.example.com' }],
109
+ * onMount: (container) => console.log('Mounted!'),
110
+ * rootSelector: 'my-app-root'
111
+ * });
112
+ */
113
+ async function register(appName, AppModule, options = {}) {
114
+ const {
115
+ platformFactory = null,
116
+ providers = [],
117
+ onMount = null,
118
+ onUnmount = null,
119
+ standalone = true,
120
+ standaloneContainer = '#root',
121
+ rootSelector = 'app-root'
122
+ } = options;
123
+
124
+ // Función de mount interna
125
+ const mountApp = async (container) => {
126
+ if (!container) {
127
+ console.error(`[WuAngular] Mount failed for ${appName}: container is null`);
128
+ return;
129
+ }
130
+
131
+ // Evitar doble mount
132
+ if (adapterState.apps.has(appName)) {
133
+ console.warn(`[WuAngular] ${appName} already mounted, unmounting first`);
134
+ await unmountApp();
135
+ }
136
+
137
+ try {
138
+ // Crear elemento root para Angular
139
+ const appElement = document.createElement(rootSelector);
140
+ appElement.setAttribute('data-wu-angular-root', appName);
141
+ container.innerHTML = '';
142
+ container.appendChild(appElement);
143
+
144
+ // Obtener platformBrowserDynamic
145
+ let platform;
146
+ if (platformFactory) {
147
+ platform = platformFactory;
148
+ } else {
149
+ // Intentar import dinámico
150
+ try {
151
+ const platformModule = await import('@angular/platform-browser-dynamic');
152
+ platform = platformModule.platformBrowserDynamic;
153
+ } catch (e) {
154
+ // Intentar desde window
155
+ if (window.ng?.platformBrowserDynamic) {
156
+ platform = window.ng.platformBrowserDynamic;
157
+ } else {
158
+ throw new Error('platformBrowserDynamic not available. Please provide it via options.platformFactory');
159
+ }
160
+ }
161
+ }
162
+
163
+ // Bootstrap del módulo
164
+ const platformRef = platform(providers);
165
+ const moduleRef = await platformRef.bootstrapModule(AppModule);
166
+
167
+ // Guardar referencias
168
+ adapterState.apps.set(appName, {
169
+ platformRef,
170
+ moduleRef,
171
+ container,
172
+ rootElement: appElement
173
+ });
174
+
175
+ console.log(`[WuAngular] ✅ ${appName} mounted successfully`);
176
+
177
+ if (onMount) {
178
+ onMount(container, moduleRef);
179
+ }
180
+ } catch (error) {
181
+ console.error(`[WuAngular] Mount error for ${appName}:`, error);
182
+ throw error;
183
+ }
184
+ };
185
+
186
+ // Función de unmount interna
187
+ const unmountApp = async (container) => {
188
+ const instance = adapterState.apps.get(appName);
189
+
190
+ if (instance) {
191
+ try {
192
+ if (onUnmount) {
193
+ onUnmount(instance.container, instance.moduleRef);
194
+ }
195
+
196
+ // Destruir el módulo
197
+ if (instance.moduleRef) {
198
+ instance.moduleRef.destroy();
199
+ }
200
+
201
+ // Destruir la plataforma
202
+ if (instance.platformRef) {
203
+ instance.platformRef.destroy();
204
+ }
205
+
206
+ // Limpiar DOM
207
+ if (instance.rootElement && instance.rootElement.parentNode) {
208
+ instance.rootElement.parentNode.removeChild(instance.rootElement);
209
+ }
210
+
211
+ adapterState.apps.delete(appName);
212
+
213
+ console.log(`[WuAngular] ✅ ${appName} unmounted successfully`);
214
+ } catch (error) {
215
+ console.error(`[WuAngular] Unmount error for ${appName}:`, error);
216
+ }
217
+ }
218
+
219
+ // Limpiar container si se proporciona
220
+ if (container) {
221
+ container.innerHTML = '';
222
+ }
223
+ };
224
+
225
+ // Intentar registrar con Wu Framework
226
+ try {
227
+ const wu = await waitForWu(3000);
228
+
229
+ wu.define(appName, {
230
+ mount: mountApp,
231
+ unmount: unmountApp
232
+ });
233
+
234
+ console.log(`[WuAngular] ✅ ${appName} registered with Wu Framework`);
235
+ return true;
236
+
237
+ } catch (error) {
238
+ console.warn(`[WuAngular] Wu Framework not available for ${appName}`);
239
+
240
+ // Modo standalone si está habilitado
241
+ if (standalone) {
242
+ const containerElement = document.querySelector(standaloneContainer);
243
+
244
+ if (containerElement) {
245
+ console.log(`[WuAngular] Running ${appName} in standalone mode`);
246
+ await mountApp(containerElement);
247
+ return true;
248
+ } else {
249
+ console.warn(`[WuAngular] Standalone container ${standaloneContainer} not found`);
250
+ }
251
+ }
252
+
253
+ return false;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Registra un componente Angular standalone como microfrontend (Angular 14+)
259
+ *
260
+ * @param {string} appName - Nombre único del microfrontend
261
+ * @param {Type<any>} RootComponent - Componente standalone principal
262
+ * @param {Object} options - Opciones adicionales
263
+ * @param {ApplicationConfig} options.appConfig - Configuración de la aplicación
264
+ * @param {Function} options.onMount - Callback después de montar
265
+ * @param {Function} options.onUnmount - Callback antes de desmontar
266
+ * @param {boolean} options.standalone - Permitir ejecución standalone (default: true)
267
+ * @param {string} options.standaloneContainer - Selector para modo standalone (default: '#root')
268
+ *
269
+ * @example
270
+ * // Angular 14+ con standalone components
271
+ * import { AppComponent } from './app/app.component';
272
+ * import { appConfig } from './app/app.config';
273
+ *
274
+ * wuAngular.registerStandalone('my-app', AppComponent, { appConfig });
275
+ */
276
+ async function registerStandalone(appName, RootComponent, options = {}) {
277
+ const {
278
+ appConfig = {},
279
+ onMount = null,
280
+ onUnmount = null,
281
+ standalone = true,
282
+ standaloneContainer = '#root'
283
+ } = options;
284
+
285
+ // Función de mount para standalone components
286
+ const mountApp = async (container) => {
287
+ if (!container) {
288
+ console.error(`[WuAngular] Mount failed for ${appName}: container is null`);
289
+ return;
290
+ }
291
+
292
+ // Evitar doble mount
293
+ if (adapterState.apps.has(appName)) {
294
+ console.warn(`[WuAngular] ${appName} already mounted, unmounting first`);
295
+ await unmountApp();
296
+ }
297
+
298
+ try {
299
+ // Obtener bootstrapApplication
300
+ let bootstrapApplication;
301
+ try {
302
+ const browserModule = await import('@angular/platform-browser');
303
+ bootstrapApplication = browserModule.bootstrapApplication;
304
+ } catch (e) {
305
+ if (window.ng?.bootstrapApplication) {
306
+ bootstrapApplication = window.ng.bootstrapApplication;
307
+ } else {
308
+ throw new Error('bootstrapApplication not available. Requires Angular 14+');
309
+ }
310
+ }
311
+
312
+ // Bootstrap del componente standalone
313
+ const appRef = await bootstrapApplication(RootComponent, appConfig);
314
+
315
+ // Guardar referencias
316
+ adapterState.apps.set(appName, {
317
+ appRef,
318
+ container,
319
+ isStandalone: true
320
+ });
321
+
322
+ console.log(`[WuAngular] ✅ ${appName} (standalone) mounted successfully`);
323
+
324
+ if (onMount) {
325
+ onMount(container, appRef);
326
+ }
327
+ } catch (error) {
328
+ console.error(`[WuAngular] Mount error for ${appName}:`, error);
329
+ throw error;
330
+ }
331
+ };
332
+
333
+ // Función de unmount para standalone
334
+ const unmountApp = async (container) => {
335
+ const instance = adapterState.apps.get(appName);
336
+
337
+ if (instance) {
338
+ try {
339
+ if (onUnmount) {
340
+ onUnmount(instance.container, instance.appRef);
341
+ }
342
+
343
+ // Destruir la aplicación
344
+ if (instance.appRef) {
345
+ instance.appRef.destroy();
346
+ }
347
+
348
+ adapterState.apps.delete(appName);
349
+
350
+ console.log(`[WuAngular] ✅ ${appName} (standalone) unmounted successfully`);
351
+ } catch (error) {
352
+ console.error(`[WuAngular] Unmount error for ${appName}:`, error);
353
+ }
354
+ }
355
+
356
+ if (container) {
357
+ container.innerHTML = '';
358
+ }
359
+ };
360
+
361
+ // Intentar registrar con Wu Framework
362
+ try {
363
+ const wu = await waitForWu(3000);
364
+
365
+ wu.define(appName, {
366
+ mount: mountApp,
367
+ unmount: unmountApp
368
+ });
369
+
370
+ console.log(`[WuAngular] ✅ ${appName} (standalone) registered with Wu Framework`);
371
+ return true;
372
+
373
+ } catch (error) {
374
+ console.warn(`[WuAngular] Wu Framework not available for ${appName}`);
375
+
376
+ if (standalone) {
377
+ const containerElement = document.querySelector(standaloneContainer);
378
+
379
+ if (containerElement) {
380
+ console.log(`[WuAngular] Running ${appName} in standalone mode`);
381
+ await mountApp(containerElement);
382
+ return true;
383
+ }
384
+ }
385
+
386
+ return false;
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Servicio Injectable para usar Wu Framework en Angular
392
+ *
393
+ * @example
394
+ * // En tu módulo o componente
395
+ * import { WuService } from 'wu-framework/adapters/angular';
396
+ *
397
+ * @Injectable()
398
+ * export class MyService {
399
+ * constructor() {
400
+ * this.wu = createWuService();
401
+ * }
402
+ *
403
+ * sendEvent() {
404
+ * this.wu.emit('user:action', { data: 'test' });
405
+ * }
406
+ * }
407
+ */
408
+ function createWuService() {
409
+ const subscriptions = [];
410
+
411
+ return {
412
+ // Event Bus
413
+ emit: (event, data, options) => {
414
+ const wu = getWuInstance();
415
+ if (wu?.eventBus) {
416
+ wu.eventBus.emit(event, data, options);
417
+ } else {
418
+ console.warn('[WuService] Wu Framework not available');
419
+ }
420
+ },
421
+
422
+ on: (event, callback) => {
423
+ const wu = getWuInstance();
424
+ if (wu?.eventBus) {
425
+ const unsubscribe = wu.eventBus.on(event, callback);
426
+ subscriptions.push(unsubscribe);
427
+ return unsubscribe;
428
+ }
429
+ console.warn('[WuService] Wu Framework not available');
430
+ return () => {};
431
+ },
432
+
433
+ once: (event, callback) => {
434
+ const wu = getWuInstance();
435
+ if (wu?.eventBus) {
436
+ return wu.eventBus.once(event, callback);
437
+ }
438
+ return () => {};
439
+ },
440
+
441
+ off: (event, callback) => {
442
+ const wu = getWuInstance();
443
+ if (wu?.eventBus) {
444
+ wu.eventBus.off(event, callback);
445
+ }
446
+ },
447
+
448
+ // Store
449
+ getState: (path) => {
450
+ const wu = getWuInstance();
451
+ return wu?.store?.get(path) || null;
452
+ },
453
+
454
+ setState: (path, value) => {
455
+ const wu = getWuInstance();
456
+ if (wu?.store) {
457
+ wu.store.set(path, value);
458
+ }
459
+ },
460
+
461
+ onStateChange: (pattern, callback) => {
462
+ const wu = getWuInstance();
463
+ if (wu?.store) {
464
+ const unsubscribe = wu.store.on(pattern, callback);
465
+ subscriptions.push(unsubscribe);
466
+ return unsubscribe;
467
+ }
468
+ return () => {};
469
+ },
470
+
471
+ // Cleanup
472
+ destroy: () => {
473
+ subscriptions.forEach(unsub => unsub());
474
+ subscriptions.length = 0;
475
+ },
476
+
477
+ // Access to raw Wu instance
478
+ get wu() {
479
+ return getWuInstance();
480
+ }
481
+ };
482
+ }
483
+
484
+ /**
485
+ * Crea un componente Angular para cargar microfrontends (para el Shell)
486
+ * Retorna la configuración del componente para ser usado con @Component
487
+ *
488
+ * @example
489
+ * // wu-slot.component.ts
490
+ * import { Component, Input, Output, EventEmitter } from '@angular/core';
491
+ * import { createWuSlotComponent } from 'wu-framework/adapters/angular';
492
+ *
493
+ * const config = createWuSlotComponent();
494
+ *
495
+ * @Component({
496
+ * selector: 'wu-slot',
497
+ * template: config.template,
498
+ * styles: config.styles
499
+ * })
500
+ * export class WuSlotComponent {
501
+ * @Input() name!: string;
502
+ * @Input() url!: string;
503
+ * @Output() load = new EventEmitter();
504
+ * @Output() error = new EventEmitter();
505
+ *
506
+ * // ... implement lifecycle methods from config.methods
507
+ * }
508
+ */
509
+ function createWuSlotComponent() {
510
+ return {
511
+ selector: 'wu-slot',
512
+
513
+ template: `
514
+ <div
515
+ #container
516
+ class="wu-slot"
517
+ [class.wu-slot-loading]="loading"
518
+ [class.wu-slot-error]="error"
519
+ [attr.data-wu-app]="name"
520
+ [attr.data-wu-url]="url"
521
+ style="min-height: 100px; position: relative;">
522
+
523
+ <div *ngIf="error" class="wu-slot-error-message"
524
+ style="padding: 1rem; border: 1px solid #f5c6cb; border-radius: 4px; background: #f8d7da; color: #721c24;">
525
+ <strong>Error loading {{ name }}</strong>
526
+ <p style="margin: 0.5rem 0 0 0;">{{ error }}</p>
527
+ </div>
528
+
529
+ <div *ngIf="loading && !error" class="wu-slot-loading-message"
530
+ style="display: flex; align-items: center; justify-content: center; padding: 2rem; color: #666;">
531
+ {{ fallbackText || 'Loading ' + name + '...' }}
532
+ </div>
533
+ </div>
534
+ `,
535
+
536
+ styles: [`
537
+ .wu-slot {
538
+ width: 100%;
539
+ min-height: 100px;
540
+ }
541
+ `],
542
+
543
+ // Métodos para implementar en el componente
544
+ methods: {
545
+ async ngOnInit() {
546
+ await this.mountMicrofrontend();
547
+ },
548
+
549
+ ngOnDestroy() {
550
+ this.unmountMicrofrontend();
551
+ },
552
+
553
+ async mountMicrofrontend() {
554
+ try {
555
+ this.loading = true;
556
+ this.error = null;
557
+
558
+ const wu = getWuInstance();
559
+ if (!wu) {
560
+ throw new Error('Wu Framework not initialized');
561
+ }
562
+
563
+ // Crear container único
564
+ const containerId = `wu-slot-${this.name}-${Date.now()}`;
565
+ const innerContainer = document.createElement('div');
566
+ innerContainer.id = containerId;
567
+ innerContainer.style.width = '100%';
568
+ innerContainer.style.height = '100%';
569
+
570
+ this.container.nativeElement.innerHTML = '';
571
+ this.container.nativeElement.appendChild(innerContainer);
572
+
573
+ // Crear y montar la app
574
+ const app = wu.app(this.name, {
575
+ url: this.url,
576
+ container: `#${containerId}`,
577
+ autoInit: true
578
+ });
579
+
580
+ this.appInstance = app;
581
+ await app.mount();
582
+
583
+ this.loading = false;
584
+ this.load.emit({ name: this.name, url: this.url });
585
+
586
+ } catch (err) {
587
+ console.error(`[WuSlot] Error loading ${this.name}:`, err);
588
+ this.error = err.message || 'Failed to load microfrontend';
589
+ this.loading = false;
590
+ this.errorEvent.emit(err);
591
+ }
592
+ },
593
+
594
+ async unmountMicrofrontend() {
595
+ if (this.appInstance) {
596
+ try {
597
+ await this.appInstance.unmount();
598
+ } catch (err) {
599
+ console.warn(`[WuSlot] Error unmounting ${this.name}:`, err);
600
+ }
601
+ this.appInstance = null;
602
+ }
603
+ }
604
+ }
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Helper para crear un módulo Angular que exporta WuSlotComponent
610
+ * Útil para shells que quieren usar <wu-slot> directamente
611
+ */
612
+ function getWuSlotModuleConfig() {
613
+ return {
614
+ imports: ['CommonModule'],
615
+ declarations: ['WuSlotComponent'],
616
+ exports: ['WuSlotComponent']
617
+ };
618
+ }
619
+
620
+ // API pública del adapter
621
+ export const wuAngular = {
622
+ register,
623
+ registerStandalone,
624
+ createWuService,
625
+ createWuSlotComponent,
626
+ getWuSlotModuleConfig,
627
+ getWuInstance,
628
+ waitForWu
629
+ };
630
+
631
+ // Named exports para conveniencia
632
+ export {
633
+ register,
634
+ registerStandalone,
635
+ createWuService,
636
+ createWuSlotComponent,
637
+ getWuSlotModuleConfig,
638
+ getWuInstance,
639
+ waitForWu
640
+ };
641
+
642
+ export default wuAngular;