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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔗 WU-DEPENDENCY-RESOLVER: SISTEMA DE RESOLUCIÓN DE DEPENDENCIAS
|
|
3
|
+
* Maneja imports/exports entre micro-apps
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class WuDependencyResolver {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.resolvedComponents = new Map();
|
|
9
|
+
this.pendingResolutions = new Map();
|
|
10
|
+
|
|
11
|
+
console.log('[WuDependencyResolver] 🔗 Dependency resolver initialized');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolver todas las dependencias de una app
|
|
16
|
+
* @param {Array} imports - Lista de imports del manifest
|
|
17
|
+
* @param {Map} availableApps - Apps disponibles
|
|
18
|
+
* @returns {Map} Componentes resueltos
|
|
19
|
+
*/
|
|
20
|
+
async resolveAll(imports, availableApps) {
|
|
21
|
+
console.log(`[WuDependencyResolver] 🔍 Resolving ${imports.length} dependencies...`);
|
|
22
|
+
|
|
23
|
+
const resolved = new Map();
|
|
24
|
+
const errors = [];
|
|
25
|
+
|
|
26
|
+
for (const importPath of imports) {
|
|
27
|
+
try {
|
|
28
|
+
const component = await this.resolve(importPath, availableApps);
|
|
29
|
+
resolved.set(importPath, component);
|
|
30
|
+
console.log(`[WuDependencyResolver] ✅ Resolved: ${importPath}`);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
errors.push({ import: importPath, error: error.message });
|
|
33
|
+
console.warn(`[WuDependencyResolver] ❌ Failed to resolve: ${importPath}`, error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (errors.length > 0) {
|
|
38
|
+
console.warn(`[WuDependencyResolver] ⚠️ ${errors.length} dependencies failed to resolve:`, errors);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`[WuDependencyResolver] 🎉 Resolved ${resolved.size}/${imports.length} dependencies`);
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Resolver una dependencia específica
|
|
47
|
+
* @param {string} importPath - Ruta de import (ej: "shared.Button")
|
|
48
|
+
* @param {Map} availableApps - Apps disponibles
|
|
49
|
+
* @returns {Function} Componente resuelto
|
|
50
|
+
*/
|
|
51
|
+
async resolve(importPath, availableApps) {
|
|
52
|
+
// Verificar cache
|
|
53
|
+
if (this.resolvedComponents.has(importPath)) {
|
|
54
|
+
console.log(`[WuDependencyResolver] ⚡ Cache hit: ${importPath}`);
|
|
55
|
+
return this.resolvedComponents.get(importPath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Verificar si ya está resolviendo
|
|
59
|
+
if (this.pendingResolutions.has(importPath)) {
|
|
60
|
+
console.log(`[WuDependencyResolver] ⏳ Waiting for pending resolution: ${importPath}`);
|
|
61
|
+
return await this.pendingResolutions.get(importPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Crear promesa de resolución
|
|
65
|
+
const resolutionPromise = this.performResolution(importPath, availableApps);
|
|
66
|
+
this.pendingResolutions.set(importPath, resolutionPromise);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const component = await resolutionPromise;
|
|
70
|
+
this.pendingResolutions.delete(importPath);
|
|
71
|
+
this.resolvedComponents.set(importPath, component);
|
|
72
|
+
return component;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.pendingResolutions.delete(importPath);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Realizar la resolución efectiva
|
|
81
|
+
* @param {string} importPath - Ruta de import
|
|
82
|
+
* @param {Map} availableApps - Apps disponibles
|
|
83
|
+
* @returns {Function} Componente resuelto
|
|
84
|
+
*/
|
|
85
|
+
async performResolution(importPath, availableApps) {
|
|
86
|
+
const [appName, componentName] = importPath.split('.');
|
|
87
|
+
|
|
88
|
+
if (!appName || !componentName) {
|
|
89
|
+
throw new Error(`Invalid import format: ${importPath}. Use "app.component"`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Buscar la app
|
|
93
|
+
const app = availableApps.get(appName);
|
|
94
|
+
if (!app) {
|
|
95
|
+
throw new Error(`App not found: ${appName}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Buscar el export en el manifest
|
|
99
|
+
const manifest = app.manifest;
|
|
100
|
+
const exportPath = manifest?.wu?.exports?.[componentName];
|
|
101
|
+
|
|
102
|
+
if (!exportPath) {
|
|
103
|
+
throw new Error(`Component ${componentName} not exported by ${appName}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Cargar el componente usando el loader de la app
|
|
107
|
+
const loader = app.loader || this.getDefaultLoader();
|
|
108
|
+
const component = await loader.loadComponent(app.url, exportPath);
|
|
109
|
+
|
|
110
|
+
if (!component) {
|
|
111
|
+
throw new Error(`Failed to load component: ${importPath}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return component;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Obtener loader por defecto si la app no tiene uno
|
|
119
|
+
*/
|
|
120
|
+
getDefaultLoader() {
|
|
121
|
+
if (!this._defaultLoader) {
|
|
122
|
+
// Crear un loader básico si no hay uno disponible
|
|
123
|
+
this._defaultLoader = {
|
|
124
|
+
loadComponent: async (appUrl, componentPath) => {
|
|
125
|
+
const fullUrl = `${appUrl}/${componentPath}`;
|
|
126
|
+
const response = await fetch(fullUrl);
|
|
127
|
+
|
|
128
|
+
if (!response.ok) {
|
|
129
|
+
throw new Error(`Failed to fetch: ${fullUrl}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const code = await response.text();
|
|
133
|
+
|
|
134
|
+
// Evaluar código del componente
|
|
135
|
+
const componentFunction = new Function('require', 'module', 'exports', `
|
|
136
|
+
${code}
|
|
137
|
+
return typeof module.exports === 'function' ? module.exports :
|
|
138
|
+
typeof module.exports === 'object' && module.exports.default ? module.exports.default :
|
|
139
|
+
exports.default || exports;
|
|
140
|
+
`);
|
|
141
|
+
|
|
142
|
+
const fakeModule = { exports: {} };
|
|
143
|
+
const fakeRequire = () => ({});
|
|
144
|
+
|
|
145
|
+
return componentFunction(fakeRequire, fakeModule, fakeModule.exports);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return this._defaultLoader;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validar dependencias de una app
|
|
154
|
+
* @param {Array} imports - Lista de imports
|
|
155
|
+
* @param {Map} availableApps - Apps disponibles
|
|
156
|
+
* @returns {Object} Resultado de validación
|
|
157
|
+
*/
|
|
158
|
+
validate(imports, availableApps) {
|
|
159
|
+
const result = {
|
|
160
|
+
valid: [],
|
|
161
|
+
invalid: [],
|
|
162
|
+
missing: [],
|
|
163
|
+
circular: []
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const importPath of imports) {
|
|
167
|
+
const [appName, componentName] = importPath.split('.');
|
|
168
|
+
|
|
169
|
+
// Validar formato
|
|
170
|
+
if (!appName || !componentName) {
|
|
171
|
+
result.invalid.push({
|
|
172
|
+
import: importPath,
|
|
173
|
+
reason: 'Invalid format. Use "app.component"'
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Validar que la app existe
|
|
179
|
+
const app = availableApps.get(appName);
|
|
180
|
+
if (!app) {
|
|
181
|
+
result.missing.push({
|
|
182
|
+
import: importPath,
|
|
183
|
+
app: appName,
|
|
184
|
+
reason: 'App not registered'
|
|
185
|
+
});
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Validar que el export existe
|
|
190
|
+
const manifest = app.manifest;
|
|
191
|
+
const exportExists = manifest?.wu?.exports?.[componentName];
|
|
192
|
+
|
|
193
|
+
if (!exportExists) {
|
|
194
|
+
result.invalid.push({
|
|
195
|
+
import: importPath,
|
|
196
|
+
reason: `Component ${componentName} not exported by ${appName}`
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
result.valid.push({
|
|
202
|
+
import: importPath,
|
|
203
|
+
app: appName,
|
|
204
|
+
component: componentName,
|
|
205
|
+
path: exportExists
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Detectar dependencias circulares
|
|
210
|
+
result.circular = this.detectCircularDependencies(imports, availableApps);
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Detectar dependencias circulares
|
|
217
|
+
* @param {Array} imports - Lista de imports
|
|
218
|
+
* @param {Map} availableApps - Apps disponibles
|
|
219
|
+
* @returns {Array} Dependencias circulares encontradas
|
|
220
|
+
*/
|
|
221
|
+
detectCircularDependencies(imports, availableApps) {
|
|
222
|
+
const circular = [];
|
|
223
|
+
const visited = new Set();
|
|
224
|
+
const visiting = new Set();
|
|
225
|
+
|
|
226
|
+
const visit = (appName, path = []) => {
|
|
227
|
+
if (visiting.has(appName)) {
|
|
228
|
+
// Encontrada dependencia circular
|
|
229
|
+
const circularPath = [...path, appName];
|
|
230
|
+
circular.push(circularPath);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (visited.has(appName)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
visiting.add(appName);
|
|
239
|
+
const currentPath = [...path, appName];
|
|
240
|
+
|
|
241
|
+
// Buscar las dependencias de esta app
|
|
242
|
+
const app = availableApps.get(appName);
|
|
243
|
+
if (app && app.manifest?.wu?.imports) {
|
|
244
|
+
for (const importPath of app.manifest.wu.imports) {
|
|
245
|
+
const [depAppName] = importPath.split('.');
|
|
246
|
+
if (depAppName) {
|
|
247
|
+
visit(depAppName, currentPath);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
visiting.delete(appName);
|
|
253
|
+
visited.add(appName);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Visitar todas las apps
|
|
257
|
+
for (const app of availableApps.keys()) {
|
|
258
|
+
visit(app);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return circular;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Crear wrapper para componente compartido
|
|
266
|
+
* @param {Function} component - Componente original
|
|
267
|
+
* @param {string} importPath - Ruta de import
|
|
268
|
+
* @returns {Function} Componente wrapper
|
|
269
|
+
*/
|
|
270
|
+
createComponentWrapper(component, importPath) {
|
|
271
|
+
return function WuSharedComponent(props) {
|
|
272
|
+
console.log(`[WuDependencyResolver] 🧩 Rendering shared component: ${importPath}`);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
return component(props);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(`[WuDependencyResolver] ❌ Error in shared component ${importPath}:`, error);
|
|
278
|
+
|
|
279
|
+
// Componente de error fallback
|
|
280
|
+
return {
|
|
281
|
+
type: 'div',
|
|
282
|
+
props: {
|
|
283
|
+
style: {
|
|
284
|
+
padding: '10px',
|
|
285
|
+
border: '1px solid #ff6b6b',
|
|
286
|
+
borderRadius: '4px',
|
|
287
|
+
background: '#ffe0e0',
|
|
288
|
+
color: '#d63031'
|
|
289
|
+
},
|
|
290
|
+
children: `Error in shared component: ${importPath}`
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Limpiar cache de dependencias
|
|
299
|
+
* @param {string} pattern - Patrón opcional
|
|
300
|
+
*/
|
|
301
|
+
clearCache(pattern) {
|
|
302
|
+
if (pattern) {
|
|
303
|
+
const regex = new RegExp(pattern);
|
|
304
|
+
for (const [importPath] of this.resolvedComponents) {
|
|
305
|
+
if (regex.test(importPath)) {
|
|
306
|
+
this.resolvedComponents.delete(importPath);
|
|
307
|
+
console.log(`[WuDependencyResolver] 🗑️ Cleared cache for: ${importPath}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
this.resolvedComponents.clear();
|
|
312
|
+
console.log(`[WuDependencyResolver] 🗑️ Dependency cache cleared completely`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Obtener estadísticas del resolver
|
|
318
|
+
*/
|
|
319
|
+
getStats() {
|
|
320
|
+
return {
|
|
321
|
+
resolved: this.resolvedComponents.size,
|
|
322
|
+
pending: this.pendingResolutions.size,
|
|
323
|
+
components: Array.from(this.resolvedComponents.keys())
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|