wu-framework 1.1.12 → 1.1.14
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/dist/wu-framework.cjs.js +1 -1
- package/dist/wu-framework.cjs.js.map +1 -1
- package/dist/wu-framework.dev.js +91 -17
- 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 +1 -1
- package/src/adapters/react/index.js +5 -18
- package/src/core/wu-core.js +91 -17
package/package.json
CHANGED
|
@@ -174,7 +174,7 @@ async function register(appName, Component, options = {}) {
|
|
|
174
174
|
return;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// Si ya está montado en el MISMO container, ignorar
|
|
177
|
+
// Si ya está montado en el MISMO container, ignorar
|
|
178
178
|
const existing = adapterState.roots.get(appName);
|
|
179
179
|
if (existing) {
|
|
180
180
|
if (existing.container === container) {
|
|
@@ -185,15 +185,9 @@ async function register(appName, Component, options = {}) {
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
try {
|
|
188
|
-
// Limpiar el DOM del container antes de crear un nuevo root.
|
|
189
|
-
// En React 18+, root.unmount() es async — si se llama createRoot()
|
|
190
|
-
// en el mismo container antes de que termine, falla silenciosamente.
|
|
191
|
-
// Limpiar innerHTML fuerza un DOM limpio para el nuevo root.
|
|
192
188
|
container.innerHTML = '';
|
|
193
|
-
|
|
194
189
|
const root = createRoot(container);
|
|
195
190
|
|
|
196
|
-
// Crear elemento con o sin StrictMode
|
|
197
191
|
let element = React.createElement(Component, props);
|
|
198
192
|
if (strictMode && React.StrictMode) {
|
|
199
193
|
element = React.createElement(React.StrictMode, null, element);
|
|
@@ -211,28 +205,21 @@ async function register(appName, Component, options = {}) {
|
|
|
211
205
|
}
|
|
212
206
|
};
|
|
213
207
|
|
|
214
|
-
//
|
|
208
|
+
// Unmount inmediato — la protección contra StrictMode (deferred unmount)
|
|
209
|
+
// se maneja en wu-core.js, no aquí en el adapter.
|
|
215
210
|
const unmountApp = (container) => {
|
|
216
211
|
const instance = adapterState.roots.get(appName);
|
|
217
|
-
|
|
218
212
|
if (instance) {
|
|
219
213
|
try {
|
|
220
|
-
if (onUnmount)
|
|
221
|
-
onUnmount(instance.container);
|
|
222
|
-
}
|
|
223
|
-
|
|
214
|
+
if (onUnmount) onUnmount(instance.container);
|
|
224
215
|
instance.root.unmount();
|
|
225
216
|
adapterState.roots.delete(appName);
|
|
226
217
|
} catch (error) {
|
|
227
218
|
console.error(`[WuReact] Unmount error for ${appName}:`, error);
|
|
228
219
|
}
|
|
229
220
|
}
|
|
230
|
-
|
|
231
|
-
// Limpiar container
|
|
232
221
|
const target = container || instance?.container;
|
|
233
|
-
if (target)
|
|
234
|
-
target.innerHTML = '';
|
|
235
|
-
}
|
|
222
|
+
if (target) target.innerHTML = '';
|
|
236
223
|
};
|
|
237
224
|
|
|
238
225
|
// Intentar registrar con Wu Framework
|
package/src/core/wu-core.js
CHANGED
|
@@ -30,6 +30,8 @@ export class WuCore {
|
|
|
30
30
|
this.manifests = new Map(); // Manifiestos cargados
|
|
31
31
|
this.mounted = new Map(); // Apps montadas
|
|
32
32
|
this.hidden = new Map(); // Keep-alive hidden apps
|
|
33
|
+
this._pendingUnmounts = new Map(); // Deferred unmount timers (StrictMode compat)
|
|
34
|
+
this._mountingPromises = new Map(); // In-flight mount dedup
|
|
33
35
|
|
|
34
36
|
// Componentes core
|
|
35
37
|
this.loader = new WuLoader();
|
|
@@ -178,6 +180,31 @@ export class WuCore {
|
|
|
178
180
|
* @param {string} containerSelector - Selector del contenedor
|
|
179
181
|
*/
|
|
180
182
|
async mount(appName, containerSelector) {
|
|
183
|
+
// ── StrictMode guard: cancel pending deferred unmount ──
|
|
184
|
+
// React StrictMode cycle: effect(mount) → cleanup(unmount) → effect(mount)
|
|
185
|
+
// The cleanup fires between two mounts. By deferring the actual unmount,
|
|
186
|
+
// the second mount cancels it and the app stays alive — zero flicker.
|
|
187
|
+
if (this._pendingUnmounts.has(appName)) {
|
|
188
|
+
clearTimeout(this._pendingUnmounts.get(appName));
|
|
189
|
+
this._pendingUnmounts.delete(appName);
|
|
190
|
+
logger.wuDebug(`${appName} deferred unmount cancelled by remount`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Already mounted in same container → no-op
|
|
194
|
+
if (this.mounted.has(appName)) {
|
|
195
|
+
const existing = this.mounted.get(appName);
|
|
196
|
+
if (existing.containerSelector === containerSelector) {
|
|
197
|
+
logger.wuDebug(`${appName} already mounted in ${containerSelector}`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Deduplicate concurrent mounts (StrictMode fires effect twice)
|
|
203
|
+
if (this._mountingPromises.has(appName)) {
|
|
204
|
+
logger.wuDebug(`${appName} mount already in progress, deduplicating`);
|
|
205
|
+
return await this._mountingPromises.get(appName);
|
|
206
|
+
}
|
|
207
|
+
|
|
181
208
|
// Check if app is in keep-alive (hidden) state
|
|
182
209
|
const hiddenEntry = this.hidden.get(appName);
|
|
183
210
|
if (hiddenEntry) {
|
|
@@ -189,7 +216,15 @@ export class WuCore {
|
|
|
189
216
|
await this._destroyHidden(appName);
|
|
190
217
|
}
|
|
191
218
|
|
|
192
|
-
|
|
219
|
+
// Track mount promise for deduplication
|
|
220
|
+
const mountPromise = this.mountWithRecovery(appName, containerSelector, 0);
|
|
221
|
+
this._mountingPromises.set(appName, mountPromise);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
return await mountPromise;
|
|
225
|
+
} finally {
|
|
226
|
+
this._mountingPromises.delete(appName);
|
|
227
|
+
}
|
|
193
228
|
}
|
|
194
229
|
|
|
195
230
|
/**
|
|
@@ -840,28 +875,60 @@ export class WuCore {
|
|
|
840
875
|
* @param {boolean} [options.force] - Force destroy even if keepAlive
|
|
841
876
|
*/
|
|
842
877
|
async unmount(appName, options = {}) {
|
|
843
|
-
|
|
844
|
-
logger.wuDebug(`Unmounting ${appName}`);
|
|
878
|
+
logger.wuDebug(`Unmounting ${appName}`);
|
|
845
879
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
852
|
-
logger.wuWarn(`App ${appName} not mounted`);
|
|
853
|
-
return;
|
|
880
|
+
const mounted = this.mounted.get(appName);
|
|
881
|
+
if (!mounted) {
|
|
882
|
+
// Check if it's hidden (keep-alive) — force destroy if requested
|
|
883
|
+
if (options.force && this.hidden.has(appName)) {
|
|
884
|
+
return await this._destroyHidden(appName);
|
|
854
885
|
}
|
|
886
|
+
logger.wuWarn(`App ${appName} not mounted`);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
855
889
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
890
|
+
// Resolve keepAlive: per-call > per-app config > default false
|
|
891
|
+
const keepAlive = options.force
|
|
892
|
+
? false
|
|
893
|
+
: (options.keepAlive ?? mounted.app?.keepAlive ?? false);
|
|
860
894
|
|
|
861
|
-
|
|
862
|
-
|
|
895
|
+
if (keepAlive) {
|
|
896
|
+
return await this.hide(appName);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Force → immediate unmount (no deferral)
|
|
900
|
+
if (options.force) {
|
|
901
|
+
return await this._executeUnmount(appName, mounted);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// ── Deferred unmount: 60ms window for React StrictMode ──
|
|
905
|
+
// StrictMode cycle: effect(mount) → cleanup(unmount) → effect(mount)
|
|
906
|
+
// The cleanup fires between two mounts. By deferring the actual unmount,
|
|
907
|
+
// the second mount() call cancels the timer and the app stays alive.
|
|
908
|
+
if (this._pendingUnmounts.has(appName)) {
|
|
909
|
+
clearTimeout(this._pendingUnmounts.get(appName));
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
this._pendingUnmounts.set(appName, setTimeout(async () => {
|
|
913
|
+
this._pendingUnmounts.delete(appName);
|
|
914
|
+
// Re-verify: only unmount if the same mount entry is still current
|
|
915
|
+
if (this.mounted.has(appName) && this.mounted.get(appName) === mounted) {
|
|
916
|
+
try {
|
|
917
|
+
await this._executeUnmount(appName, mounted);
|
|
918
|
+
} catch (error) {
|
|
919
|
+
logger.wuError(`Deferred unmount failed for ${appName}:`, error);
|
|
920
|
+
}
|
|
863
921
|
}
|
|
922
|
+
}, 60));
|
|
923
|
+
}
|
|
864
924
|
|
|
925
|
+
/**
|
|
926
|
+
* Execute the actual unmount immediately (no deferral).
|
|
927
|
+
* Called by the deferred timer, force unmount, or destroy.
|
|
928
|
+
* @private
|
|
929
|
+
*/
|
|
930
|
+
async _executeUnmount(appName, mounted) {
|
|
931
|
+
try {
|
|
865
932
|
// Execute beforeUnmount hooks
|
|
866
933
|
const beforeUnmountResult = await this.hooks.execute('beforeUnmount', { appName, mounted });
|
|
867
934
|
if (beforeUnmountResult.cancelled) {
|
|
@@ -1277,6 +1344,13 @@ export class WuCore {
|
|
|
1277
1344
|
// Call plugin onDestroy hooks
|
|
1278
1345
|
await this.pluginSystem.callHook('onDestroy', {});
|
|
1279
1346
|
|
|
1347
|
+
// Cancel all pending deferred unmounts
|
|
1348
|
+
for (const timer of this._pendingUnmounts.values()) {
|
|
1349
|
+
clearTimeout(timer);
|
|
1350
|
+
}
|
|
1351
|
+
this._pendingUnmounts.clear();
|
|
1352
|
+
this._mountingPromises.clear();
|
|
1353
|
+
|
|
1280
1354
|
// Force-destroy all hidden (keep-alive) apps first
|
|
1281
1355
|
for (const appName of [...this.hidden.keys()]) {
|
|
1282
1356
|
await this._destroyHidden(appName);
|