wu-framework 1.1.14 → 1.1.16

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.
Files changed (90) hide show
  1. package/LICENSE +39 -39
  2. package/README.md +408 -408
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15151 -15151
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js.map +1 -1
  7. package/dist/wu-framework.umd.js.map +1 -1
  8. package/integrations/astro/README.md +127 -127
  9. package/integrations/astro/WuApp.astro +63 -63
  10. package/integrations/astro/WuShell.astro +39 -39
  11. package/integrations/astro/index.js +68 -68
  12. package/integrations/astro/package.json +38 -38
  13. package/integrations/astro/types.d.ts +53 -53
  14. package/package.json +161 -161
  15. package/src/adapters/angular/ai.js +30 -30
  16. package/src/adapters/angular/index.d.ts +154 -154
  17. package/src/adapters/angular/index.js +932 -932
  18. package/src/adapters/angular.d.ts +3 -3
  19. package/src/adapters/angular.js +3 -3
  20. package/src/adapters/index.js +168 -168
  21. package/src/adapters/lit/ai.js +20 -20
  22. package/src/adapters/lit/index.d.ts +120 -120
  23. package/src/adapters/lit/index.js +721 -721
  24. package/src/adapters/lit.d.ts +3 -3
  25. package/src/adapters/lit.js +3 -3
  26. package/src/adapters/preact/ai.js +33 -33
  27. package/src/adapters/preact/index.d.ts +108 -108
  28. package/src/adapters/preact/index.js +661 -661
  29. package/src/adapters/preact.d.ts +3 -3
  30. package/src/adapters/preact.js +3 -3
  31. package/src/adapters/react/index.js +48 -54
  32. package/src/adapters/react.d.ts +3 -3
  33. package/src/adapters/react.js +3 -3
  34. package/src/adapters/shared.js +64 -64
  35. package/src/adapters/solid/ai.js +32 -32
  36. package/src/adapters/solid/index.d.ts +101 -101
  37. package/src/adapters/solid/index.js +586 -586
  38. package/src/adapters/solid.d.ts +3 -3
  39. package/src/adapters/solid.js +3 -3
  40. package/src/adapters/svelte/ai.js +31 -31
  41. package/src/adapters/svelte/index.d.ts +166 -166
  42. package/src/adapters/svelte/index.js +798 -798
  43. package/src/adapters/svelte.d.ts +3 -3
  44. package/src/adapters/svelte.js +3 -3
  45. package/src/adapters/vanilla/ai.js +30 -30
  46. package/src/adapters/vanilla/index.d.ts +179 -179
  47. package/src/adapters/vanilla/index.js +785 -785
  48. package/src/adapters/vanilla.d.ts +3 -3
  49. package/src/adapters/vanilla.js +3 -3
  50. package/src/adapters/vue/ai.js +52 -52
  51. package/src/adapters/vue/index.d.ts +299 -299
  52. package/src/adapters/vue/index.js +610 -610
  53. package/src/adapters/vue.d.ts +3 -3
  54. package/src/adapters/vue.js +3 -3
  55. package/src/ai/wu-ai-actions.js +261 -261
  56. package/src/ai/wu-ai-agent.js +546 -546
  57. package/src/ai/wu-ai-browser-primitives.js +354 -354
  58. package/src/ai/wu-ai-browser.js +380 -380
  59. package/src/ai/wu-ai-context.js +332 -332
  60. package/src/ai/wu-ai-conversation.js +613 -613
  61. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  62. package/src/ai/wu-ai-permissions.js +381 -381
  63. package/src/ai/wu-ai-provider.js +700 -700
  64. package/src/ai/wu-ai-schema.js +225 -225
  65. package/src/ai/wu-ai-triggers.js +396 -396
  66. package/src/ai/wu-ai.js +804 -804
  67. package/src/core/wu-app.js +236 -236
  68. package/src/core/wu-cache.js +477 -477
  69. package/src/core/wu-core.js +1398 -1398
  70. package/src/core/wu-error-boundary.js +382 -382
  71. package/src/core/wu-event-bus.js +348 -348
  72. package/src/core/wu-hooks.js +350 -350
  73. package/src/core/wu-html-parser.js +190 -190
  74. package/src/core/wu-iframe-sandbox.js +328 -328
  75. package/src/core/wu-loader.js +272 -272
  76. package/src/core/wu-logger.js +134 -134
  77. package/src/core/wu-manifest.js +509 -509
  78. package/src/core/wu-mcp-bridge.js +432 -432
  79. package/src/core/wu-overrides.js +510 -510
  80. package/src/core/wu-performance.js +228 -228
  81. package/src/core/wu-plugin.js +348 -348
  82. package/src/core/wu-prefetch.js +414 -414
  83. package/src/core/wu-proxy-sandbox.js +476 -476
  84. package/src/core/wu-sandbox.js +779 -779
  85. package/src/core/wu-script-executor.js +113 -113
  86. package/src/core/wu-snapshot-sandbox.js +227 -227
  87. package/src/core/wu-strategies.js +256 -256
  88. package/src/core/wu-style-bridge.js +477 -477
  89. package/src/index.js +224 -224
  90. package/src/utils/dependency-resolver.js +327 -327
