- Published
Refactoring Old React Project To Wujie
- Authors

- Name
- Xu Zhiyi
Micro-Frontend Evolution: Rebuilding From iframe to Wujie
Background
Core hosts more than ten business modules. In the early days, we used iframes to embed independent subsystems quickly. As the product grew, the iframe approach started showing serious weaknesses:
- Routing and state often fell out of sync; cross-module navigation frequently triggered full-page reloads.
- DOM/CSS tweaks had to be injected manually for each scenario—mobile, dialogs, sidebars—leading to endless hacks.
- Security headers (tfsgv, JSEncrypt) were injected ad hoc in every iframe, making governance difficult.
- Loading, timeout handling, and exception UX were all tangled in one giant component, driving up maintenance cost.
Legacy Implementation: A Powerful but Bloated SubWindow
src/components/functions/subWindow/SubWindow.tsx renders each subsystem via <iframe>:
- Communication: It exposes
window._client/historyPush, manually parses routes, and keeps the host menu in sync—a high-coupling design. - UI Manipulation: Direct DOM scripting inside the iframe hides sidebars, overrides backgrounds, and adjusts
z-index; mobile mode even usesMutationObserver. - Security Injection: On iframe load, scripts rewrite XHR/FETCH to add tfsgv, each page duplicating the logic.
- Exception Handling: Loading states, 10-second timeouts, and modal prompts are all managed inside the component.
- Compatibility Layer: Extra code adapts Angular-era breadcrumbs,
?_timestamp,fcparams, etc.
It works, but everything is bolted on: responsibilities blur, the code is hard to evolve, iframes pull in full resource trees, and first paint stays relatively slow.
New Architecture: Wujie-Powered Container
The new implementation in src/components/new/wujie/ follows a “container component + plugin chain + Redux cooperation” pattern.
1. Lightweight Container (index.tsx)
- Uses
WujieReactto render sub apps, supportingalive(keep-alive) andreload. - Leverages Wujie’s event bus:
sub-route-change: readsfunctionNameMapto auto-fill tab names, i18n text, and detail suffixes.sub-route-replace: updates Reduxtabs/currTabandhistory.pushfor seamless replacements.
- Exposes
props2.jumpso sub apps can trigger host routing via props instead of global variables.
2. Controlled Lifecycle (wujie.tsx)
- Delegates
startApp/destroyApptowujie;window.__WUJIE_QUEUEensures ordered loading for same-name apps. reloadtears down and restarts the sandbox with clean state.- The component renders only a div container—no direct iframe manipulation.
3. Unified Plugin Chain (plugin.ts)
- Pulls in
wujie-polyfillto support Selection, Fullscreen, WindowMessage, etc., inside the sandbox. - Custom plugins inject
jsencrypt.min.js, tfsgv headers, and interceptwindow.open—all defined once, reused everywhere. - CSS plugins inject tweaks (e.g.
.ant-layout-sider{display:none}) before any child styles load, replacing imperative DOM hacks.
4. Redux & Event-Driven Coordination
useTabsMenuhandles tab creation and naming; Wujie only emits events and stays out of business rules.- Redux is the single source of truth for host and sub-app menu/tab state, improving observability and consistency.
Architecture Comparison
| Aspect | iframe Approach | Wujie Approach |
|---|---|---|
| Route Sync | Global window helpers; frequent full reloads | Event bus + Redux/Router, no refresh |
| DOM/CSS | querySelector + style overrides | Configurable plugin injection |
| Security | Per-iframe scripts, duplicated | Centralized plugin chain |
| Code Structure | One component owns everything | Container / plugin / Redux separation |
| Extensibility | Copy massive template per module | Configure name / url / props only |
| UX | Flashing screens, inconsistent tabs | Smooth switching, aligned tabs |
Migration Strategy & Lessons
- Dual directory: Legacy code stays in
components/functions; the new container lives incomponents/newfor incremental rollout. - functionNameMap compatibility: Keep using
sessionStorage.functionNameMapso menu names stay consistent during transition. - Event protocol: Sub apps gradually replace
window.historyPushwithwindow.$wujie.bus.$emit(...). - Plugin governance: Centralize security and UI patches in plugins for unified upgrades and troubleshooting.
Challenges
1. window.open navigation
- Challenge: Sub apps that call
window.openspawn new browser tabs; after the redesign we prefer internal tabs instead of new browser tabs. - Approach: Minimize business churn—register events, or patch
window.open? - Solution: In
plugin.ts, overridewindow.open. For URLs not on the allowlist, emit on the bus and route viabus.$emit("sub-route-change", ...), then fall back to the nativewindow.openelsewhere. Exposeprops.jumpwhen sub apps need an explicit escape hatch.
jsBeforeLoaders: [
{
content: `var originalWindowOpen = window.open;
window.open = (url, windowName, windowFeatures) => {
if (!["whiteList"].some(item => url.includes(item))) {
window.$wujie.bus.$emit("sub-route-change", "windowOpen路径", url);
} else {
originalWindowOpen(url, windowName, windowFeatures);
}
};`,
},
]
2. Sub-app DOM hacks
- Challenge: Sidebars lived inside nested iframes and were visually covered by the host chrome, so they looked “invisible.” After migration, sidebars show up again.
- Approach: Can we patch Web Components with minimal change? If not, do we push visibility flags into every sub app?
- Solution: Centralize rules in
cssBeforeLoadersso sub apps stop mutating DOM by hand.
cssBeforeLoaders: [
// Inline styles injected before any HTML-linked styles
{ content: ".ant-layout-sider{display:none}" },
{
content:
"[class*='drawer_isFullDrawer'] { .ant-drawer-content-wrapper { width: 100% !important;}}",
},
// { content: "html{padding-top: 70px;height:100%}" },
]
3. DLL runtime error
- Challenge:
Uncaught ReferenceError: _dll_bundle_ is not defined. - Hypothesis: The DLL symbol used to live only inside the iframe sandbox; mounting as a Web Component broke the expected scope.
- Solution: In
jsLoader, rewritebundle.dll.jsto hang the bundle onwindow(blunt but effective—tighten per environment).
jsLoader: (code: any, url: any) => {
if (url.includes("bundle.dll.js"))
return code.replace("var _dll_bundle", "window._dll_bundle");
return code;
}
4. Many sub-app tabs → growing memory
- Challenge: The product is tab-based; users can open many tabs in principle, and sub apps often run with
alivekeep-alive. - Insight: Keep-alive usually means one cached instance per sub app. The growth often looks like retention from isolation + caching, not a classic leak.
- Mitigation: Use an LCU-style policy to reclaim memory for idle tabs and cap open tabs (e.g. 20). Calling
destroyApphelps, but the drop is modest—we still saw on the order of ~2GB with 20 tabs, so we are continuing to profile and tune.
Benefits & Next Steps
- Experience: Route transitions feel native; tabs stay synchronized without flashing.
- Velocity: Onboarding new subsystems is far simpler; QA handoffs shrink.
- Governance: Security injections and polyfills are centralized and auditable.
Upcoming work:
- Provide onboarding scaffolds that standardize bus events and lifecycle hooks.
- Move functionNameMap / menu metadata into a backend config service.
- Add instrumentation for bus events and plugin execution to diagnose sub-app issues quickly.
Switching to Wujie is more than replacing iframes—it turns our micro-frontend container into a configurable, observable, and evolvable platform. Hopefully these lessons help guide future architecture upgrades.