wu-framework 1.1.15 → 1.1.17

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 (88) hide show
  1. package/README.md +52 -20
  2. package/dist/wu-framework.cjs.js +1 -1
  3. package/dist/wu-framework.cjs.js.map +1 -1
  4. package/dist/wu-framework.dev.js +15511 -15146
  5. package/dist/wu-framework.dev.js.map +1 -1
  6. package/dist/wu-framework.esm.js +1 -1
  7. package/dist/wu-framework.esm.js.map +1 -1
  8. package/dist/wu-framework.umd.js +1 -1
  9. package/dist/wu-framework.umd.js.map +1 -1
  10. package/package.json +166 -161
  11. package/src/adapters/angular/ai.js +30 -30
  12. package/src/adapters/angular/index.d.ts +154 -154
  13. package/src/adapters/angular/index.js +932 -932
  14. package/src/adapters/angular.d.ts +3 -3
  15. package/src/adapters/angular.js +3 -3
  16. package/src/adapters/index.js +168 -168
  17. package/src/adapters/lit/ai.js +20 -20
  18. package/src/adapters/lit/index.d.ts +120 -120
  19. package/src/adapters/lit/index.js +721 -721
  20. package/src/adapters/lit.d.ts +3 -3
  21. package/src/adapters/lit.js +3 -3
  22. package/src/adapters/preact/ai.js +33 -33
  23. package/src/adapters/preact/index.d.ts +108 -108
  24. package/src/adapters/preact/index.js +661 -661
  25. package/src/adapters/preact.d.ts +3 -3
  26. package/src/adapters/preact.js +3 -3
  27. package/src/adapters/react/index.js +48 -54
  28. package/src/adapters/react.d.ts +3 -3
  29. package/src/adapters/react.js +3 -3
  30. package/src/adapters/shared.js +64 -64
  31. package/src/adapters/solid/ai.js +32 -32
  32. package/src/adapters/solid/index.d.ts +101 -101
  33. package/src/adapters/solid/index.js +586 -586
  34. package/src/adapters/solid.d.ts +3 -3
  35. package/src/adapters/solid.js +3 -3
  36. package/src/adapters/svelte/ai.js +31 -31
  37. package/src/adapters/svelte/index.d.ts +166 -166
  38. package/src/adapters/svelte/index.js +798 -798
  39. package/src/adapters/svelte.d.ts +3 -3
  40. package/src/adapters/svelte.js +3 -3
  41. package/src/adapters/vanilla/ai.js +30 -30
  42. package/src/adapters/vanilla/index.d.ts +179 -179
  43. package/src/adapters/vanilla/index.js +785 -785
  44. package/src/adapters/vanilla.d.ts +3 -3
  45. package/src/adapters/vanilla.js +3 -3
  46. package/src/adapters/vue/ai.js +52 -52
  47. package/src/adapters/vue/index.d.ts +299 -299
  48. package/src/adapters/vue/index.js +610 -610
  49. package/src/adapters/vue.d.ts +3 -3
  50. package/src/adapters/vue.js +3 -3
  51. package/src/ai/wu-ai-actions.js +261 -261
  52. package/src/ai/wu-ai-agent.js +546 -546
  53. package/src/ai/wu-ai-browser-primitives.js +354 -354
  54. package/src/ai/wu-ai-browser.js +380 -380
  55. package/src/ai/wu-ai-context.js +332 -332
  56. package/src/ai/wu-ai-conversation.js +613 -613
  57. package/src/ai/wu-ai-orchestrate.js +1021 -1021
  58. package/src/ai/wu-ai-permissions.js +381 -381
  59. package/src/ai/wu-ai-provider.js +700 -700
  60. package/src/ai/wu-ai-schema.js +225 -225
  61. package/src/ai/wu-ai-triggers.js +396 -396
  62. package/src/ai/wu-ai.js +804 -804
  63. package/src/core/wu-app.js +236 -236
  64. package/src/core/wu-cache.js +498 -477
  65. package/src/core/wu-core.js +1412 -1398
  66. package/src/core/wu-error-boundary.js +396 -382
  67. package/src/core/wu-event-bus.js +390 -348
  68. package/src/core/wu-hooks.js +350 -350
  69. package/src/core/wu-html-parser.js +199 -190
  70. package/src/core/wu-iframe-sandbox.js +328 -328
  71. package/src/core/wu-loader.js +385 -273
  72. package/src/core/wu-logger.js +142 -134
  73. package/src/core/wu-manifest.js +532 -509
  74. package/src/core/wu-mcp-bridge.js +432 -432
  75. package/src/core/wu-overrides.js +510 -510
  76. package/src/core/wu-performance.js +228 -228
  77. package/src/core/wu-plugin.js +401 -348
  78. package/src/core/wu-prefetch.js +414 -414
  79. package/src/core/wu-proxy-sandbox.js +477 -476
  80. package/src/core/wu-sandbox.js +779 -779
  81. package/src/core/wu-script-executor.js +161 -113
  82. package/src/core/wu-snapshot-sandbox.js +227 -227
  83. package/src/core/wu-store.js +13 -3
  84. package/src/core/wu-strategies.js +256 -256
  85. package/src/core/wu-style-bridge.js +477 -477
  86. package/src/index.d.ts +317 -0
  87. package/src/index.js +234 -224
  88. 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
+ }