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 +1 -1
- package/src/adapters/vue.js +192 -2
- package/src/core/wu-style-bridge.js +46 -8
package/package.json
CHANGED
package/src/adapters/vue.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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);
|