wu-framework 1.1.3 → 1.1.5

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,143 @@
1
+ /**
2
+ * 🔐 ROLLUP PLUGIN: HEX ENCODER
3
+ *
4
+ * Convierte el código JavaScript a formato hexadecimal
5
+ * para máxima ofuscación y protección del código fuente.
6
+ *
7
+ * El código resultante se decodifica y ejecuta en runtime.
8
+ */
9
+
10
+ /**
11
+ * Convierte string a hexadecimal
12
+ * @param {string} str - String a convertir
13
+ * @returns {string} String en hexadecimal
14
+ */
15
+ function stringToHex(str) {
16
+ let hex = '';
17
+ for (let i = 0; i < str.length; i++) {
18
+ const charCode = str.charCodeAt(i);
19
+ hex += charCode.toString(16).padStart(2, '0');
20
+ }
21
+ return hex;
22
+ }
23
+
24
+ /**
25
+ * Genera el loader que decodifica y ejecuta el código hex
26
+ * @param {string} hexCode - Código en hexadecimal
27
+ * @param {boolean} isModule - Si es módulo ES
28
+ * @returns {string} Código JavaScript con loader
29
+ */
30
+ function generateLoader(hexCode, isModule = true) {
31
+ // Loader minificado que decodifica hex y ejecuta
32
+ const loader = `
33
+ (function(){
34
+ var h="${hexCode}";
35
+ var d=function(x){
36
+ for(var s="",i=0;i<x.length;i+=2)
37
+ s+=String.fromCharCode(parseInt(x.substr(i,2),16));
38
+ return s;
39
+ };
40
+ var c=d(h);
41
+ ${isModule ?
42
+ 'var b=new Blob([c],{type:"text/javascript"});var u=URL.createObjectURL(b);import(u).then(function(m){URL.revokeObjectURL(u);Object.assign(window.__wu_exports__=window.__wu_exports__||{},m);});' :
43
+ 'var s=document.createElement("script");s.text=c;document.head.appendChild(s);'
44
+ }
45
+ })();
46
+ `.trim().replace(/\s+/g, ' ');
47
+
48
+ return loader;
49
+ }
50
+
51
+ /**
52
+ * Genera loader asíncrono para módulos ES
53
+ * @param {string} hexCode - Código hexadecimal
54
+ * @returns {string} Código con loader async
55
+ */
56
+ function generateAsyncLoader(hexCode) {
57
+ return `/*!
58
+ * Wu Framework - Hex Encoded Build
59
+ * Decode and execute at runtime
60
+ */
61
+ const __wu_hex__="${hexCode}";
62
+ const __wu_decode__=(h)=>{let s="";for(let i=0;i<h.length;i+=2)s+=String.fromCharCode(parseInt(h.substr(i,2),16));return s;};
63
+ const __wu_code__=__wu_decode__(__wu_hex__);
64
+ const __wu_blob__=new Blob([__wu_code__],{type:"text/javascript"});
65
+ const __wu_url__=URL.createObjectURL(__wu_blob__);
66
+ const __wu_module__=await import(__wu_url__);
67
+ URL.revokeObjectURL(__wu_url__);
68
+ export const WuCore=__wu_module__.WuCore;
69
+ export const WuLoader=__wu_module__.WuLoader;
70
+ export const WuSandbox=__wu_module__.WuSandbox;
71
+ export const WuManifest=__wu_module__.WuManifest;
72
+ export const WuEventBus=__wu_module__.WuEventBus;
73
+ export const WuStore=__wu_module__.WuStore;
74
+ export const WuCache=__wu_module__.WuCache;
75
+ export const WuPlugin=__wu_module__.WuPlugin;
76
+ export const WuHooks=__wu_module__.WuHooks;
77
+ export const WuPerformance=__wu_module__.WuPerformance;
78
+ export const adapters=__wu_module__.adapters;
79
+ export default __wu_module__.default||__wu_module__.WuCore;
80
+ `;
81
+ }
82
+
83
+ /**
84
+ * Plugin de Rollup para codificación hexadecimal
85
+ * @param {Object} options - Opciones del plugin
86
+ * @returns {Object} Plugin de Rollup
87
+ */
88
+ export default function hexEncoder(options = {}) {
89
+ const {
90
+ encodeStrings = true,
91
+ encodeIdentifiers = true,
92
+ addLoader = true,
93
+ asyncLoader = true,
94
+ chunkSize = 0 // 0 = sin chunks, >0 = dividir en chunks
95
+ } = options;
96
+
97
+ return {
98
+ name: 'hex-encoder',
99
+
100
+ renderChunk(code, chunk, outputOptions) {
101
+ console.log(`[HexEncoder] Processing chunk: ${chunk.fileName}`);
102
+
103
+ // Convertir código a hexadecimal
104
+ const hexCode = stringToHex(code);
105
+
106
+ console.log(`[HexEncoder] Original size: ${code.length} bytes`);
107
+ console.log(`[HexEncoder] Hex size: ${hexCode.length} bytes`);
108
+
109
+ if (!addLoader) {
110
+ // Solo devolver el hex sin loader
111
+ return {
112
+ code: `// Wu Framework - Hex Encoded\n// Decode with: hexToString("${hexCode}")\nexport default "${hexCode}";`,
113
+ map: null
114
+ };
115
+ }
116
+
117
+ // Generar código con loader
118
+ let finalCode;
119
+
120
+ if (asyncLoader) {
121
+ finalCode = generateAsyncLoader(hexCode);
122
+ } else {
123
+ finalCode = generateLoader(hexCode, outputOptions.format === 'es');
124
+ }
125
+
126
+ console.log(`[HexEncoder] Final size: ${finalCode.length} bytes`);
127
+
128
+ return {
129
+ code: finalCode,
130
+ map: null
131
+ };
132
+ },
133
+
134
+ generateBundle(outputOptions, bundle) {
135
+ console.log(`[HexEncoder] Build complete!`);
136
+ console.log(`[HexEncoder] Output format: ${outputOptions.format}`);
137
+ console.log(`[HexEncoder] Files generated: ${Object.keys(bundle).join(', ')}`);
138
+ }
139
+ };
140
+ }
141
+
142
+ // Exportar utilidades para uso standalone
143
+ export { stringToHex, generateLoader, generateAsyncLoader };
@@ -387,6 +387,175 @@ async function registerStandalone(appName, RootComponent, options = {}) {
387
387
  }
