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.
- package/README.md +52 -20
- package/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +15511 -15146
- package/dist/wu-framework.dev.js.map +1 -1
- package/dist/wu-framework.esm.js +1 -1
- package/dist/wu-framework.esm.js.map +1 -1
- package/dist/wu-framework.umd.js +1 -1
- package/dist/wu-framework.umd.js.map +1 -1
- package/package.json +166 -161
- package/src/adapters/angular/ai.js +30 -30
- package/src/adapters/angular/index.d.ts +154 -154
- package/src/adapters/angular/index.js +932 -932
- package/src/adapters/angular.d.ts +3 -3
- package/src/adapters/angular.js +3 -3
- package/src/adapters/index.js +168 -168
- package/src/adapters/lit/ai.js +20 -20
- package/src/adapters/lit/index.d.ts +120 -120
- package/src/adapters/lit/index.js +721 -721
- package/src/adapters/lit.d.ts +3 -3
- package/src/adapters/lit.js +3 -3
- package/src/adapters/preact/ai.js +33 -33
- package/src/adapters/preact/index.d.ts +108 -108
- package/src/adapters/preact/index.js +661 -661
- package/src/adapters/preact.d.ts +3 -3
- package/src/adapters/preact.js +3 -3
- package/src/adapters/react/index.js +48 -54
- package/src/adapters/react.d.ts +3 -3
- package/src/adapters/react.js +3 -3
- package/src/adapters/shared.js +64 -64
- package/src/adapters/solid/ai.js +32 -32
- package/src/adapters/solid/index.d.ts +101 -101
- package/src/adapters/solid/index.js +586 -586
- package/src/adapters/solid.d.ts +3 -3
- package/src/adapters/solid.js +3 -3
- package/src/adapters/svelte/ai.js +31 -31
- package/src/adapters/svelte/index.d.ts +166 -166
- package/src/adapters/svelte/index.js +798 -798
- package/src/adapters/svelte.d.ts +3 -3
- package/src/adapters/svelte.js +3 -3
- package/src/adapters/vanilla/ai.js +30 -30
- package/src/adapters/vanilla/index.d.ts +179 -179
- package/src/adapters/vanilla/index.js +785 -785
- package/src/adapters/vanilla.d.ts +3 -3
- package/src/adapters/vanilla.js +3 -3
- package/src/adapters/vue/ai.js +52 -52
- package/src/adapters/vue/index.d.ts +299 -299
- package/src/adapters/vue/index.js +610 -610
- package/src/adapters/vue.d.ts +3 -3
- package/src/adapters/vue.js +3 -3
- package/src/ai/wu-ai-actions.js +261 -261
- package/src/ai/wu-ai-agent.js +546 -546
- package/src/ai/wu-ai-browser-primitives.js +354 -354
- package/src/ai/wu-ai-browser.js +380 -380
- package/src/ai/wu-ai-context.js +332 -332
- package/src/ai/wu-ai-conversation.js +613 -613
- package/src/ai/wu-ai-orchestrate.js +1021 -1021
- package/src/ai/wu-ai-permissions.js +381 -381
- package/src/ai/wu-ai-provider.js +700 -700
- package/src/ai/wu-ai-schema.js +225 -225
- package/src/ai/wu-ai-triggers.js +396 -396
- package/src/ai/wu-ai.js +804 -804
- package/src/core/wu-app.js +236 -236
- package/src/core/wu-cache.js +498 -477
- package/src/core/wu-core.js +1412 -1398
- package/src/core/wu-error-boundary.js +396 -382
- package/src/core/wu-event-bus.js +390 -348
- package/src/core/wu-hooks.js +350 -350
- package/src/core/wu-html-parser.js +199 -190
- package/src/core/wu-iframe-sandbox.js +328 -328
- package/src/core/wu-loader.js +385 -273
- package/src/core/wu-logger.js +142 -134
- package/src/core/wu-manifest.js +532 -509
- package/src/core/wu-mcp-bridge.js +432 -432
- package/src/core/wu-overrides.js +510 -510
- package/src/core/wu-performance.js +228 -228
- package/src/core/wu-plugin.js +401 -348
- package/src/core/wu-prefetch.js +414 -414
- package/src/core/wu-proxy-sandbox.js +477 -476
- package/src/core/wu-sandbox.js +779 -779
- package/src/core/wu-script-executor.js +161 -113
- package/src/core/wu-snapshot-sandbox.js +227 -227
- package/src/core/wu-store.js +13 -3
- package/src/core/wu-strategies.js +256 -256
- package/src/core/wu-style-bridge.js +477 -477
- package/src/index.d.ts +317 -0
- package/src/index.js +234 -224
- 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
|
+
}
|