wu-framework 1.1.1 → 1.1.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wu-framework",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "🚀 Universal Microfrontends Framework - 8 frameworks, zero config, Shadow DOM isolation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -23,9 +23,153 @@ const adapterState = {
23
23
  apps: new Map(),
24
24
  Vue: null,
25
25
  createApp: null,
26
- initialized: false
26
+ initialized: false,
27
+ collectedStyles: new Map() // Estilos recolectados por app
27
28
  };
28
29
 
30
+ /**
31
+ * 🎨 STYLE COLLECTOR: Captura estilos inyectados dinámicamente
32
+ * Soluciona el problema de estilos no disponibles en Shadow DOM
33
+ *
34
+ * Estrategia:
35
+ * 1. Captura estilos existentes (incluyendo los de Vite HMR)
36
+ * 2. Filtra por URL base del MFE para identificar estilos relevantes
37
+ * 3. Observa nuevos estilos durante el mount
38
+ */
39
+ function createStyleCollector(appName) {
40
+ const styles = [];
41
+ const capturedIds = new Set();
42
+ let observer = null;
43
+
44
+ // Capturar estilos existentes antes del mount
45
+ // Incluye estilos inyectados por Vite via data-vite-dev-id
46
+ const captureExistingStyles = () => {
47
+ const styleTags = document.querySelectorAll('style:not([data-wu-shared])');
48
+ styleTags.forEach(style => {
49
+ const content = style.textContent;
50
+ const viteId = style.getAttribute('data-vite-dev-id');
51
+
52
+ // Evitar duplicados
53
+ const styleId = viteId || content?.substring(0, 100);
54
+ if (capturedIds.has(styleId)) return;
55
+
56
+ if (content && content.trim().length > 0) {
57
+ // Capturar todos los estilos que no sean de wu-framework
58
+ styles.push({
59
+ type: 'existing',
60
+ content: content,
61
+ viteId: viteId
62
+ });
63
+ capturedIds.add(styleId);
64
+ }
65
+ });
66
+ return styles.length;
67
+ };
68
+
69
+ // Observar nuevos estilos agregados durante el mount
70
+ const startObserving = () => {
71
+ if (!window.MutationObserver) return;
72
+
73
+ observer = new MutationObserver((mutations) => {
74
+ for (const mutation of mutations) {
75
+ if (mutation.type === 'childList') {
76
+ mutation.addedNodes.forEach(node => {
77
+ if (node.tagName === 'STYLE' && !node.hasAttribute('data-wu-shared')) {
78
+ const content = node.textContent;
79
+ const viteId = node.getAttribute('data-vite-dev-id');
80
+
81
+ // Evitar duplicados
82
+ const styleId = viteId || content?.substring(0, 100);
83
+ if (capturedIds.has(styleId)) return;
84
+
85
+ if (content && content.trim().length > 0) {
86
+ styles.push({
87
+ type: 'dynamic',
88
+ content: content,
89
+ viteId: viteId
90
+ });
91
+ capturedIds.add(styleId);
92
+ console.log(`[WuVue] 🎨 Captured dynamic style for ${appName}`);
93
+ }
94
+ }
95
+ });
96
+ }
97
+ }
98
+ });
99
+
100
+ observer.observe(document.head, { childList: true, subtree: true });
101
+ };
102
+
103
+ const stopObserving = () => {
104
+ if (observer) {
105
+ observer.disconnect();
106
+ observer = null;
107
+ }
108
+ };
109
+
110
+ const getStyles = () => [...styles];
111
+
112
+ return {
113
+ captureExistingStyles,
114
+ startObserving,
115
+ stopObserving,
116
+ getStyles
117
+ };
118
+ }
119
+
120
+ /**
121
+ * 🎨 INJECT STYLES TO SHADOW: Inyecta estilos capturados al Shadow DOM
122
+ */
123
+ function injectStylesToShadow(shadowRoot, styles, appName) {
124
+ if (!shadowRoot || !styles || styles.length === 0) return 0;
125
+
126
+ let injected = 0;
127
+
128
+ for (const style of styles) {
129
+ // Verificar si ya existe
130
+ const viteId = style.viteId;
131
+ if (viteId) {
132
+ const existing = shadowRoot.querySelector(`style[data-wu-vite-id="${viteId}"]`);
133
+ if (existing) continue;
134
+ }
135
+
136
+ const styleTag = document.createElement('style');
137
+ styleTag.textContent = style.content;
138
+ styleTag.setAttribute('data-wu-shared', 'true');
139
+ styleTag.setAttribute('data-wu-app', appName);
140
+ if (viteId) {
141
+ styleTag.setAttribute('data-wu-vite-id', viteId);
142
+ }
143
+
144
+ shadowRoot.insertBefore(styleTag, shadowRoot.firstChild);
145
+ injected++;
146
+ }
147
+
148
+ if (injected > 0) {
149
+ console.log(`[WuVue] 🎨 Injected ${injected} styles into Shadow DOM for ${appName}`);
150
+ }
151
+
152
+ return injected;
153
+ }
154
+
155
+ /**
156
+ * 🔍 GET SHADOW ROOT: Obtiene el Shadow Root del contenedor
157
+ */
158
+ function getShadowRoot(container) {
159
+ // Buscar shadow root en la jerarquía
160
+ let current = container;
161
+ while (current) {
162
+ if (current.getRootNode && current.getRootNode() instanceof ShadowRoot) {
163
+ return current.getRootNode();
164
+ }
165
+ if (current.shadowRoot) {
166
+ return current.shadowRoot;
167
+ }
168
+ current = current.parentElement;
169
+ }
170
+ return null;
171
+ }
172
+
29
173
  /**
30
174
  * Detecta y obtiene Vue del contexto global o lo importa
31
175
  */
