nx icon indicating copy to clipboard operation
nx copied to clipboard

perf(module-federation): optimize performance with caching and algorithm improvements

Open adwait1290 opened this issue 6 days ago • 3 comments

Current Behavior

The module federation utilities have several performance inefficiencies and code duplication:

Performance Issues

┌─────────────────────────────────────────────────────────────────────────────┐
│                    BEFORE: Performance Bottlenecks                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Build Start                                                                 │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ For each remote (10+ times):         │                                   │
│  │   • JSON.parse(env variable)  ──────►│ Repeated parsing!                 │
│  │   • readRootPackageJson()     ──────►│ Repeated file I/O!                │
│  └──────────────────────────────────────┘                                   │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ For each library in dependency tree: │                                   │
│  │   • readTsPathMappings()      ──────►│ Called in loop!                   │
│  │   • Array.find() for lookup   ──────►│ O(n) per lookup!                  │
│  └──────────────────────────────────────┘                                   │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ For EVERY import during build:       │                                   │
│  │   • dirname(library.path)     ──────►│ Hot path!                         │
│  │   • normalize(...)            ──────►│ Computed every time!              │
│  └──────────────────────────────────────┘                                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Code Duplication

┌─────────────────────────────────────────────────────────────────────────────┐
│                    BEFORE: Duplicated Code (~400 lines)                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  webpack/utils.ts ─────┐                                                     │
│                        │                                                     │
│  rspack/utils.ts  ─────┼─► getFunctionDeterminateRemoteUrl() (~38 lines x 3) │
│                        │   • Same logic, different remoteEntry extension     │
│  angular/utils.ts ─────┘                                                     │
│                                                                              │
│  ────────────────────────────────────────────────────────────────────────   │
│                                                                              │
│  plugins/utils/start-remote-proxies.ts ─┐                                    │
│                                         │                                    │
│  utils/start-remote-proxies.ts ─────────┼─► Proxy server setup (~50 lines x 3)│
│                                         │   • SSL handling                   │
│  utils/start-ssr-remote-proxies.ts ─────┘   • Express + middleware           │
│                                             • Process signal handlers        │
│                                                                              │
│  ────────────────────────────────────────────────────────────────────────   │
│                                                                              │
│  parseStaticRemotesConfig() ────┐                                            │
│                                 ├─► 94% identical code (~35 lines x 2)       │
│  parseStaticSsrRemotesConfig() ─┘   • Only diff: dirname(outputPath) for SSR │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Expected Behavior

┌─────────────────────────────────────────────────────────────────────────────┐
│                    AFTER: Optimized Architecture                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Build Start                                                                 │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ Cached Operations (computed once):   │                                   │
│  │   • getStaticRemotesFromEnv() ──────►│ Cached with invalidation          │
│  │   • readRootPackageJson()     ──────►│ Cached                            │
│  │   • readTsPathMappings()      ──────►│ Hoisted outside loops             │
│  │   • getDependentPackages()    ──────►│ WeakMap memoization               │
│  └──────────────────────────────────────┘                                   │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ Pre-computed Values:                 │                                   │
│  │   • workspaceLibsByImportKey  ──────►│ Map for O(1) lookup               │
│  │   • keyDepths Map             ──────►│ Pre-computed for sorting          │
│  │   • pathMappings[].libFolder  ──────►│ Pre-computed dirname/normalize    │
│  └──────────────────────────────────────┘                                   │
│      │                                                                       │
│      ▼                                                                       │
│  ┌──────────────────────────────────────┐                                   │
│  │ Hot Path (every import):             │                                   │
│  │   • Use pre-computed libFolder ─────►│ No computation needed!            │
│  └──────────────────────────────────────┘                                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Shared Utilities

┌─────────────────────────────────────────────────────────────────────────────┐
│                    AFTER: Consolidated Code                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  NEW: utils/remote-url.ts                                                    │
│  ┌─────────────────────────────────────────────────┐                        │
│  │ createRemoteUrlResolver({ isServer, bundler })  │                        │
│  │   • Cached getStaticRemotesFromEnv()            │                        │
│  │   • Bundler-specific remoteEntry extension      │                        │
│  └─────────────────────────────────────────────────┘                        │
│           │                                                                  │
│           ├─► webpack/utils.ts  (3 lines)                                    │
│           ├─► rspack/utils.ts   (3 lines)                                    │
│           └─► angular/utils.ts  (4 lines)                                    │
│                                                                              │
│  ────────────────────────────────────────────────────────────────────────   │
│                                                                              │
│  NEW: utils/proxy-server.ts                                                  │
│  ┌─────────────────────────────────────────────────┐                        │
│  │ createRemoteProxyServers(remotes, options)      │                        │
│  │   • SSL certificate handling                    │                        │
│  │   • Express server + proxy middleware           │                        │
│  │   • Process signal handlers                     │                        │
│  └─────────────────────────────────────────────────┘                        │
│           │                                                                  │
│           ├─► plugins/utils/start-remote-proxies.ts  (10 lines)              │
│           ├─► utils/start-remote-proxies.ts          (10 lines)              │
│           └─► utils/start-ssr-remote-proxies.ts      (10 lines)              │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Changes Made

Performance Optimizations

# Optimization File(s) Impact
1 Cache env variable parsing webpack, rspack, angular utils Eliminates 10+ JSON.parse per build
2 Pre-compute sort depths share.ts O(n) vs O(n log n) string splits
3 Map for O(1) lookups share.ts O(1) vs O(n) per lookup
4 Memoize secondary entry points secondary-entry-points.ts Avoids repeated dir scans
5 Cache readRootPackageJson package-json.ts Single file read per build
6 Hoist tsConfigPathMappings dependencies.ts Avoids repeated calls in loop
7 Memoize getDependentPackages dependencies.ts WeakMap with auto-invalidation
8 Pre-compute libFolder share.ts No hot-path computation

Code Deduplication

# Consolidation Lines Removed New Shared Module
9 Remote URL resolver ~80 lines utils/remote-url.ts
10 Proxy server setup ~120 lines utils/proxy-server.ts
11 Parse remotes config ~35 lines Shared core function

Bug Fix

# Fix File Issue
12 require.resolve error handling share.ts Was checking for null, but it throws

Test Results

Test Suites: 10 passed, 10 total
Tests:       1 skipped, 56 passed, 57 total

Summary

 13 files changed
+ 346 insertions
- 357 deletions
──────────────────
  -11 lines net (with MORE functionality!)

New shared utilities:

  • packages/module-federation/src/utils/proxy-server.ts
  • packages/module-federation/src/utils/remote-url.ts

Related Issue(s)

Performance improvements for module federation - no specific issue

Merge Dependencies

Must be merged AFTER: #33733 Must be merged BEFORE: #33735


adwait1290 avatar Dec 08 '25 02:12 adwait1290