wu-framework 1.0.0
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/LICENSE +21 -0
- package/README.md +559 -0
- package/package.json +84 -0
- package/src/api/wu-simple.js +316 -0
- package/src/core/wu-app.js +192 -0
- package/src/core/wu-cache.js +374 -0
- package/src/core/wu-core.js +1296 -0
- package/src/core/wu-error-boundary.js +380 -0
- package/src/core/wu-event-bus.js +257 -0
- package/src/core/wu-hooks.js +348 -0
- package/src/core/wu-html-parser.js +280 -0
- package/src/core/wu-loader.js +271 -0
- package/src/core/wu-logger.js +119 -0
- package/src/core/wu-manifest.js +366 -0
- package/src/core/wu-performance.js +226 -0
- package/src/core/wu-plugin.js +213 -0
- package/src/core/wu-proxy-sandbox.js +153 -0
- package/src/core/wu-registry.js +130 -0
- package/src/core/wu-sandbox-pool.js +390 -0
- package/src/core/wu-sandbox.js +720 -0
- package/src/core/wu-script-executor.js +216 -0
- package/src/core/wu-snapshot-sandbox.js +184 -0
- package/src/core/wu-store.js +297 -0
- package/src/core/wu-strategies.js +241 -0
- package/src/core/wu-style-bridge.js +357 -0
- package/src/index.js +690 -0
- package/src/utils/dependency-resolver.js +326 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🎯 WU-STRATEGIES: LOADING STRATEGIES
|
|
3
|
+
*
|
|
4
|
+
* Estrategias de carga para optimizar performance:
|
|
5
|
+
* - Lazy: Carga solo cuando se monta
|
|
6
|
+
* - Eager: Precarga en init
|
|
7
|
+
* - Preload: Usa <link rel="prefetch">
|
|
8
|
+
* - Idle: Carga cuando el navegador está idle
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class WuLoadingStrategy {
|
|
12
|
+
constructor(core) {
|
|
13
|
+
this.core = core;
|
|
14
|
+
this.strategies = new Map();
|
|
15
|
+
this.loadingQueue = [];
|
|
16
|
+
this.isIdle = false;
|
|
17
|
+
|
|
18
|
+
this.registerDefaultStrategies();
|
|
19
|
+
this.setupIdleCallback();
|
|
20
|
+
|
|
21
|
+
console.log('[WuStrategies] 🎯 Loading strategies initialized');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 📋 REGISTER DEFAULT STRATEGIES
|
|
26
|
+
*/
|
|
27
|
+
registerDefaultStrategies() {
|
|
28
|
+
// Lazy: Solo carga cuando se necesita (no precarga)
|
|
29
|
+
this.register('lazy', {
|
|
30
|
+
shouldPreload: false,
|
|
31
|
+
load: async (appName, config) => {
|
|
32
|
+
console.log(`[Strategy:Lazy] Loading ${appName} on demand (no preload)`);
|
|
33
|
+
// No hace nada, la app se carga cuando se monta
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Eager: Carga inmediatamente en init
|
|
39
|
+
this.register('eager', {
|
|
40
|
+
shouldPreload: true,
|
|
41
|
+
priority: 'high',
|
|
42
|
+
load: async (appName, config) => {
|
|
43
|
+
console.log(`[Strategy:Eager] Preloading ${appName} immediately`);
|
|
44
|
+
|
|
45
|
+
// Cargar el módulo de la app
|
|
46
|
+
const app = this.core.apps.get(appName);
|
|
47
|
+
if (app) {
|
|
48
|
+
const moduleUrl = await this.core.resolveModulePath(app);
|
|
49
|
+
await import(/* @vite-ignore */ moduleUrl);
|
|
50
|
+
console.log(`[Strategy:Eager] ✅ ${appName} preloaded`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Preload: Usa resource hints del navegador
|
|
56
|
+
this.register('preload', {
|
|
57
|
+
shouldPreload: true,
|
|
58
|
+
priority: 'medium',
|
|
59
|
+
load: async (appName, config) => {
|
|
60
|
+
console.log(`[Strategy:Preload] Using resource hints for ${appName}`);
|
|
61
|
+
|
|
62
|
+
// Crear <link rel="prefetch">
|
|
63
|
+
const app = this.core.apps.get(appName);
|
|
64
|
+
if (app) {
|
|
65
|
+
const moduleUrl = await this.core.resolveModulePath(app);
|
|
66
|
+
|
|
67
|
+
const link = document.createElement('link');
|
|
68
|
+
link.rel = 'prefetch';
|
|
69
|
+
link.href = moduleUrl;
|
|
70
|
+
link.as = 'script';
|
|
71
|
+
document.head.appendChild(link);
|
|
72
|
+
|
|
73
|
+
console.log(`[Strategy:Preload] ✅ Resource hint added for ${appName}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Idle: Carga cuando el navegador está idle
|
|
79
|
+
this.register('idle', {
|
|
80
|
+
shouldPreload: false,
|
|
81
|
+
load: async (appName, config) => {
|
|
82
|
+
console.log(`[Strategy:Idle] Queueing ${appName} for idle loading`);
|
|
83
|
+
|
|
84
|
+
return new Promise((resolve) => {
|
|
85
|
+
this.loadingQueue.push({
|
|
86
|
+
appName,
|
|
87
|
+
config,
|
|
88
|
+
resolve
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Si ya estamos idle, procesar inmediatamente
|
|
92
|
+
if (this.isIdle) {
|
|
93
|
+
this.processIdleQueue();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 📦 REGISTER: Registrar estrategia personalizada
|
|
102
|
+
* @param {string} name - Nombre de la estrategia
|
|
103
|
+
* @param {Object} strategy - Configuración de la estrategia
|
|
104
|
+
*/
|
|
105
|
+
register(name, strategy) {
|
|
106
|
+
if (!strategy.load || typeof strategy.load !== 'function') {
|
|
107
|
+
throw new Error('[WuStrategies] Strategy must have a load function');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.strategies.set(name, {
|
|
111
|
+
name,
|
|
112
|
+
shouldPreload: strategy.shouldPreload || false,
|
|
113
|
+
priority: strategy.priority || 'low',
|
|
114
|
+
load: strategy.load
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
console.log(`[WuStrategies] Strategy "${name}" registered`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 🚀 LOAD: Cargar app con estrategia
|
|
122
|
+
* @param {string} appName - Nombre de la app
|
|
123
|
+
* @param {Object} config - Configuración con strategy
|
|
124
|
+
* @returns {Promise}
|
|
125
|
+
*/
|
|
126
|
+
async load(appName, config) {
|
|
127
|
+
const strategyName = config.strategy || 'lazy';
|
|
128
|
+
const strategy = this.strategies.get(strategyName);
|
|
129
|
+
|
|
130
|
+
if (!strategy) {
|
|
131
|
+
console.warn(`[WuStrategies] Strategy "${strategyName}" not found, using lazy`);
|
|
132
|
+
return await this.strategies.get('lazy').load(appName, config);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return await strategy.load(appName, config);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 🎯 PRELOAD: Precargar apps según estrategia
|
|
140
|
+
* @param {Array} apps - Apps a evaluar para precarga
|
|
141
|
+
*/
|
|
142
|
+
async preload(apps) {
|
|
143
|
+
const toPreload = apps.filter(app => {
|
|
144
|
+
const strategy = this.strategies.get(app.strategy || 'lazy');
|
|
145
|
+
return strategy.shouldPreload;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Ordenar por prioridad
|
|
149
|
+
toPreload.sort((a, b) => {
|
|
150
|
+
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
151
|
+
const aPriority = this.strategies.get(a.strategy)?.priority || 'low';
|
|
152
|
+
const bPriority = this.strategies.get(b.strategy)?.priority || 'low';
|
|
153
|
+
return priorityOrder[aPriority] - priorityOrder[bPriority];
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.log(`[WuStrategies] Preloading ${toPreload.length} apps`);
|
|
157
|
+
|
|
158
|
+
// Precargar en orden
|
|
159
|
+
for (const app of toPreload) {
|
|
160
|
+
try {
|
|
161
|
+
await this.load(app.name, app);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(`[WuStrategies] Failed to preload ${app.name}:`, error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* ⏰ SETUP IDLE CALLBACK: Configurar idle loading
|
|
170
|
+
*/
|
|
171
|
+
setupIdleCallback() {
|
|
172
|
+
if ('requestIdleCallback' in window) {
|
|
173
|
+
const idleCallback = (deadline) => {
|
|
174
|
+
this.isIdle = true;
|
|
175
|
+
this.processIdleQueue(deadline);
|
|
176
|
+
|
|
177
|
+
// Re-schedule
|
|
178
|
+
requestIdleCallback(idleCallback);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
requestIdleCallback(idleCallback);
|
|
182
|
+
} else {
|
|
183
|
+
// Fallback: usar setTimeout
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
this.isIdle = true;
|
|
186
|
+
this.processIdleQueue();
|
|
187
|
+
}, 2000);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 📋 PROCESS IDLE QUEUE: Procesar cola de carga idle
|
|
193
|
+
* @param {Object} deadline - IdleDeadline object
|
|
194
|
+
*/
|
|
195
|
+
async processIdleQueue(deadline) {
|
|
196
|
+
while (this.loadingQueue.length > 0) {
|
|
197
|
+
// Si tenemos deadline y se acabó el tiempo, salir
|
|
198
|
+
if (deadline && deadline.timeRemaining() <= 0) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const item = this.loadingQueue.shift();
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const app = this.core.apps.get(item.appName);
|
|
206
|
+
if (app) {
|
|
207
|
+
const moduleUrl = await this.core.resolveModulePath(app);
|
|
208
|
+
await import(/* @vite-ignore */ moduleUrl);
|
|
209
|
+
console.log(`[Strategy:Idle] ✅ ${item.appName} loaded during idle time`);
|
|
210
|
+
item.resolve(true);
|
|
211
|
+
} else {
|
|
212
|
+
item.resolve(null);
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error(`[Strategy:Idle] Failed to load ${item.appName}:`, error);
|
|
216
|
+
item.resolve(null);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 📊 GET STATS: Estadísticas de estrategias
|
|
223
|
+
* @returns {Object}
|
|
224
|
+
*/
|
|
225
|
+
getStats() {
|
|
226
|
+
return {
|
|
227
|
+
totalStrategies: this.strategies.size,
|
|
228
|
+
strategies: Array.from(this.strategies.keys()),
|
|
229
|
+
idleQueueSize: this.loadingQueue.length,
|
|
230
|
+
isIdle: this.isIdle
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 🧹 CLEANUP: Limpiar estrategias
|
|
236
|
+
*/
|
|
237
|
+
cleanup() {
|
|
238
|
+
this.loadingQueue = [];
|
|
239
|
+
console.log('[WuStrategies] 🧹 Strategies cleaned up');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
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
|
+
|
|
8
|
+
export class WuStyleBridge {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.sharedStyles = new Map();
|
|
11
|
+
this.styleObserver = null;
|
|
12
|
+
this.config = {
|
|
13
|
+
// Librerías que se deben compartir automáticamente
|
|
14
|
+
autoShareLibraries: [
|
|
15
|
+
'element-plus',
|
|
16
|
+
'vue-flow',
|
|
17
|
+
'@vue-flow',
|
|
18
|
+
'vueuse',
|
|
19
|
+
'@vueuse',
|
|
20
|
+
'normalize.css',
|
|
21
|
+
'reset.css'
|
|
22
|
+
],
|
|
23
|
+
// Patrones de URLs a compartir
|
|
24
|
+
sharePatterns: [
|
|
25
|
+
/\/node_modules\//,
|
|
26
|
+
/\/@vite\/client/,
|
|
27
|
+
/\/dist\/index\.css$/,
|
|
28
|
+
/\/dist\/style\.css$/
|
|
29
|
+
],
|
|
30
|
+
// Modo de compartición
|
|
31
|
+
mode: 'auto', // 'auto' | 'manual' | 'all'
|
|
32
|
+
// Caché de estilos
|
|
33
|
+
cacheEnabled: true
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
console.log('[WuStyleBridge] 🎨 Style sharing system initialized');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 🔍 DETECTAR ESTILOS: Escanea todos los estilos del documento
|
|
41
|
+
* @returns {Array} Lista de estilos detectados
|
|
42
|
+
*/
|
|
43
|
+
detectDocumentStyles() {
|
|
44
|
+
const styles = [];
|
|
45
|
+
|
|
46
|
+
// 1. Detectar TODOS los <link> tags de CSS
|
|
47
|
+
const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
|
|
48
|
+
linkTags.forEach((link) => {
|
|
49
|
+
styles.push({
|
|
50
|
+
type: 'link',
|
|
51
|
+
href: link.href,
|
|
52
|
+
element: link,
|
|
53
|
+
library: this.extractLibraryName(link.href)
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. Detectar TODOS los <style> tags (incluyendo Vue scoped styles)
|
|
58
|
+
const styleTags = document.querySelectorAll('style');
|
|
59
|
+
styleTags.forEach((style, index) => {
|
|
60
|
+
// Excluir solo estilos ya compartidos por wu-framework
|
|
61
|
+
if (style.getAttribute('data-wu-shared') === 'true') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const viteId = style.getAttribute('data-vite-dev-id');
|
|
66
|
+
const content = style.textContent;
|
|
67
|
+
|
|
68
|
+
// Incluir todos los estilos con contenido
|
|
69
|
+
if (content && content.trim().length > 0) {
|
|
70
|
+
styles.push({
|
|
71
|
+
type: 'inline',
|
|
72
|
+
content,
|
|
73
|
+
element: style,
|
|
74
|
+
viteId,
|
|
75
|
+
library: this.extractLibraryName(viteId || ''),
|
|
76
|
+
index
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 3. Detectar Constructable Stylesheets (si están disponibles)
|
|
82
|
+
if (document.adoptedStyleSheets && document.adoptedStyleSheets.length > 0) {
|
|
83
|
+
document.adoptedStyleSheets.forEach((sheet, index) => {
|
|
84
|
+
styles.push({
|
|
85
|
+
type: 'adoptedStyleSheet',
|
|
86
|
+
sheet,
|
|
87
|
+
index
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`[WuStyleBridge] 🔍 Detected ${styles.length} shareable styles`);
|
|
93
|
+
return styles;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 🎯 VERIFICAR SI SE DEBE COMPARTIR: Filtra estilos según configuración
|
|
98
|
+
* @param {string} urlOrId - URL o ID del estilo
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
shouldShareStyle(urlOrId) {
|
|
102
|
+
if (!urlOrId) return false;
|
|
103
|
+
|
|
104
|
+
// Modo 'all' - compartir todo
|
|
105
|
+
if (this.config.mode === 'all') return true;
|
|
106
|
+
|
|
107
|
+
// Verificar patrones configurados
|
|
108
|
+
for (const pattern of this.config.sharePatterns) {
|
|
109
|
+
if (pattern.test(urlOrId)) return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Verificar librerías específicas
|
|
113
|
+
for (const lib of this.config.autoShareLibraries) {
|
|
114
|
+
if (urlOrId.includes(lib)) return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 📦 EXTRAER NOMBRE DE LIBRERÍA: Obtiene el nombre de la librería desde la URL
|
|
122
|
+
* @param {string} url - URL del estilo
|
|
123
|
+
* @returns {string|null}
|
|
124
|
+
*/
|
|
125
|
+
extractLibraryName(url) {
|
|
126
|
+
if (!url) return null;
|
|
127
|
+
|
|
128
|
+
// Extraer de node_modules
|
|
129
|
+
const nodeModulesMatch = url.match(/\/node_modules\/(@?[^/]+\/[^/]+|@?[^/]+)/);
|
|
130
|
+
if (nodeModulesMatch) return nodeModulesMatch[1];
|
|
131
|
+
|
|
132
|
+
// Extraer de vite dev id
|
|
133
|
+
const viteMatch = url.match(/\/node_modules\/(.+?)\/.*?\.css/);
|
|
134
|
+
if (viteMatch) return viteMatch[1];
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 🌉 INYECTAR ESTILOS EN SHADOW DOM: Clona estilos al Shadow DOM
|
|
141
|
+
* @param {ShadowRoot} shadowRoot - Shadow DOM donde inyectar
|
|
142
|
+
* @param {string} appName - Nombre de la app
|
|
143
|
+
* @returns {Promise<number>}
|
|
144
|
+
*/
|
|
145
|
+
async injectStylesIntoShadow(shadowRoot, appName) {
|
|
146
|
+
if (!shadowRoot) {
|
|
147
|
+
console.warn('[WuStyleBridge] ⚠️ No shadow root provided');
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log(`[WuStyleBridge] 🌉 Injecting shared styles into ${appName}...`);
|
|
152
|
+
|
|
153
|
+
// Detectar estilos del documento
|
|
154
|
+
const styles = this.detectDocumentStyles();
|
|
155
|
+
let injectedCount = 0;
|
|
156
|
+
|
|
157
|
+
// Inyectar cada estilo
|
|
158
|
+
for (const style of styles) {
|
|
159
|
+
try {
|
|
160
|
+
switch (style.type) {
|
|
161
|
+
case 'link':
|
|
162
|
+
await this.injectLinkStyle(shadowRoot, style);
|
|
163
|
+
injectedCount++;
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case 'inline':
|
|
167
|
+
this.injectInlineStyle(shadowRoot, style);
|
|
168
|
+
injectedCount++;
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case 'adoptedStyleSheet':
|
|
172
|
+
this.injectAdoptedStyleSheet(shadowRoot, style);
|
|
173
|
+
injectedCount++;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.warn(`[WuStyleBridge] ⚠️ Failed to inject style:`, error);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(`[WuStyleBridge] ✅ Injected ${injectedCount} styles into ${appName}`);
|
|
182
|
+
return injectedCount;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 🔗 INYECTAR LINK STYLE: Clona <link> tag al Shadow DOM
|
|
187
|
+
* @param {ShadowRoot} shadowRoot
|
|
188
|
+
* @param {Object} style
|
|
189
|
+
*/
|
|
190
|
+
async injectLinkStyle(shadowRoot, style) {
|
|
191
|
+
// Verificar si ya existe
|
|
192
|
+
const existing = shadowRoot.querySelector(`link[href="${style.href}"]`);
|
|
193
|
+
if (existing) {
|
|
194
|
+
console.log(`[WuStyleBridge] ⏭️ Style already exists: ${style.library || style.href}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Clonar link tag
|
|
199
|
+
const link = document.createElement('link');
|
|
200
|
+
link.rel = 'stylesheet';
|
|
201
|
+
link.href = style.href;
|
|
202
|
+
link.setAttribute('data-wu-shared', 'true');
|
|
203
|
+
link.setAttribute('data-wu-library', style.library || 'unknown');
|
|
204
|
+
|
|
205
|
+
// Insertar al principio del shadow root (antes de otros estilos)
|
|
206
|
+
shadowRoot.insertBefore(link, shadowRoot.firstChild);
|
|
207
|
+
|
|
208
|
+
console.log(`[WuStyleBridge] 🔗 Injected link: ${style.library || style.href}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 📝 INYECTAR INLINE STYLE: Clona <style> tag al Shadow DOM
|
|
213
|
+
* @param {ShadowRoot} shadowRoot
|
|
214
|
+
* @param {Object} style
|
|
215
|
+
*/
|
|
216
|
+
injectInlineStyle(shadowRoot, style) {
|
|
217
|
+
// Verificar si ya existe
|
|
218
|
+
const viteId = style.viteId;
|
|
219
|
+
if (viteId) {
|
|
220
|
+
const existing = shadowRoot.querySelector(`style[data-wu-vite-id="${viteId}"]`);
|
|
221
|
+
if (existing) {
|
|
222
|
+
console.log(`[WuStyleBridge] ⏭️ Inline style already exists: ${viteId}`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Crear nuevo style tag
|
|
228
|
+
const styleTag = document.createElement('style');
|
|
229
|
+
styleTag.textContent = style.content;
|
|
230
|
+
styleTag.setAttribute('data-wu-shared', 'true');
|
|
231
|
+
styleTag.setAttribute('data-wu-library', style.library || 'unknown');
|
|
232
|
+
if (viteId) {
|
|
233
|
+
styleTag.setAttribute('data-wu-vite-id', viteId);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Insertar al principio del shadow root
|
|
237
|
+
shadowRoot.insertBefore(styleTag, shadowRoot.firstChild);
|
|
238
|
+
|
|
239
|
+
console.log(`[WuStyleBridge] 📝 Injected inline style: ${style.library || viteId}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 📋 INYECTAR ADOPTED STYLESHEET: Comparte stylesheet constructable
|
|
244
|
+
* @param {ShadowRoot} shadowRoot
|
|
245
|
+
* @param {Object} style
|
|
246
|
+
*/
|
|
247
|
+
injectAdoptedStyleSheet(shadowRoot, style) {
|
|
248
|
+
try {
|
|
249
|
+
// Agregar stylesheet al array de adopted stylesheets
|
|
250
|
+
if (!shadowRoot.adoptedStyleSheets) {
|
|
251
|
+
shadowRoot.adoptedStyleSheets = [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Verificar si ya existe
|
|
255
|
+
if (shadowRoot.adoptedStyleSheets.includes(style.sheet)) {
|
|
256
|
+
console.log(`[WuStyleBridge] ⏭️ Adopted stylesheet already exists`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
shadowRoot.adoptedStyleSheets = [
|
|
261
|
+
...shadowRoot.adoptedStyleSheets,
|
|
262
|
+
style.sheet
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
console.log(`[WuStyleBridge] 📋 Injected adopted stylesheet`);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.warn(`[WuStyleBridge] ⚠️ Failed to inject adopted stylesheet:`, error);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 🔄 OBSERVAR CAMBIOS: Monitorea nuevos estilos en el documento
|
|
273
|
+
* @param {Function} callback - Callback cuando se detectan cambios
|
|
274
|
+
*/
|
|
275
|
+
observeStyleChanges(callback) {
|
|
276
|
+
// Limpiar observer anterior si existe
|
|
277
|
+
if (this.styleObserver) {
|
|
278
|
+
this.styleObserver.disconnect();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Crear MutationObserver para detectar nuevos estilos
|
|
282
|
+
this.styleObserver = new MutationObserver((mutations) => {
|
|
283
|
+
let hasStyleChanges = false;
|
|
284
|
+
|
|
285
|
+
for (const mutation of mutations) {
|
|
286
|
+
if (mutation.type === 'childList') {
|
|
287
|
+
// Verificar si se agregaron <link> o <style> tags
|
|
288
|
+
const addedNodes = Array.from(mutation.addedNodes);
|
|
289
|
+
const hasNewStyles = addedNodes.some(node =>
|
|
290
|
+
node.tagName === 'LINK' || node.tagName === 'STYLE'
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (hasNewStyles) {
|
|
294
|
+
hasStyleChanges = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (hasStyleChanges && callback) {
|
|
301
|
+
console.log('[WuStyleBridge] 🔄 Style changes detected');
|
|
302
|
+
callback();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Observar <head> para cambios en estilos
|
|
307
|
+
this.styleObserver.observe(document.head, {
|
|
308
|
+
childList: true,
|
|
309
|
+
subtree: true
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
console.log('[WuStyleBridge] 👀 Observing style changes');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* ⚙️ CONFIGURAR: Actualiza la configuración
|
|
317
|
+
* @param {Object} config - Nueva configuración
|
|
318
|
+
*/
|
|
319
|
+
configure(config) {
|
|
320
|
+
this.config = {
|
|
321
|
+
...this.config,
|
|
322
|
+
...config
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
console.log('[WuStyleBridge] ⚙️ Configuration updated:', this.config);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 🧹 LIMPIAR: Detiene la observación
|
|
330
|
+
*/
|
|
331
|
+
cleanup() {
|
|
332
|
+
if (this.styleObserver) {
|
|
333
|
+
this.styleObserver.disconnect();
|
|
334
|
+
this.styleObserver = null;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
this.sharedStyles.clear();
|
|
338
|
+
console.log('[WuStyleBridge] 🧹 StyleBridge cleaned up');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 📊 OBTENER ESTADÍSTICAS: Información sobre estilos compartidos
|
|
343
|
+
* @returns {Object}
|
|
344
|
+
*/
|
|
345
|
+
getStats() {
|
|
346
|
+
const styles = this.detectDocumentStyles();
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
totalStyles: styles.length,
|
|
350
|
+
linkStyles: styles.filter(s => s.type === 'link').length,
|
|
351
|
+
inlineStyles: styles.filter(s => s.type === 'inline').length,
|
|
352
|
+
adoptedStyleSheets: styles.filter(s => s.type === 'adoptedStyleSheet').length,
|
|
353
|
+
libraries: [...new Set(styles.map(s => s.library).filter(Boolean))],
|
|
354
|
+
config: this.config
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}
|