@@ -121,6 +265,7 @@ function waitForWu(timeout = 5000) {
121
265
  * @param {Function} options.onUnmount - Callback antes de desmontar
122
266
  * @param {boolean} options.standalone - Permitir ejecución standalone (default: true)
123
267
  * @param {string} options.standaloneContainer - Selector para modo standalone (default: '#app')
268
+ * @param {string|string[]} options.styles - CSS string o array de CSS strings para inyectar en Shadow DOM
124
269
  *
125
270
  * @example
126
271
  * // Básico
@@ -135,6 +280,12 @@ function waitForWu(timeout = 5000) {
135
280
  * app.component('MyGlobal', MyComponent);
136
281
  * }
137
282
  * });
283
+ *
284
+ * @example
285
+ * // Con estilos explícitos para Shadow DOM
286
+ * wuVue.register('my-app', App, {
287
+ * styles: `.header { background: blue; }`
288
+ * });
138
289
  */
139
290
  async function register(appName, RootComponent, options = {}) {
140
291
  const {
@@ -143,7 +294,8 @@ async function register(appName, RootComponent, options = {}) {
143
294
  onMount = null,
144
295
  onUnmount = null,
145
296
  standalone = true,
146
- standaloneContainer = '#app'
297
+ standaloneContainer = '#app',
298
+ styles = null // 🎨 CSS string o array de CSS strings para inyectar en Shadow DOM
147
299
  } = options;
148
300
 
149
301
  // Asegurar que Vue está disponible
@@ -155,6 +307,9 @@ async function register(appName, RootComponent, options = {}) {
155
307
 
156
308
  const { createApp } = adapterState;
157
309
 
310
+ // 🎨 Crear collector de estilos para esta app
311
+ const styleCollector = createStyleCollector(appName);
312
+
158
313
  // Función de mount interna
159
314
  const mountApp = (container) => {
160
315
  if (!container) {
@@ -169,6 +324,13 @@ async function register(appName, RootComponent, options = {}) {
169
324
  }
170
325
 
171
326
  try {
327
+ // 🎨 Capturar estilos existentes ANTES del mount
328
+ const existingStylesCount = styleCollector.captureExistingStyles();
329
+ console.log(`[WuVue] 🎨 Captured ${existingStylesCount} existing styles for ${appName}`);
330
+
331
+ // 🎨 Comenzar a observar nuevos estilos
332
+ styleCollector.startObserving();
333
+
172
334
  // Crear la aplicación Vue
173
335
  const app = createApp(RootComponent, props);
174
336
 
@@ -184,6 +346,33 @@ async function register(appName, RootComponent, options = {}) {
184
346
  // Montar
185
347
  app.mount(container);
186
348
 
349
+ // 🎨 Detener observación
350
+ styleCollector.stopObserving();
351
+
352
+ // 🎨 Inyectar estilos en Shadow DOM
353
+ const shadowRoot = getShadowRoot(container);
354
+ if (shadowRoot) {
355
+ // 1. Inyectar estilos declarados explícitamente (opción `styles`)
356
+ if (styles) {
357
+ const styleArray = Array.isArray(styles) ? styles : [styles];
358
+ const explicitStyles = styleArray.map(css => ({ type: 'explicit', content: css }));
359
+ injectStylesToShadow(shadowRoot, explicitStyles, appName);
360
+ console.log(`[WuVue] 🎨 Injected ${explicitStyles.length} explicit styles for ${appName}`);
361
+ }
362
+
363
+ // 2. Inyectar estilos capturados dinámicamente
364
+ const collectedStyles = styleCollector.getStyles();
365
+ console.log(`[WuVue] 🎨 Total styles collected for ${appName}:`, collectedStyles.length);
366
+
367
+ // Inyectar estilos capturados
368
+ injectStylesToShadow(shadowRoot, collectedStyles, appName);
369
+
370
+ // Guardar referencia de estilos para re-inyección si es necesario
371
+ adapterState.collectedStyles.set(appName, collectedStyles);
372
+ } else {
373
+ console.log(`[WuVue] ℹ️ No Shadow DOM detected for ${appName}, styles applied normally`);
374
+ }
375
+
187
376
  // Guardar referencia
188
377
  adapterState.apps.set(appName, { app, container });
189
378
 
@@ -193,6 +382,7 @@ async function register(appName, RootComponent, options = {}) {
193
382
  onMount(container, app);
194
383
  }
195
384
  } catch (error) {
385
+ styleCollector.stopObserving();
196
386
  console.error(`[WuVue] Mount error for ${appName}:`, error);
197
387
  throw error;
198
388
  }
@@ -9,8 +9,13 @@ export class WuStyleBridge {
9
9
  constructor() {
10
10
  this.sharedStyles = new Map();
11
11
  this.styleObserver = null;
12
+ this.appStyleModes = new Map(); // Per-app style isolation mode
12
13
  this.config = {
13
- // Librerías que se deben compartir automáticamente
14
+ // 🛡️ DEFAULT MODE: 'isolated' = cada MFE tiene sus propios estilos
15
+ // 'shared' = compartir estilos del padre
16
+ // 'auto' = compartir solo librerías específicas
17
+ defaultMode: 'isolated',
18
+ // Librerías que se deben compartir automáticamente (solo en modo 'auto')
14
19
  autoShareLibraries: [
15
20
  'element-plus',
16
21
  'vue-flow',
@@ -20,20 +25,37 @@ export class WuStyleBridge {
20
25
  'normalize.css',
21
26
  'reset.css'
22
27
  ],
23
- // Patrones de URLs a compartir
28
+ // Patrones de URLs a compartir (solo en modo 'auto' o 'shared')
24
29
  sharePatterns: [
25
30
  /\/node_modules\//,
26
31
  /\/@vite\/client/,
27
32
  /\/dist\/index\.css$/,
28
33
  /\/dist\/style\.css$/
29
34
  ],
30
- // Modo de compartición
31
- mode: 'auto', // 'auto' | 'manual' | 'all'
32
35
  // Caché de estilos
33
36
  cacheEnabled: true
34
37
  };
35
38
 
36
- console.log('[WuStyleBridge] 🎨 Style sharing system initialized');
39
+ console.log('[WuStyleBridge] 🎨 Style sharing system initialized (default: isolated)');
40
+ }
41
+
42
+ /**
43
+ * 🛡️ SET APP STYLE MODE: Configura el modo de estilos para una app específica
44
+ * @param {string} appName - Nombre de la app
45
+ * @param {'isolated' | 'shared' | 'auto'} mode - Modo de estilos
46
+ */
47
+ setAppStyleMode(appName, mode) {
48
+ this.appStyleModes.set(appName, mode);
49
+ console.log(`[WuStyleBridge] 🎨 Style mode for ${appName}: ${mode}`);
50
+ }
51
+
52
+ /**
53
+ * 🔍 GET APP STYLE MODE: Obtiene el modo de estilos de una app
54
+ * @param {string} appName - Nombre de la app
55
+ * @returns {'isolated' | 'shared' | 'auto'}
56
+ */
57
+ getAppStyleMode(appName) {
58
+ return this.appStyleModes.get(appName) || this.config.defaultMode;
37
59
  }
38
60
 
39
61
  /**
@@ -137,7 +159,7 @@ export class WuStyleBridge {
137
159
  }
138
160
 
139
161
  /**
140
- * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM
162
+ * 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM según el modo
141
163
  * @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
142
164
  * @param {string} appName - Nombre de la app
143
165
  * @returns {Promise<number>}
@@ -148,15 +170,31 @@ export class WuStyleBridge {
148
170
  return 0;
149
171
  }
150
172
 
151
- console.log(`[WuStyleBridge] 🌉 Injecting shared styles into ${appName}...`);
173
+ // 🛡️ CHECK STYLE MODE: Respetar el modo de aislamiento de la app
174
+ const styleMode = this.getAppStyleMode(appName);
175
+
176
+ if (styleMode === 'isolated') {
177
+ console.log(`[WuStyleBridge] 🛡️ ${appName} is in ISOLATED mode - no parent styles will be shared`);
178
+ return 0;
179
+ }
180
+
181
+ console.log(`[WuStyleBridge] 🌉 Injecting styles into ${appName} (mode: ${styleMode})...`);
152
182
 
153
183
  // Detectar estilos del documento
154
184
  const styles = this.detectDocumentStyles();
155
185
  let injectedCount = 0;
156
186
 
157
- // Inyectar cada estilo
187
+ // Inyectar cada estilo según el modo
158
188
  for (const style of styles) {
159
189
  try {
190
+ // 🎯 En modo 'auto', solo compartir librerías específicas
191
+ if (styleMode === 'auto') {
192
+ const shouldShare = this.shouldShareStyle(style.href || style.viteId || '');
193
+ if (!shouldShare) {
194
+ continue; // Skip this style
195
+ }
196
+ }
197
+
160
198
  switch (style.type) {
161
199
  case 'link':
162
200
  await this.injectLinkStyle(shadowRoot, style);