@@ -1,477 +1,477 @@
1
- /**
2
- * 🎨 WU-STYLE-BRIDGE: SHADOW DOM STYLE SHARING SYSTEM
3
- *
4
- * Comparte automáticamente estilos de node_modules entre padre e hijos Shadow DOM
5
- * Soluciona el problema de aislamiento CSS en microfrontends
6
- *
7
- * MODOS DE INYECCIÓN DE ESTILOS:
8
- * ================================
9
- *
10
- * 1. "shared" (default):
11
- * - Inyecta TODOS los estilos del documento padre en el Shadow DOM
12
- * - Incluye: librerías (Element Plus, Vue Flow), estilos globales, etc.
13
- * - Ideal para: Apps que necesitan compartir un design system común
14
- * - Riesgo de colisiones: ALTO
15
- *
16
- * 2. "isolated":
17
- * - NO inyecta estilos externos
18
- * - Usa el encapsulamiento NATIVO del Shadow DOM
19
- * - La app debe incluir sus propios estilos (CSS-in-JS, scoped styles, etc.)
20
- * - Ideal para: Apps con estilos completamente independientes
21
- * - Riesgo de colisiones: NINGUNO
22
- *
23
- * 3. "fully-isolated":
24
- * - Inyecta SOLO los estilos propios de la micro-app específica
25
- * - Detecta estilos por patrón: packages/appName/src/
26
- * - Usa MutationObserver para HMR de Vite
27
- * - Ideal para: Apps que necesitan sus estilos pero no los globales
28
- * - Riesgo de colisiones: NINGUNO
29
- */
30
-
31
- import { logger } from './wu-logger.js';
32
-
33
- export class WuStyleBridge {
34
- constructor() {
35
- this.styleObserver = null;
36
- this.fullyIsolatedApps = new Map(); // Mapa de appName -> appUrl para apps con fully-isolated
37
- this.config = {
38
- // Librerías que se deben compartir automáticamente
39
- autoShareLibraries: [
40
- 'element-plus',
41
- 'vue-flow',
42
- '@vue-flow',
43
- 'vueuse',
44
- '@vueuse',
45
- 'normalize.css',
46
- 'reset.css'
47
- ],
48
- // Patrones de URLs a compartir
49
- sharePatterns: [
50
- /\/node_modules\//,
51
- /\/@vite\/client/,
52
- /\/dist\/index\.css$/,
53
- /\/dist\/style\.css$/
54
- ],
55
- // Modo de compartición
56
- mode: 'auto', // 'auto' | 'manual' | 'all'
57
- // Caché de estilos
58
- cacheEnabled: true
59
- };
60
-
61
- logger.debug('[WuStyleBridge] 🎨 Style sharing system initialized');
62
- }
63
-
64
- /**
65
- * 🛡️ REGISTRAR APP FULLY-ISOLATED: Registra una app con fully-isolated para filtrar sus estilos
66
- * @param {string} appName - Nombre de la app
67
- * @param {string} appUrl - URL base de la app
68
- */
69
- registerFullyIsolatedApp(appName, appUrl) {
70
- this.fullyIsolatedApps.set(appName, appUrl);
71
- logger.debug(`[WuStyleBridge] 🛡️ Registered fully-isolated app: ${appName} (${appUrl})`);
72
- }
73
-
74
- /**
75
- * 🔍 VERIFICAR SI ESTILO ES DE APP FULLY-ISOLATED: Verifica si un estilo proviene de una app con fully-isolated
76
- * @param {string|Object|HTMLElement} styleUrlOrElement - URL del estilo, objeto de estilo, o elemento DOM
77
- * @returns {boolean}
78
- */
79
- isStyleFromFullyIsolatedApp(styleUrlOrElement) {
80
- let url = '';
81
-
82
- // Si es un string, usar directamente
83
- if (typeof styleUrlOrElement === 'string') {
84
- url = styleUrlOrElement;
85
- }
86
- // Si es un elemento DOM (HTMLElement) - verificar si tiene getAttribute (método común de elementos DOM)
87
- else if (styleUrlOrElement && typeof styleUrlOrElement.getAttribute === 'function') {
88
- // Obtener data-vite-dev-id o href del elemento
89
- url = styleUrlOrElement.getAttribute('data-vite-dev-id') || styleUrlOrElement.href || '';
90
- }
91
- // Si es un objeto con propiedades
92
- else if (styleUrlOrElement) {
93
- if (styleUrlOrElement.href) {
94
- url = styleUrlOrElement.href;
95
- } else if (styleUrlOrElement.viteId) {
96
- url = styleUrlOrElement.viteId;
97
- } else if (styleUrlOrElement.element) {
98
- if (typeof styleUrlOrElement.element.getAttribute === 'function') {
99
- url = styleUrlOrElement.element.getAttribute('data-vite-dev-id') || styleUrlOrElement.element.href || '';
100
- } else if (styleUrlOrElement.element.href) {
101
- url = styleUrlOrElement.element.href;
102
- }
103
- }
104
- }
105
-
106
- if (!url || url.trim() === '') return false;
107
-
108
- // Normalizar la URL para comparación (convertir backslashes a forward slashes)
109
- const normalizedUrl = url.replace(/\\/g, '/').toLowerCase();
110
-
111
- // Verificar si la URL pertenece a alguna app con fully-isolated
112
- for (const [appName, appUrl] of this.fullyIsolatedApps.entries()) {
113
- const normalizedAppUrl = appUrl.replace(/\\/g, '/').toLowerCase();
114
- const normalizedAppName = appName.toLowerCase();
115
-
116
- // Verificar si la URL contiene la URL base de la app (ej: http://localhost:4001)
117
- if (normalizedAppUrl && normalizedUrl.includes(normalizedAppUrl)) {
118
- return true;
119
- }
120
-
121
- // Verificar si la URL contiene rutas del app en el sistema de archivos
122
- // Ej: C:/Users/.../header/src/... o /header/src/...
123
- // Patrón: cualquier ruta que contenga /header/ o \header\
124
- const appPathPattern = new RegExp(`[/\\\\]${normalizedAppName}[/\\\\]`, 'i');
125
- if (appPathPattern.test(normalizedUrl)) {
126
- return true;
127
- }
128
- }
129
-
130
- return false;
131
- }
132
-
133
- /**
134
- * 🔍 DETECTAR ESTILOS: Escanea todos los estilos del documento
135
- * @returns {Array} Lista de estilos detectados (filtrados para excluir apps con fully-isolated)
136
- */
137
- detectDocumentStyles() {
138
- const styles = [];
139
-
140
- // 1. Detectar TODOS los <link> tags de CSS
141
- const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
142
- linkTags.forEach((link) => {
143
- // Filtrar estilos de apps con fully-isolated
144
- if (this.isStyleFromFullyIsolatedApp(link)) {
145
- return;
146
- }
147
-
148
- styles.push({
149
- type: 'link',
150
- href: link.href,
151
- element: link,
152
- library: this.extractLibraryName(link.href)
153
- });
154
- });
155
-
156
- // 2. Detectar TODOS los <style> tags (incluyendo Vue scoped styles)
157
- const styleTags = document.querySelectorAll('style');
158
- styleTags.forEach((style, index) => {
159
- // Excluir solo estilos ya compartidos por wu-framework
160
- if (style.getAttribute('data-wu-shared') === 'true') {
161
- return;
162
- }
163
-
164
- const viteId = style.getAttribute('data-vite-dev-id');
165
- const content = style.textContent;
166
-
167
- // Filtrar estilos de apps con fully-isolated (después de obtener viteId para mejor detección)
168
- if (this.isStyleFromFullyIsolatedApp(style) || (viteId && this.isStyleFromFullyIsolatedApp(viteId))) {
169
- logger.debug(`[WuStyleBridge] 🛡️ Filtered out style from fully-isolated app: ${viteId || 'unknown'}`);
170
- return;
171
- }
172
-
173
- // Incluir todos los estilos con contenido
174
- if (content && content.trim().length > 0) {
175
- styles.push({
176
- type: 'inline',
177
- content,
178
- element: style,
179
- viteId,
180
- library: this.extractLibraryName(viteId || ''),
181
- index
182
- });
183
- }
184
- });
185
-
186
- // 3. Detectar Constructable Stylesheets (si están disponibles)
187
- if (document.adoptedStyleSheets && document.adoptedStyleSheets.length > 0) {
188
- document.adoptedStyleSheets.forEach((sheet, index) => {
189
- styles.push({
190
- type: 'adoptedStyleSheet',
191
- sheet,
192
- index
193
- });
194
- });
195
- }
196
-
197
- logger.debug(`[WuStyleBridge] 🔍 Detected ${styles.length} shareable styles`);
198
- return styles;
199
- }
200
-
201
- /**
202
- * 🎯 VERIFICAR SI SE DEBE COMPARTIR: Filtra estilos según configuración
203
- * @param {string} urlOrId - URL o ID del estilo
204
- * @returns {boolean}
205
- */
206
- shouldShareStyle(urlOrId) {
207
- if (!urlOrId) return false;
208
-
209
- // Modo 'all' - compartir todo
210
- if (this.config.mode === 'all') return true;
211
-
212
- // Verificar patrones configurados
213
- for (const pattern of this.config.sharePatterns) {
214
- if (pattern.test(urlOrId)) return true;
215
- }
216
-
217
- // Verificar librerías específicas
218
- for (const lib of this.config.autoShareLibraries) {
219
- if (urlOrId.includes(lib)) return true;
220
- }
221
-
222
- return false;
223
- }
224
-
225
- /**
226
- * 📦 EXTRAER NOMBRE DE LIBRERÍA: Obtiene el nombre de la librería desde la URL
227
- * @param {string} url - URL del estilo
228
- * @returns {string|null}
229
- */
230
- extractLibraryName(url) {
231
- if (!url) return null;
232
-
233
- // Extraer de node_modules
234
- const nodeModulesMatch = url.match(/\/node_modules\/(@?[^/]+\/[^/]+|@?[^/]+)/);
235
- if (nodeModulesMatch) return nodeModulesMatch[1];
236
-
237
- // Extraer de vite dev id
238
- const viteMatch = url.match(/\/node_modules\/(.+?)\/.*?\.css/);
239
- if (viteMatch) return viteMatch[1];
240
-
241
- return null;
242
- }
243
-
244
- /**
245
- * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM
246
- * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
247
- * @param {string} appName - Nombre de la app
248
- * @param {string} styleMode - Modo de estilos: 'shared', 'isolated', 'fully-isolated'
249
- * @returns {Promise<number>}
250
- */
251
- async injectStylesIntoShadow(shadowRoot, appName, styleMode) {
252
- if (!shadowRoot) {
253
- logger.warn('[WuStyleBridge] ⚠️ No shadow root provided');
254
- return 0;
255
- }
256
-
257
- // 🛡️ MODO FULLY-ISOLATED: No inyectar ningún estilo compartido
258
- // Los estilos propios se manejan en wu-sandbox.js con injectOwnStylesToShadow
259
- if (styleMode === 'fully-isolated') {
260
- logger.debug(`[WuStyleBridge] 🛡️ Style mode "fully-isolated" for ${appName}, skipping shared style injection`);
261
- return 0;
262
- }
263
-
264
- // 🔒 MODO ISOLATED: No inyectar estilos externos - usar encapsulamiento nativo de Shadow DOM
265
- // La app debe manejar sus propios estilos (CSS-in-JS, scoped styles, imports directos)
266
- if (styleMode === 'isolated') {
267
- logger.debug(`[WuStyleBridge] 🔒 Style mode "isolated" for ${appName}, using native Shadow DOM encapsulation (no external styles)`);
268
- return 0;
269
- }
270
-
271
- // 🌐 MODO SHARED (default): Inyectar todos los estilos compartidos del documento
272
- logger.debug(`[WuStyleBridge] 🌐 Style mode "shared" for ${appName}, injecting all shared styles...`);
273
-
274
- // Detectar estilos del documento
275
- const styles = this.detectDocumentStyles();
276
- let injectedCount = 0;
277
-
278
- // Inyectar cada estilo
279
- for (const style of styles) {
280
- try {
281
- switch (style.type) {
282
- case 'link':
283
- await this.injectLinkStyle(shadowRoot, style);
284
- injectedCount++;
285
- break;
286
-
287
- case 'inline':
288
- this.injectInlineStyle(shadowRoot, style);
289
- injectedCount++;
290
- break;
291
-
292
- case 'adoptedStyleSheet':
293
- this.injectAdoptedStyleSheet(shadowRoot, style);
294
- injectedCount++;
295
- break;
296
- }
297
- } catch (error) {
298
- logger.warn(`[WuStyleBridge] ⚠️ Failed to inject style:`, error);
299
- }
300
- }
301
-
302
- logger.debug(`[WuStyleBridge] ✅ Injected ${injectedCount} shared styles into ${appName}`);
303
- return injectedCount;
304
- }
305
-
306
- /**
307
- * 🔗 INYECTAR LINK STYLE: Clona <link> tag al Shadow DOM
308
- * @param {ShadowRoot} shadowRoot
309
- * @param {Object} style
310
- */
311
- async injectLinkStyle(shadowRoot, style) {
312
- // Verificar si ya existe
313
- const existing = shadowRoot.querySelector(`link[href="${style.href}"]`);
314
- if (existing) {
315
- logger.debug(`[WuStyleBridge] ⏭️ Style already exists: ${style.library || style.href}`);
316
- return;
317
- }
318
-
319
- // Clonar link tag
320
- const link = document.createElement('link');
321
- link.rel = 'stylesheet';
322
- link.href = style.href;
323
- link.setAttribute('data-wu-shared', 'true');
324
- link.setAttribute('data-wu-library', style.library || 'unknown');
325
-
326
- // Insertar al principio del shadow root (antes de otros estilos)
327
- shadowRoot.insertBefore(link, shadowRoot.firstChild);
328
-
329
- logger.debug(`[WuStyleBridge] 🔗 Injected link: ${style.library || style.href}`);
330
- }
331
-
332
- /**
333
- * 📝 INYECTAR INLINE STYLE: Clona <style> tag al Shadow DOM
334
- * @param {ShadowRoot} shadowRoot
335
- * @param {Object} style
336
- */
337
- injectInlineStyle(shadowRoot, style) {
338
- // Verificar si ya existe
339
- const viteId = style.viteId;
340
- if (viteId) {
341
- const existing = shadowRoot.querySelector(`style[data-wu-vite-id="${viteId}"]`);
342
- if (existing) {
343
- logger.debug(`[WuStyleBridge] ⏭️ Inline style already exists: ${viteId}`);
344
- return;
345
- }
346
- }
347
-
348
- // Crear nuevo style tag
349
- const styleTag = document.createElement('style');
350
- styleTag.textContent = style.content;
351
- styleTag.setAttribute('data-wu-shared', 'true');
352
- styleTag.setAttribute('data-wu-library', style.library || 'unknown');
353
- if (viteId) {
354
- styleTag.setAttribute('data-wu-vite-id', viteId);
355
- }
356
-
357
- // Insertar al principio del shadow root
358
- shadowRoot.insertBefore(styleTag, shadowRoot.firstChild);
359
-
360
- logger.debug(`[WuStyleBridge] 📝 Injected inline style: ${style.library || viteId}`);
361
- }
362
-
363
- /**
364
- * 📋 INYECTAR ADOPTED STYLESHEET: Comparte stylesheet constructable
365
- * @param {ShadowRoot} shadowRoot
366
- * @param {Object} style
367
- */
368
- injectAdoptedStyleSheet(shadowRoot, style) {
369
- try {
370
- // Agregar stylesheet al array de adopted stylesheets
371
- if (!shadowRoot.adoptedStyleSheets) {
372
- shadowRoot.adoptedStyleSheets = [];
373
- }
374
-
375
- // Verificar si ya existe
376
- if (shadowRoot.adoptedStyleSheets.includes(style.sheet)) {
377
- logger.debug(`[WuStyleBridge] ⏭️ Adopted stylesheet already exists`);
378
- return;
379
- }
380
-
381
- shadowRoot.adoptedStyleSheets = [
382
- ...shadowRoot.adoptedStyleSheets,
383
- style.sheet
384
- ];
385
-
386
- logger.debug(`[WuStyleBridge] 📋 Injected adopted stylesheet`);
387
- } catch (error) {
388
- logger.warn(`[WuStyleBridge] ⚠️ Failed to inject adopted stylesheet:`, error);
389
- }
390
- }
391
-
392
- /**
393
- * 🔄 OBSERVAR CAMBIOS: Monitorea nuevos estilos en el documento
394
- * @param {Function} callback - Callback cuando se detectan cambios
395
- */
396
- observeStyleChanges(callback) {
397
- // Limpiar observer anterior si existe
398
- if (this.styleObserver) {
399
- this.styleObserver.disconnect();
400
- }
401
-
402
- // Crear MutationObserver para detectar nuevos estilos
403
- this.styleObserver = new MutationObserver((mutations) => {
404
- let hasStyleChanges = false;
405
-
406
- for (const mutation of mutations) {
407
- if (mutation.type === 'childList') {
408
- // Verificar si se agregaron <link> o <style> tags
409
- const addedNodes = Array.from(mutation.addedNodes);
410
- const hasNewStyles = addedNodes.some(node =>
411
- node.tagName === 'LINK' || node.tagName === 'STYLE'
412
- );
413
-
414
- if (hasNewStyles) {
415
- hasStyleChanges = true;
416
- break;
417
- }
418
- }
419
- }
420
-
421
- if (hasStyleChanges && callback) {
422
- logger.debug('[WuStyleBridge] 🔄 Style changes detected');
423
- callback();
424
- }
425
- });
426
-
427
- // Observar <head> para cambios en estilos
428
- this.styleObserver.observe(document.head, {
429
- childList: true,
430
- subtree: true
431
- });
432
-
433
- logger.debug('[WuStyleBridge] 👀 Observing style changes');
434
- }
435
-
436
- /**
437
- * ⚙️ CONFIGURAR: Actualiza la configuración
438
- * @param {Object} config - Nueva configuración
439
- */
440
- configure(config) {
441
- this.config = {
442
- ...this.config,
443
- ...config
444
- };
445
-
446
- logger.debug('[WuStyleBridge] ⚙️ Configuration updated:', this.config);
447
- }
448
-
449
- /**
450
- * 🧹 LIMPIAR: Detiene la observación
451
- */
452
- cleanup() {
453
- if (this.styleObserver) {
454
- this.styleObserver.disconnect();
455
- this.styleObserver = null;
456
- }
457
-
458
- logger.debug('[WuStyleBridge] 🧹 StyleBridge cleaned up');
459
- }
460
-
461
- /**
462
- * 📊 OBTENER ESTADÍSTICAS: Información sobre estilos compartidos
463
- * @returns {Object}
464
- */
465
- getStats() {
466
- const styles = this.detectDocumentStyles();
467
-
468
- return {
469
- totalStyles: styles.length,
470
- linkStyles: styles.filter(s => s.type === 'link').length,
471
- inlineStyles: styles.filter(s => s.type === 'inline').length,
472
- adoptedStyleSheets: styles.filter(s => s.type === 'adoptedStyleSheet').length,
473
- libraries: [...new Set(styles.map(s => s.library).filter(Boolean))],
474
- config: this.config
475
- };
476
- }
477
- }
1
+ /**
2
+ * 🎨 WU-STYLE-BRIDGE: SHADOW DOM STYLE SHARING SYSTEM
3
+ *
4
+ * Comparte automáticamente estilos de node_modules entre padre e hijos Shadow DOM
5
+ * Soluciona el problema de aislamiento CSS en microfrontends
6
+ *
7
+ * MODOS DE INYECCIÓN DE ESTILOS:
8
+ * ================================
9
+ *
10
+ * 1. "shared" (default):
11
+ * - Inyecta TODOS los estilos del documento padre en el Shadow DOM
12
+ * - Incluye: librerías (Element Plus, Vue Flow), estilos globales, etc.
13
+ * - Ideal para: Apps que necesitan compartir un design system común
14
+ * - Riesgo de colisiones: ALTO
15
+ *
16
+ * 2. "isolated":
17
+ * - NO inyecta estilos externos
18
+ * - Usa el encapsulamiento NATIVO del Shadow DOM
19
+ * - La app debe incluir sus propios estilos (CSS-in-JS, scoped styles, etc.)
20
+ * - Ideal para: Apps con estilos completamente independientes
21
+ * - Riesgo de colisiones: NINGUNO
22
+ *
23
+ * 3. "fully-isolated":
24
+ * - Inyecta SOLO los estilos propios de la micro-app específica
25
+ * - Detecta estilos por patrón: packages/appName/src/
26
+ * - Usa MutationObserver para HMR de Vite
27
+ * - Ideal para: Apps que necesitan sus estilos pero no los globales
28
+ * - Riesgo de colisiones: NINGUNO
29
+ */
30
+
31
+ import { logger } from './wu-logger.js';
32
+
33
+ export class WuStyleBridge {
34
+ constructor() {
35
+ this.styleObserver = null;
36
+ this.fullyIsolatedApps = new Map(); // Mapa de appName -> appUrl para apps con fully-isolated
37
+ this.config = {
38
+ // Librerías que se deben compartir automáticamente
39
+ autoShareLibraries: [
40
+ 'element-plus',
41
+ 'vue-flow',
42
+ '@vue-flow',
43
+ 'vueuse',
44
+ '@vueuse',
45
+ 'normalize.css',
46
+ 'reset.css'
47
+ ],
48
+ // Patrones de URLs a compartir
49
+ sharePatterns: [
50
+ /\/node_modules\//,
51
+ /\/@vite\/client/,
52
+ /\/dist\/index\.css$/,
53
+ /\/dist\/style\.css$/
54
+ ],
55
+ // Modo de compartición
56
+ mode: 'auto', // 'auto' | 'manual' | 'all'
57
+ // Caché de estilos
58
+ cacheEnabled: true
59
+ };
60
+
61
+ logger.debug('[WuStyleBridge] 🎨 Style sharing system initialized');
62
+ }
63
+
64
+ /**
65
+ * 🛡️ REGISTRAR APP FULLY-ISOLATED: Registra una app con fully-isolated para filtrar sus estilos
66
+ * @param {string} appName - Nombre de la app
67
+ * @param {string} appUrl - URL base de la app
68
+ */
69
+ registerFullyIsolatedApp(appName, appUrl) {
70
+ this.fullyIsolatedApps.set(appName, appUrl);
71
+ logger.debug(`[WuStyleBridge] 🛡️ Registered fully-isolated app: ${appName} (${appUrl})`);
72
+ }
73
+
74
+ /**
75
+ * 🔍 VERIFICAR SI ESTILO ES DE APP FULLY-ISOLATED: Verifica si un estilo proviene de una app con fully-isolated
76
+ * @param {string|Object|HTMLElement} styleUrlOrElement - URL del estilo, objeto de estilo, o elemento DOM
77
+ * @returns {boolean}
78
+ */
79
+ isStyleFromFullyIsolatedApp(styleUrlOrElement) {
80
+ let url = '';
81
+
82
+ // Si es un string, usar directamente
83
+ if (typeof styleUrlOrElement === 'string') {
84
+ url = styleUrlOrElement;
85
+ }
86
+ // Si es un elemento DOM (HTMLElement) - verificar si tiene getAttribute (método común de elementos DOM)
87
+ else if (styleUrlOrElement && typeof styleUrlOrElement.getAttribute === 'function') {
88
+ // Obtener data-vite-dev-id o href del elemento
89
+ url = styleUrlOrElement.getAttribute('data-vite-dev-id') || styleUrlOrElement.href || '';
90
+ }
91
+ // Si es un objeto con propiedades
92
+ else if (styleUrlOrElement) {
93
+ if (styleUrlOrElement.href) {
94
+ url = styleUrlOrElement.href;
95
+ } else if (styleUrlOrElement.viteId) {
96
+ url = styleUrlOrElement.viteId;
97
+ } else if (styleUrlOrElement.element) {
98
+ if (typeof styleUrlOrElement.element.getAttribute === 'function') {
99
+ url = styleUrlOrElement.element.getAttribute('data-vite-dev-id') || styleUrlOrElement.element.href || '';
100
+ } else if (styleUrlOrElement.element.href) {
101
+ url = styleUrlOrElement.element.href;
102
+ }
103
+ }
104
+ }
105
+
106
+ if (!url || url.trim() === '') return false;
107
+
108
+ // Normalizar la URL para comparación (convertir backslashes a forward slashes)
109
+ const normalizedUrl = url.replace(/\\/g, '/').toLowerCase();
110
+
111
+ // Verificar si la URL pertenece a alguna app con fully-isolated
112
+ for (const [appName, appUrl] of this.fullyIsolatedApps.entries()) {
113
+ const normalizedAppUrl = appUrl.replace(/\\/g, '/').toLowerCase();
114
+ const normalizedAppName = appName.toLowerCase();
115
+
116
+ // Verificar si la URL contiene la URL base de la app (ej: http://localhost:4001)
117
+ if (normalizedAppUrl && normalizedUrl.includes(normalizedAppUrl)) {
118
+ return true;
119
+ }
120
+
121
+ // Verificar si la URL contiene rutas del app en el sistema de archivos
122
+ // Ej: C:/Users/.../header/src/... o /header/src/...
123
+ // Patrón: cualquier ruta que contenga /header/ o \header\
124
+ const appPathPattern = new RegExp(`[/\\\\]${normalizedAppName}[/\\\\]`, 'i');
125
+ if (appPathPattern.test(normalizedUrl)) {
126
+ return true;
127
+ }
128
+ }
129
+
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * 🔍 DETECTAR ESTILOS: Escanea todos los estilos del documento
135
+ * @returns {Array} Lista de estilos detectados (filtrados para excluir apps con fully-isolated)
136
+ */
137
+ detectDocumentStyles() {
138
+ const styles = [];
139
+
140
+ // 1. Detectar TODOS los <link> tags de CSS
141
+ const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
142
+ linkTags.forEach((link) => {
143
+ // Filtrar estilos de apps con fully-isolated
144
+ if (this.isStyleFromFullyIsolatedApp(link)) {
145
+ return;
146
+ }
147
+
148
+ styles.push({
149
+ type: 'link',
150
+ href: link.href,
151
+ element: link,
152
+ library: this.extractLibraryName(link.href)
153
+ });
154
+ });
155
+
156
+ // 2. Detectar TODOS los <style> tags (incluyendo Vue scoped styles)
157
+ const styleTags = document.querySelectorAll('style');
158
+ styleTags.forEach((style, index) => {
159
+ // Excluir solo estilos ya compartidos por wu-framework
160
+ if (style.getAttribute('data-wu-shared') === 'true') {
161
+ return;
162
+ }
163
+
164
+ const viteId = style.getAttribute('data-vite-dev-id');
165
+ const content = style.textContent;
166
+
167
+ // Filtrar estilos de apps con fully-isolated (después de obtener viteId para mejor detección)
168
+ if (this.isStyleFromFullyIsolatedApp(style) || (viteId && this.isStyleFromFullyIsolatedApp(viteId))) {
169
+ logger.debug(`[WuStyleBridge] 🛡️ Filtered out style from fully-isolated app: ${viteId || 'unknown'}`);
170
+ return;
171
+ }
172
+
173
+ // Incluir todos los estilos con contenido
174
+ if (content && content.trim().length > 0) {
175
+ styles.push({
176
+ type: 'inline',
177
+ content,
178
+ element: style,
179
+ viteId,
180
+ library: this.extractLibraryName(viteId || ''),
181
+ index
182
+ });
183
+ }
184
+ });
185
+
186
+ // 3. Detectar Constructable Stylesheets (si están disponibles)
187
+ if (document.adoptedStyleSheets && document.adoptedStyleSheets.length > 0) {
188
+ document.adoptedStyleSheets.forEach((sheet, index) => {
189
+ styles.push({
190
+ type: 'adoptedStyleSheet',
191
+ sheet,
192
+ index
193
+ });
194
+ });
195
+ }
196
+
197
+ logger.debug(`[WuStyleBridge] 🔍 Detected ${styles.length} shareable styles`);
198
+ return styles;
199
+ }
200
+
201
+ /**
202
+ * 🎯 VERIFICAR SI SE DEBE COMPARTIR: Filtra estilos según configuración
203
+ * @param {string} urlOrId - URL o ID del estilo
204
+ * @returns {boolean}
205
+ */
206
+ shouldShareStyle(urlOrId) {
207
+ if (!urlOrId) return false;
208
+
209
+ // Modo 'all' - compartir todo
210
+ if (this.config.mode === 'all') return true;
211
+
212
+ // Verificar patrones configurados
213
+ for (const pattern of this.config.sharePatterns) {
214
+ if (pattern.test(urlOrId)) return true;
215
+ }
216
+
217
+ // Verificar librerías específicas
218
+ for (const lib of this.config.autoShareLibraries) {
219
+ if (urlOrId.includes(lib)) return true;
220
+ }
221
+
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * 📦 EXTRAER NOMBRE DE LIBRERÍA: Obtiene el nombre de la librería desde la URL
227
+ * @param {string} url - URL del estilo
228
+ * @returns {string|null}
229
+ */
230
+ extractLibraryName(url) {
231
+ if (!url) return null;
232
+
233
+ // Extraer de node_modules
234
+ const nodeModulesMatch = url.match(/\/node_modules\/(@?[^/]+\/[^/]+|@?[^/]+)/);
235
+ if (nodeModulesMatch) return nodeModulesMatch[1];
236
+
237
+ // Extraer de vite dev id
238
+ const viteMatch = url.match(/\/node_modules\/(.+?)\/.*?\.css/);
239
+ if (viteMatch) return viteMatch[1];
240
+
241
+ return null;
242
+ }
243
+
244
+ /**
245
+ * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM
246
+ * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
247
+ * @param {string} appName - Nombre de la app
248
+ * @param {string} styleMode - Modo de estilos: 'shared', 'isolated', 'fully-isolated'
249
+ * @returns {Promise<number>}
250
+ */
251
+ async injectStylesIntoShadow(shadowRoot, appName, styleMode) {
252
+ if (!shadowRoot) {
253
+ logger.warn('[WuStyleBridge] ⚠️ No shadow root provided');
254
+ return 0;
255
+ }
256
+
257
+ // 🛡️ MODO FULLY-ISOLATED: No inyectar ningún estilo compartido
258
+ // Los estilos propios se manejan en wu-sandbox.js con injectOwnStylesToShadow
259
+ if (styleMode === 'fully-isolated') {
260
+ logger.debug(`[WuStyleBridge] 🛡️ Style mode "fully-isolated" for ${appName}, skipping shared style injection`);
261
+ return 0;
262
+ }
263
+
264
+ // 🔒 MODO ISOLATED: No inyectar estilos externos - usar encapsulamiento nativo de Shadow DOM
265
+ // La app debe manejar sus propios estilos (CSS-in-JS, scoped styles, imports directos)
266
+ if (styleMode === 'isolated') {
267
+ logger.debug(`[WuStyleBridge] 🔒 Style mode "isolated" for ${appName}, using native Shadow DOM encapsulation (no external styles)`);
268
+ return 0;
269
+ }
270
+
271
+ // 🌐 MODO SHARED (default): Inyectar todos los estilos compartidos del documento
272
+ logger.debug(`[WuStyleBridge] 🌐 Style mode "shared" for ${appName}, injecting all shared styles...`);
273
+
274
+ // Detectar estilos del documento
275
+ const styles = this.detectDocumentStyles();
276
+ let injectedCount = 0;
277
+
278
+ // Inyectar cada estilo
279
+ for (const style of styles) {
280
+ try {
281
+ switch (style.type) {
282
+ case 'link':
283
+ await this.injectLinkStyle(shadowRoot, style);
284
+ injectedCount++;
285
+ break;
286
+
287
+ case 'inline':
288
+ this.injectInlineStyle(shadowRoot, style);
289
+ injectedCount++;
290
+ break;
291
+
292
+ case 'adoptedStyleSheet':
293
+ this.injectAdoptedStyleSheet(shadowRoot, style);
294
+ injectedCount++;
295
+ break;
296
+ }
297
+ } catch (error) {
298
+ logger.warn(`[WuStyleBridge] ⚠️ Failed to inject style:`, error);
299
+ }
300
+ }
301
+
302
+ logger.debug(`[WuStyleBridge] ✅ Injected ${injectedCount} shared styles into ${appName}`);
303
+ return injectedCount;
304
+ }
305
+
306
+ /**
307
+ * 🔗 INYECTAR LINK STYLE: Clona <link> tag al Shadow DOM
308
+ * @param {ShadowRoot} shadowRoot
309
+ * @param {Object} style
310
+ */
311
+ async injectLinkStyle(shadowRoot, style) {
312
+ // Verificar si ya existe
313
+ const existing = shadowRoot.querySelector(`link[href="${style.href}"]`);
314
+ if (existing) {
315
+ logger.debug(`[WuStyleBridge] ⏭️ Style already exists: ${style.library || style.href}`);
316
+ return;
317
+ }
318
+
319
+ // Clonar link tag
320
+ const link = document.createElement('link');
321
+ link.rel = 'stylesheet';
322
+ link.href = style.href;
323
+ link.setAttribute('data-wu-shared', 'true');
324
+ link.setAttribute('data-wu-library', style.library || 'unknown');
325
+
326
+ // Insertar al principio del shadow root (antes de otros estilos)
327
+ shadowRoot.insertBefore(link, shadowRoot.firstChild);
328
+
329
+ logger.debug(`[WuStyleBridge] 🔗 Injected link: ${style.library || style.href}`);
330
+ }
331
+
332
+ /**
333
+ * 📝 INYECTAR INLINE STYLE: Clona <style> tag al Shadow DOM
334
+ * @param {ShadowRoot} shadowRoot
335
+ * @param {Object} style
336
+ */
337
+ injectInlineStyle(shadowRoot, style) {
338
+ // Verificar si ya existe
339
+ const viteId = style.viteId;
340
+ if (viteId) {
341
+ const existing = shadowRoot.querySelector(`style[data-wu-vite-id="${viteId}"]`);
342
+ if (existing) {
343
+ logger.debug(`[WuStyleBridge] ⏭️ Inline style already exists: ${viteId}`);
344
+ return;
345
+ }
346
+ }
347
+
348
+ // Crear nuevo style tag
349
+ const styleTag = document.createElement('style');
350
+ styleTag.textContent = style.content;
351
+ styleTag.setAttribute('data-wu-shared', 'true');
352
+ styleTag.setAttribute('data-wu-library', style.library || 'unknown');
353
+ if (viteId) {
354
+ styleTag.setAttribute('data-wu-vite-id', viteId);
355
+ }
356
+
357
+ // Insertar al principio del shadow root
358
+ shadowRoot.insertBefore(styleTag, shadowRoot.firstChild);
359
+
360
+ logger.debug(`[WuStyleBridge] 📝 Injected inline style: ${style.library || viteId}`);
361
+ }
362
+
363
+ /**
364
+ * 📋 INYECTAR ADOPTED STYLESHEET: Comparte stylesheet constructable
365
+ * @param {ShadowRoot} shadowRoot
366
+ * @param {Object} style
367
+ */
368
+ injectAdoptedStyleSheet(shadowRoot, style) {
369
+ try {
370
+ // Agregar stylesheet al array de adopted stylesheets
371
+ if (!shadowRoot.adoptedStyleSheets) {
372
+ shadowRoot.adoptedStyleSheets = [];
373
+ }
374
+
375
+ // Verificar si ya existe
376
+ if (shadowRoot.adoptedStyleSheets.includes(style.sheet)) {
377
+ logger.debug(`[WuStyleBridge] ⏭️ Adopted stylesheet already exists`);
378
+ return;
379
+ }
380
+
381
+ shadowRoot.adoptedStyleSheets = [
382
+ ...shadowRoot.adoptedStyleSheets,
383
+ style.sheet
384
+ ];
385
+
386
+ logger.debug(`[WuStyleBridge] 📋 Injected adopted stylesheet`);
387
+ } catch (error) {
388
+ logger.warn(`[WuStyleBridge] ⚠️ Failed to inject adopted stylesheet:`, error);
389
+ }
390
+ }
391
+
392
+ /**
393
+ * 🔄 OBSERVAR CAMBIOS: Monitorea nuevos estilos en el documento
394
+ * @param {Function} callback - Callback cuando se detectan cambios
395
+ */
396
+ observeStyleChanges(callback) {
397
+ // Limpiar observer anterior si existe
398
+ if (this.styleObserver) {
399
+ this.styleObserver.disconnect();
400
+ }
401
+
402
+ // Crear MutationObserver para detectar nuevos estilos
403
+ this.styleObserver = new MutationObserver((mutations) => {
404
+ let hasStyleChanges = false;
405
+
406
+ for (const mutation of mutations) {
407
+ if (mutation.type === 'childList') {
408
+ // Verificar si se agregaron <link> o <style> tags
409
+ const addedNodes = Array.from(mutation.addedNodes);
410
+ const hasNewStyles = addedNodes.some(node =>
411
+ node.tagName === 'LINK' || node.tagName === 'STYLE'
412
+ );
413
+
414
+ if (hasNewStyles) {
415
+ hasStyleChanges = true;
416
+ break;
417
+ }
418
+ }
419
+ }
420
+
421
+ if (hasStyleChanges && callback) {
422
+ logger.debug('[WuStyleBridge] 🔄 Style changes detected');
423
+ callback();
424
+ }
425
+ });
426
+
427
+ // Observar <head> para cambios en estilos
428
+ this.styleObserver.observe(document.head, {
429
+ childList: true,
430
+ subtree: true
431
+ });
432
+
433
+ logger.debug('[WuStyleBridge] 👀 Observing style changes');
434
+ }
435
+
436
+ /**
437
+ * ⚙️ CONFIGURAR: Actualiza la configuración
438
+ * @param {Object} config - Nueva configuración
439
+ */
440
+ configure(config) {
441
+ this.config = {
442
+ ...this.config,
443
+ ...config
444
+ };
445
+
446
+ logger.debug('[WuStyleBridge] ⚙️ Configuration updated:', this.config);
447
+ }
448
+
449
+ /**
450
+ * 🧹 LIMPIAR: Detiene la observación
451
+ */
452
+ cleanup() {
453
+ if (this.styleObserver) {
454
+ this.styleObserver.disconnect();
455
+ this.styleObserver = null;
456
+ }
457
+
458
+ logger.debug('[WuStyleBridge] 🧹 StyleBridge cleaned up');
459
+ }
460
+
461
+ /**
462
+ * 📊 OBTENER ESTADÍSTICAS: Información sobre estilos compartidos
463
+ * @returns {Object}
464
+ */
465
+ getStats() {
466
+ const styles = this.detectDocumentStyles();
467
+
468
+ return {
469
+ totalStyles: styles.length,
470
+ linkStyles: styles.filter(s => s.type === 'link').length,
471
+ inlineStyles: styles.filter(s => s.type === 'inline').length,
472
+ adoptedStyleSheets: styles.filter(s => s.type === 'adoptedStyleSheet').length,
473
+ libraries: [...new Set(styles.map(s => s.library).filter(Boolean))],
474
+ config: this.config
475
+ };
476
+ }
477
+ }