10000 GitHub - eurusik/ngx-mf-remote-loader: SSR-compatible remote module loader for Angular Micro Frontends. Seamlessly load remote modules in both browser and server environments with a unified API. Built for Angular 17+ with Nx support.
[go: up one dir, main page]

Skip to content

SSR-compatible remote module loader for Angular Micro Frontends. Seamlessly load remote modules in both browser and server environments with a unified API. Built for Angular 17+ with Nx support.

Notifications You must be signed in to change notification settings

eurusik/ngx-mf-remote-loader

.hUCRAk{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}/*!sc*/ data-styled.g1[id="Box-sc-g0xbh4-0"]{content:"hUCRAk,"}/*!sc*/ .hWlpPn{position:relative;display:inline-block;}/*!sc*/ .hWlpPn::after{position:absolute;z-index:1000000;display:none;padding:0.5em 0.75em;font:normal normal 11px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";-webkit-font-smoothing:subpixel-antialiased;color:var(--tooltip-fgColor,var(--fgColor-onEmphasis,var(--color-fg-on-emphasis,#ffffff)));text-align:center;-webkit-text-decoration:none;text-decoration:none;text-shadow:none;text-transform:none;-webkit-letter-spacing:normal;-moz-letter-spacing:normal;-ms-letter-spacing:normal;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:var(--tooltip-bgColor,var(--bgColor-emphasis,var(--color-neutral-emphasis-plus,#24292f)));border-radius:6px;opacity:0;}/*!sc*/ @-webkit-keyframes tooltip-appear{from{opacity:0;}to{opacity:1;}}/*!sc*/ @keyframes tooltip-appear{from{opacity:0;}to{opacity:1;}}/*!sc*/ .hWlpPn:hover::after,.hWlpPn:active::after,.hWlpPn:focus::after,.hWlpPn:focus-within::after{display:inline-block;-webkit-text-decoration:none;text-decoration:none;-webkit-animation-name:tooltip-appear;animation-name:tooltip-appear;-webkit-animation-duration:0.1s;animation-duration:0.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-animation-delay:0s;animation-delay:0s;}/*!sc*/ .hWlpPn.tooltipped-no-delay:hover::after,.hWlpPn.tooltipped-no-delay:active::after,.hWlpPn.tooltipped-no-delay:focus::after,.hWlpPn.tooltipped-no-delay:focus-within::after{-webkit-animation-delay:0s;animation-delay:0s;}/*!sc*/ .hWlpPn.tooltipped-multiline:hover::after,.hWlpPn.tooltipped-multiline:active::after,.hWlpPn.tooltipped-multiline:focus::after,.hWlpPn.tooltipped-multiline:focus-within::after{display:table-cell;}/*!sc*/ .hWlpPn.tooltipped-s::after,.hWlpPn.tooltipped-se::after,.hWlpPn.tooltipped-sw::after{top:100%;right:50%;margin-top:6px;}/*!sc*/ .hWlpPn.tooltipped-se::after{right:auto;left:50%;margin-left:-16px;}/*!sc*/ .hWlpPn.tooltipped-sw::after{margin-right:-16px;}/*!sc*/ .hWlpPn.tooltipped-n::after,.hWlpPn.tooltipped-ne::after,.hWlpPn.tooltipped-nw::after{right:50%;bottom:100%;margin-bottom:6px;}/*!sc*/ .hWlpPn.tooltipped-ne::after{right:auto;left:50%;margin-left:-16px;}/*!sc*/ .hWlpPn.tooltipped-nw::after{margin-right:-16px;}/*!sc*/ .hWlpPn.tooltipped-s::after,.hWlpPn.tooltipped-n::after{-webkit-transform:translateX(50%);-ms-transform:translateX(50%);transform:translateX(50%);}/*!sc*/ .hWlpPn.tooltipped-w::after{right:100%;bottom:50%;margin-right:6px;-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%);}/*!sc*/ .hWlpPn.tooltipped-e::after{bottom:50%;left:100%;margin-left:6px;-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%);}/*!sc*/ .hWlpPn.tooltipped-multiline::after{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:250px;word-wrap:break-word;white-space:pre-line;border-collapse:separate;}/*!sc*/ .hWlpPn.tooltipped-multiline.tooltipped-s::after,.hWlpPn.tooltipped-multiline.tooltipped-n::after{right:auto;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%);}/*!sc*/ .hWlpPn.tooltipped-multiline.tooltipped-w::after,.hWlpPn.tooltipped-multiline.tooltipped-e::after{right:100%;}/*!sc*/ .hWlpPn.tooltipped-align-right-2::after{right:0;margin-right:0;}/*!sc*/ .hWlpPn.tooltipped-align-left-2::after{left:0;margin-left:0;}/*!sc*/ data-styled.g4[id="Tooltip__TooltipBase-sc-17tf59c-0"]{content:"hWlpPn,"}/*!sc*/

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ngx-mf-remote-loader

SSR-compatible dynamic remote module loader for Angular + Nx Micro Frontends.

Features

  • SSR-compatible remote module loading
  • Works with Angular and Nx Micro Frontends
  • Simple API for loading remote modules
  • Separate implementations for browser and server environments

Usage

Basic Setup

  1. First, provide the appropriate RemoteLoader implementation in your app module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RemoteLoader, RemoteLoaderBrowser } from 'ngx-mf-remote-loader';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [
    {
      provide: RemoteLoader,
      useClass: RemoteLoaderBrowser
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
  1. Use the remoteLoader function in your routes:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { remoteLoader } from 'ngx-mf-remote-loader';

const routes: Routes = [
  {
    path: 'remote-feature',
    loadChildren: remoteLoader('remoteAppName')
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

SSR Configuration

For SSR applications, you need to provide different implementations for browser and server:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RemoteLoader } from 'ngx-mf-remote-loader';
import { AppComponent } from './app.component';
import { PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [
    {
      provide: RemoteLoader,
      useFactory: () => {
        const platformId = inject(PLATFORM_ID);
        if (isPlatformBrowser(platformId)) {
          const { RemoteLoaderBrowser } = require('ngx-mf-remote-loader');
          return new RemoteLoaderBrowser();
        } else {
          const { RemoteLoaderServer } = require('ngx-mf-remote-loader');
          return new RemoteLoaderServer();
        }
      }
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

Customizing Server-Side Loading

For server-side rendering, you need to customize the RemoteLoaderServer implementation to handle your specific remote modules. There are two main approaches:

Approach 1: Using registerRemoteModule

//# ngx-mf-remote-loader-server.ts
import { RemoteLoaderServer, RemoteItems } from 'ngx-mf-remote-loader';

export class CustomRemoteLoaderServer extends RemoteLoaderServer {
  constructor() {
    super();
    // Register all your remote modules
    this.registerRemoteModule('app1', 'Module', () => import('app1/Module'));
    this.registerRemoteModule('app2', 'Module', () => import('app2/Module'));
    // Add more registrations as needed
  }
  
  // You can also override the load method to add custom logic
  load(remoteName: string, remoteModule: RemoteItems): Promise<any> {
    // Custom logic here if needed
    return super.load(remoteName, remoteModule);
  }
}

Approach 2: Direct switch case implementation

This approach directly handles the module loading with switch cases:

//# ngx-mf-remote-loader-server.ts
import { RemoteLoader, RemoteItems } from 'ngx-mf-remote-loader';

// Define your specific remote module types for better type safety
type RemotesKey =
  | 'app1'
  | 'app2'
  | 'app3-component';

export class CustomRemoteLoaderServer extends RemoteLoader {
  load(remoteName: RemotesKey, remoteModule: RemoteItems): Promise<any> {
    switch (remoteName + remoteModule) {
      case 'app1Module':
        // eslint-disable-next-line @nx/enforce-module-boundaries
        return import('app1/Module').then((m) => m.RemoteEntryModule);
      case 'app2Module':
        // eslint-disable-next-line @nx/enforce-module-boundaries
        return import('app2/Module').then((m) => m.RemoteEntryModule);
      case 'app3-componentCarouselComponent':
        // eslint-disable-next-line @nx/enforce-module-boundaries
        return import('app3-component/CarouselComponent');
      // Add more cases as needed
      default:
        throw new Error(`Remote module ${remoteName}/${remoteModule} not found`);
    }
  }
}

Module Federation Manifest Setup

Important: Setting up the module-federation.manifest.json file is a required step for ngx-mf-remote-loader to work properly. This manifest is essential for mapping remote module names to their URLs and is a crucial part of the Nx Module Federation setup that our library depends on.

Manifest Structure

The manifest file is a simple JSON mapping of remote module names to their URLs:

{
  "remote-app-1": "http://localhost:4201",
  "remote-app-2": "http://localhost:4202",
  "remote-app-3": "http://localhost:4203"
}

Loading the Manifest

Required Step: For ngx-mf-remote-loader to function correctly, you must load the manifest file in your host application and set the remote definitions as shown below:

Using setRemoteDefinitions (Deprecated in Nx 22)

// main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';

// Load the manifest file at application startup
fetch('assets/module-federation.manifest.json')
  .then((res) => res.json())
  .then((definitions) => setRemoteDefinitions(definitions))
  .then(() => import('./bootstrap').catch((err) => console.error(err)));

Using init() from @module-federation/enhanced/runtime (Recommended)

// main.ts
import { init } from '@module-federation/enhanced/runtime';

// Example of direct initialization
init({
  name: 'host',
  remotes: [{
    name: 'my-remote-app',
    entry: 'http://localhost:4201/mf-manifest.json'
  }]
});

// Or loading from a manifest file
fetch('assets/module-federation.manifest.json')
  .then((res) => res.json())
  .then((definitions) => {
    const remotes = Object.entries(definitions).map(([name, entry]) => ({
      name,
      entry
    }));
    
    return init({
      name: 'host',
      remotes
    });
  })
  .then(() => import('./bootstrap').catch((err) => console.error(err)));

Note: setRemoteDefinitions will be removed in Nx 22. It's recommended to migrate to init() from @module-federation/enhanced/runtime.

Workaround for Lazy-Loaded Modules

If your application has calls to loadRemoteModule inside lazy-loaded NgModules, you might encounter issues with loading federated modules. Here's a workaround that makes remote definitions available globally:

// main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';

fetch('assets/module-federation.manifest.json')
  .then((res) => res.json())
  .then((remoteDefinitions) => {
    setRemoteDefinitions(remoteDefinitions);
    
    // Workaround: Make remote definitions available globally
    // This helps with loading federated modules in lazy-loaded NgModules
    // See https://github.com/nrwl/nx/issues/27842
    Object.assign(globalThis, { remoteDefinitions });
  })
  .then(() => import('./bootstrap').catch((err) => console.error(err)));

This workaround assigns the remote definitions to the global scope, making them accessible to lazy-loaded modules that need to resolve remote module URLs.

This initialization step is essential for our library to properly resolve and load remote modules.

How It Works

  1. Initialization: The host application loads the manifest file and passes it to setRemoteDefinitions()
  2. Remote Loading: When loadRemoteModule() is called, it:
    • Looks up the remote's URL in the manifest
    • Dynamically loads the JavaScript from that URL
    • Returns the requested module or component

Environment-Specific Manifests

You can create environment-specific manifest files for different deployment environments:

/assets/
  module-federation.manifest.dev.json
  module-federation.manifest.prod.json
  module-federation.manifest.json  # The active manifest

During the build process, you can copy the appropriate environment-specific manifest to the main module-federation.manifest.json file.

Advanced: Dynamic Manifest Updates

For applications that need to update remote URLs at runtime:

import { setRemoteDefinitions } from '@nx/angular/mf';

// Fetch an updated manifest from your API
fetchUpdatedManifest().then(newDefinitions => {
  setRemoteDefinitions(newDefinitions);
  // Now loadRemoteModule will use the updated URLs
});

Troubleshooting

Common issues with module federation manifest:

  • 404 Not Found: Ensure the manifest file is correctly copied to the assets folder during build
  • Remote Loading Failures: Verify that the URLs in your manifest are correct and accessible
  • SSR Errors: For server-side rendering, ensure you've properly configured the server-side loader

Implementation Details

Under the hood, the library uses Nx's loadRemoteModule function, which relies on the manifest definitions:

// RemoteLoaderBrowser implementation
export class RemoteLoaderBrowser extends RemoteLoader {
  load(remoteName: string, remoteModule: RemoteItems): Promise<any> {
    return loadRemoteModule(remoteName, './' + remoteModule).then((m) => {
      return remoteModule === 'Module' ? m.RemoteEntryModule : m;
    });
  }
}

This approach ensures compatibility with the Nx Module Federation system while providing a more convenient API for Angular applications.

Recommended Companion Tool: nx-mf-remote-loader-generator

For the best development experience, we strongly recommend using our companion tool nx-mf-remote-loader-generator which automates the creation and configuration of remote components that work seamlessly with this library.

License

MIT

About

SSR-compatible remote module loader for Angular Micro Frontends. Seamlessly load remote modules in both browser and server environments with a unified API. Built for Angular 17+ with Nx support.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published
0