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.
@@ -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
+ }