388
388
  }
389
389
 
390
+ /**
391
+ * Registra un componente Angular Elements (Web Component) como microfrontend
392
+ *
393
+ * @param {string} appName - Nombre único del microfrontend
394
+ * @param {Type<any>} Component - Componente Angular standalone
395
+ * @param {Object} options - Opciones adicionales
396
+ * @param {string} options.elementTag - Tag del custom element (default: basado en appName)
397
+ * @param {ApplicationConfig} options.appConfig - Configuración de la aplicación Angular
398
+ * @param {Function} options.onMount - Callback después de montar
399
+ * @param {Function} options.onUnmount - Callback antes de desmontar
400
+ * @param {boolean} options.standalone - Permitir ejecución standalone (default: true)
401
+ * @param {string} options.standaloneContainer - Selector para modo standalone (default: '#root')
402
+ *
403
+ * @example
404
+ * // Angular Elements (Web Components)
405
+ * import { App } from './app/app';
406
+ * import { appConfig } from './app/app.config';
407
+ *
408
+ * wuAngular.registerElement('mfe-angular', App, {
409
+ * elementTag: 'mfe-angular-content',
410
+ * appConfig
411
+ * });
412
+ */
413
+ async function registerElement(appName, Component, options = {}) {
414
+ const {
415
+ elementTag = `${appName}-element`,
416
+ appConfig = {},
417
+ onMount = null,
418
+ onUnmount = null,
419
+ standalone = true,
420
+ standaloneContainer = '#root'
421
+ } = options;
422
+
423
+ let customElementRegistered = false;
424
+
425
+ // Función para inicializar Angular Elements
426
+ const initializeElement = async () => {
427
+ if (customElementRegistered) return true;
428
+
429
+ try {
430
+ // Import dinámico de Angular
431
+ const [{ createApplication }, { createCustomElement }] = await Promise.all([
432
+ import('@angular/platform-browser'),
433
+ import('@angular/elements')
434
+ ]);
435
+
436
+ // Crear aplicación Angular
437
+ const app = await createApplication(appConfig);
438
+
439
+ // Crear y registrar el custom element
440
+ const CustomElement = createCustomElement(Component, { injector: app.injector });
441
+
442
+ if (!customElements.get(elementTag)) {
443
+ customElements.define(elementTag, CustomElement);
444
+ console.log(`[WuAngular] ✅ Custom element registered: ${elementTag}`);
445
+ }
446
+
447
+ customElementRegistered = true;
448
+
449
+ // Guardar referencia
450
+ adapterState.apps.set(`${appName}:element`, {
451
+ app,
452
+ elementTag,
453
+ CustomElement
454
+ });
455
+
456
+ return true;
457
+ } catch (error) {
458
+ console.error(`[WuAngular] Failed to initialize Angular Element:`, error);
459
+ throw error;
460
+ }
461
+ };
462
+
463
+ // Función de mount
464
+ const mountApp = async (container) => {
465
+ if (!container) {
466
+ console.error(`[WuAngular] Mount failed for ${appName}: container is null`);
467
+ return;
468
+ }
469
+
470
+ try {
471
+ // Asegurar que el elemento está registrado
472
+ await initializeElement();
473
+
474
+ // Crear el elemento custom
475
+ const element = document.createElement(elementTag);
476
+ element.setAttribute('data-wu-angular-element', appName);
477
+
478
+ // Limpiar y agregar al container
479
+ container.innerHTML = '';
480
+ container.appendChild(element);
481
+
482
+ // Guardar referencia del mount
483
+ adapterState.apps.set(appName, {
484
+ element,
485
+ container,
486
+ elementTag
487
+ });
488
+
489
+ console.log(`[WuAngular] ✅ ${appName} (element) mounted successfully`);
490
+
491
+ if (onMount) {
492
+ onMount(container, element);
493
+ }
494
+
495
+ return element;
496
+ } catch (error) {
497
+ console.error(`[WuAngular] Mount error for ${appName}:`, error);
498
+ throw error;
499
+ }
500
+ };
501
+
502
+ // Función de unmount
503
+ const unmountApp = async (container) => {
504
+ const instance = adapterState.apps.get(appName);
505
+
506
+ if (instance) {
507
+ try {
508
+ if (onUnmount) {
509
+ onUnmount(instance.container, instance.element);
510
+ }
511
+
512
+ // Remover elemento del DOM
513
+ if (instance.element && instance.element.parentNode) {
514
+ instance.element.remove();
515
+ }
516
+
517
+ adapterState.apps.delete(appName);
518
+
519
+ console.log(`[WuAngular] ✅ ${appName} (element) unmounted successfully`);
520
+ } catch (error) {
521
+ console.error(`[WuAngular] Unmount error for ${appName}:`, error);
522
+ }
523
+ }
524
+
525
+ if (container) {
526
+ container.innerHTML = '';
527
+ }
528
+ };
529
+
530
+ // Intentar registrar con Wu Framework
531
+ try {
532
+ const wu = await waitForWu(3000);
533
+
534
+ wu.define(appName, {
535
+ mount: mountApp,
536
+ unmount: unmountApp
537
+ });
538
+
539
+ console.log(`[WuAngular] ✅ ${appName} (element) registered with Wu Framework`);
540
+ return true;
541
+
542
+ } catch (error) {
543
+ console.warn(`[WuAngular] Wu Framework not available for ${appName}`);
544
+
545
+ if (standalone) {
546
+ const containerElement = document.querySelector(standaloneContainer);
547
+
548
+ if (containerElement) {
549
+ console.log(`[WuAngular] Running ${appName} in standalone mode`);
550
+ await mountApp(containerElement);
551
+ return true;
552
+ }
553
+ }
554
+
555
+ return false;
556
+ }
557
+ }
558
+
390
559
  /**
391
560
  * Servicio Injectable para usar Wu Framework en Angular
392
561
  *
@@ -621,6 +790,7 @@ function getWuSlotModuleConfig() {
621
790
  export const wuAngular = {
622
791
  register,
623
792
  registerStandalone,
793
+ registerElement,
624
794
  createWuService,
625
795
  createWuSlotComponent,
626
796
  getWuSlotModuleConfig,
@@ -632,6 +802,7 @@ export const wuAngular = {
632
802
  export {
633
803
  register,
634
804
  registerStandalone,
805
+ registerElement,
635
806
  createWuService,
636
807
  createWuSlotComponent,
637
808
  getWuSlotModuleConfig,
@@ -169,6 +169,17 @@ async function register(appName, RootComponent, options = {}) {
169
169
  }
170
170
 
171
171
  try {
172
+ // Detectar si el container está dentro de un Shadow DOM
173
+ let shadowRoot = null;
174
+ let element = container;
175
+ while (element && element !== document.body) {
176
+ if (element.getRootNode && element.getRootNode() instanceof ShadowRoot) {
177
+ shadowRoot = element.getRootNode();
178
+ break;
179
+ }
180
+ element = element.parentElement || element.host;
181
+ }
182
+
172
183
  // Crear la aplicación Vue
173
184
  const app = createApp(RootComponent, props);
174
185
 
@@ -184,6 +195,36 @@ async function register(appName, RootComponent, options = {}) {
184
195
  // Montar
185
196
  app.mount(container);
186
197
 
198
+ // Si está en Shadow DOM, copiar estilos de Vue al Shadow DOM
199
+ if (shadowRoot) {
200
+ // Esperar un poco para que Vue inyecte los estilos en el head
201
+ setTimeout(() => {
202
+ const vueStyles = document.querySelectorAll('style[data-vite-dev-id*="/' + appName + '/"], style[data-vite-dev-id*="\\' + appName + '\\"]');
203
+ vueStyles.forEach(style => {
204
+ // Verificar que no esté ya en el Shadow DOM
205
+ const viteId = style.getAttribute('data-vite-dev-id');
206
+ if (viteId && !shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`)) {
207
+ const clonedStyle = style.cloneNode(true);
208
+ shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
209
+ console.log(`[WuVue] ✅ Injected style into Shadow DOM: ${viteId}`);
210
+ }
211
+ });
212
+
213
+ // También copiar estilos que contengan rutas del app en el viteId
214
+ const allStyles = document.querySelectorAll('style[data-vite-dev-id]');
215
+ allStyles.forEach(style => {
216
+ const viteId = style.getAttribute('data-vite-dev-id');
217
+ if (viteId && (viteId.includes(`/${appName}/`) || viteId.includes(`\\${appName}\\`))) {
218
+ if (!shadowRoot.querySelector(`style[data-vite-dev-id="${viteId}"]`)) {
219
+ const clonedStyle = style.cloneNode(true);
220
+ shadowRoot.insertBefore(clonedStyle, shadowRoot.firstChild);
221
+ console.log(`[WuVue] ✅ Injected app style into Shadow DOM: ${viteId}`);
222
+ }
223
+ }
224
+ });
225
+ }, 100);
226
+ }
227
+
187
228
  // Guardar referencia
188
229
  adapterState.apps.set(appName, { app, container });
189
230
 
@@ -587,25 +587,12 @@ export class WuCore {
587
587
  // 🏊 Acquire sandbox from pool (if configured)
588
588
  const poolSandbox = this.sandboxPool.acquire(appName);
589
589
 
590
- // 🎨 Configure style mode from manifest before creating sandbox
591
- const manifest = app.manifest;
592
- console.log(`[Wu] 🔍 DEBUG: Manifest for ${appName}:`, JSON.stringify(manifest, null, 2));
593
-
594
- // Default to 'isolated' if no styleMode specified
595
- const styleMode = manifest?.styleMode || 'isolated';
596
- if (this.sandbox.styleBridge) {
597
- // Pass app URL so StyleBridge can identify MFE's own styles
598
- this.sandbox.styleBridge.setAppStyleMode(appName, styleMode, app.url);
599
- console.log(`[Wu] 🎨 Style mode for ${appName}: ${styleMode}`);
600
-
601
- // 📁 If folder is specified in manifest, register it for style detection
602
- // This is needed when folder name differs from app name (e.g., folder="container", name="content")
603
- const folderName = manifest?.folder || appName;
604
- this.sandbox.styleBridge.setAppFolder(appName, `/${folderName}/`);
605
- }
606
-
607
- // 🛡️ Quantum sandbox creation
608
- const sandbox = this.sandbox.create(appName, container);
590
+ // 🛡️ Quantum sandbox creation - pasar manifest con styleMode y URL de la app
591
+ const sandbox = this.sandbox.create(appName, container, {
592
+ manifest: app.manifest,
593
+ styleMode: app.manifest?.styleMode,
594
+ appUrl: app.url // Pasar URL de la app para filtrar estilos de apps fully-isolated
595
+ });
609
596
 
610
597
  // 🪝 Execute afterLoad hooks
611
598
  await this.hooks.execute('afterLoad', { appName, containerSelector, sandbox });
@@ -620,14 +607,6 @@ export class WuCore {
620
607
  if (!lifecycle) {
621
608
  throw new Error(`App ${appName} did not register with wu.define()`);
622
609
  }
623
-
624
- // 🎨 RE-INJECT STYLES: After remote module loads, Vite may have injected new styles
625
- // We need to re-scan and inject any new styles into the Shadow DOM
626
- if (sandbox.shadowRoot && this.sandbox.styleBridge) {
627
- console.log(`[Wu] 🎨 Re-injecting styles after module load for ${appName}...`);
628
- // Pass app URL so StyleBridge can identify MFE's own styles
629
- await this.sandbox.styleBridge.injectStylesIntoShadow(sandbox.shadowRoot, appName, app.url);
630
- }
631
610
  }
632
611
 
633
612
  // 🪝 Execute beforeMount hooks
@@ -1029,12 +1008,15 @@ export class WuCore {
1029
1008
  const content = await response.text();
1030
1009
 
1031
1010
  // 🚫 DETECT HTML FALLBACK: Check if server returned HTML instead of JS
1011
+ // Only check if content STARTS with HTML markers (trimmed), not if it contains them anywhere
1012
+ // This avoids false positives for Angular/React bundles that contain template strings
1013
+ const trimmedContent = content.trim().toLowerCase();
1032
1014
  const isHtmlFallback =
1033
- content.includes('<!doctype') ||
1034
- content.includes('<html') ||
1035
- content.includes('<head>') ||
1036
- content.includes('<body>') ||
1037
- (content.includes('<') && content.includes('>') && content.length > 200);
1015
+ trimmedContent.startsWith('<!doctype') ||
1016
+ trimmedContent.startsWith('<html') ||
1017
+ trimmedContent.startsWith('<head') ||
1018
+ trimmedContent.startsWith('<body') ||
1019
+ trimmedContent.startsWith('<!-');
1038
1020
 
1039
1021
  if (isHtmlFallback) {
1040
1022
  console.log(`[Wu] ❌ Path validation failed - Server returned HTML fallback page: ${url}`);
@@ -43,19 +43,14 @@ export class WuManifest {
43
43
  defineSchema() {
44
44
  this.schemas.set('wu.json', {
45
45
  required: ['name', 'entry'],
46
- optional: ['wu', 'styleMode'],
46
+ optional: ['wu'],
47
47
  wu: {
48
48
  optional: ['exports', 'imports', 'routes', 'permissions'],
49
49
  exports: 'object',
50
50
  imports: 'array',
51
51
  routes: 'array',
52
52
  permissions: 'array'
53
- },
54
- // 🎨 styleMode: 'isolated' | 'shared' | 'auto'
55
- // - isolated: No comparte estilos del padre (default, máximo aislamiento)
56
- // - shared: Comparte todos los estilos del padre
57
- // - auto: Solo comparte estilos de librerías específicas (element-plus, etc.)
58
- styleMode: 'string'
53
+ }
59
54
  });
60
55
  }
61
56
 
@@ -317,11 +312,7 @@ export class WuManifest {
317
312
  normalize(manifest) {
318
313
  const normalized = {
319
314
  name: manifest.name.trim(),
320
- // 📁 folder: carpeta física del MFE (si difiere del name)
321
- folder: manifest.folder?.trim() || manifest.name.trim(),
322
315
  entry: this.normalizeEntry(manifest.entry),
323
- // 🎨 styleMode: 'isolated' (default) | 'shared' | 'auto'
324
- styleMode: this.normalizeStyleMode(manifest.styleMode),
325
316
  wu: {
326
317
  exports: manifest.wu?.exports || {},
327
318
  imports: manifest.wu?.imports || [],
@@ -330,6 +321,17 @@ export class WuManifest {
330
321
  }
331
322
  };
332
323
 
324
+ // Preservar campos opcionales del manifest (styleMode, version, folder, etc.)
325
+ if (manifest.styleMode) {
326
+ normalized.styleMode = manifest.styleMode;
327
+ }
328
+ if (manifest.version) {
329
+ normalized.version = manifest.version;
330
+ }
331
+ if (manifest.folder) {
332
+ normalized.folder = manifest.folder;
333
+ }
334
+
333
335
  // Normalizar exports
334
336
  if (normalized.wu.exports) {
335
337
  const normalizedExports = {};
@@ -351,26 +353,6 @@ export class WuManifest {
351
353
  return normalized;
352
354
  }
353
355
 
354
- /**
355
- * 🎨 Normalizar styleMode
356
- * @param {string} styleMode - Modo de estilos
357
- * @returns {'isolated' | 'fully-isolated' | 'shared' | 'auto'} styleMode normalizado
358
- *
359
- * Modos disponibles:
360
- * - 'isolated': MFE solo tiene sus propios estilos (CSS variables SÍ se heredan)
361
- * - 'fully-isolated': Aislamiento TOTAL - resetea CSS variables del padre (ÚNICO en wu-framework!)
362
- * - 'shared': Comparte todos los estilos del padre
363
- * - 'auto': Comparte solo librerías específicas (element-plus, etc.)
364
- */
365
- normalizeStyleMode(styleMode) {
366
- const validModes = ['isolated', 'fully-isolated', 'shared', 'auto'];
367
- if (styleMode && validModes.includes(styleMode)) {
368
- return styleMode;
369
- }
370
- // Default: isolated (máximo aislamiento sin romper CSS variables)
371
- return 'isolated';
372
- }
373
-
374
356
  /**
375
357
  * Normalizar entry path
376
358
  * @param {string} entry - Entry path