{"id":13808958,"url":"https://github.com/LoaderB0T/ng-dynamic-mf","last_synced_at":"2025-05-14T03:31:55.083Z","repository":{"id":40778879,"uuid":"465383813","full_name":"LoaderB0T/ng-dynamic-mf","owner":"LoaderB0T","description":"Truly dynamic modules at runtime with Module Federation","archived":false,"fork":false,"pushed_at":"2024-10-21T13:50:53.000Z","size":1031,"stargazers_count":8,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-10T03:42:29.920Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LoaderB0T.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-02T16:28:53.000Z","updated_at":"2024-10-22T21:28:21.000Z","dependencies_parsed_at":"2023-02-14T06:45:36.707Z","dependency_job_id":"2dea41b1-5960-4c30-a978-21e0427a401d","html_url":"https://github.com/LoaderB0T/ng-dynamic-mf","commit_stats":{"total_commits":78,"total_committers":4,"mean_commits":19.5,"dds":"0.16666666666666663","last_synced_commit":"4ba0f0d47a1bc0f632fcab815851d128e06af3e9"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LoaderB0T%2Fng-dynamic-mf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LoaderB0T%2Fng-dynamic-mf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LoaderB0T%2Fng-dynamic-mf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LoaderB0T%2Fng-dynamic-mf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LoaderB0T","download_url":"https://codeload.github.com/LoaderB0T/ng-dynamic-mf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225273295,"owners_count":17448083,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:01:56.073Z","updated_at":"2024-11-19T00:31:15.675Z","avatar_url":"https://github.com/LoaderB0T.png","language":"TypeScript","funding_links":[],"categories":["Architecture and Advanced Topics"],"sub_categories":["Module Federation"],"readme":"[![npm](https://img.shields.io/npm/v/ng-dynamic-mf?color=%2300d26a\u0026style=for-the-badge)](https://www.npmjs.com/package/ng-dynamic-mf)\n[![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/LoaderB0T_ng-dynamic-mf?server=https%3A%2F%2Fsonarcloud.io\u0026style=for-the-badge)](https://sonarcloud.io/summary/new_code?id=LoaderB0T_ng-dynamic-mf)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/ng-dynamic-mf?color=%23FF006F\u0026label=Bundle%20Size\u0026style=for-the-badge)](https://bundlephobia.com/package/ng-dynamic-mf)\n\n# ng-dynamic-mf\n\nTruly dynamic modules at runtime with Module / Native Federation!\n\n## Motivation 💥\n\nng-dynamic-mf works with both module federation and native federation. In the following, I will refer to both as \"module federation\" for simplicity.\n\nModule Federation builds upon the concepts of Module Federation without the need for webpack. For Angular, there is the excellent [Module Federation Plugin](https://github.com/angular-architects/module-federation-plugin/blob/main/README.md). ng-dynamic-mf now leverages Module Federation, bringing modularity in Angular to the next level by providing methods to easily load modules at runtime.\n\n## Features 🔥\n\n✅ Dynamic loading of modules at runtime\n\n✅ The host/shell application does not need to be aware of the modules it loads\n\n✅ Modules can be loaded from any location (e.g., from a CDN) that can be unknown to the developer\n\n✅ Works with Webpack \u0026 ESBuild\n\n✅ Modules can be deployed on demand without modifying the host/shell application\n\n✅ Assets are resolved correctly at runtime (both for running the app standalone and in a shell) with a provided pipe.\n\n✅ Support for custom routing logic (Register module routes as a child of any other module)\n\n✅ Support for global styles (Load global styles from any module)\n\n✅ Use with Nx with [nx-dynamic-mf](https://github.com/LoaderB0T/nx-dynamic-mf)\n\nThis project aims to improve the general experience of developers using Module Federation in Angular. Even if you don't _need_ truly dynamic modules, you can still benefit from this project.\n\n## Built With 🔧\n\n- [TypeScript](https://www.typescriptlang.org/)\n- [Angular](https://angular.io/)\n\n## Getting Started 🚀\n\nIf you want to check out a working example, take a look at my portfolio project [awdware](https://github.com/LoaderB0T/awdware3).\n\nIf you are using Nx, you should also check out [nx-dynamic-mf](https://github.com/LoaderB0T/nx-dynamic-mf)\n\n### Module Federation Plugin\n\nThis project uses the [Module Federation Plugin](https://github.com/angular-architects/module-federation-plugin/blob/main/libs/mf/README.md) to load modules at runtime. Please follow the instructions in the README.md file of the plugin to get started.\n\n### Bootstrapping the host/shell application\n\nng-dynamic-mf provides an `initializeApp` method that can be used to bootstrap the shell app.\n\nExample:\n\n```typescript\n// This example uses native federation!\n\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { initializeApp } from 'ng-dynamic-mf/nf';\nimport { environment } from 'ng-dynamic-mf/environment';\n// Due to an issue in the Module Federation Plugin, we need to import loadRemoteModule from the plugin directly and pass it to ng-dynamic-mf. This is only required for the native federation use case.\nimport { loadRemoteModule } from '@angular-architects/native-federation';\n\ninitializeApp({\n  type: 'host', // Specify that this is the host/shell application\n  loadRemoteModule,\n})\n.then(() =\u003e {\n  if (environment.production) { // This is the environment variable provided by ng-dynamic-mf\n    enableProdMode();\n  }\n  return import('./app/app.module');\n})\n.then(x =\u003e x.AppModule)\n.then(x =\u003e {\n  platformBrowserDynamic()\n    .bootstrapModule(x)\n    .catch(err =\u003e console.error(err));\n});\n```\n\nNote: ng-dynamic-mf does also export the `environment` variable. This replaces Angulars environment system. You should remove the environment.\\*.ts files generated by Angular and switch to the runtime version of ng-dynamic-mf. [more info here](#environment-variables).\n\nng-dynamic-mf expects a `modules.json` file to be present in the `src` folder of the host/shell application.\nExamples of `modules.json`s:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/LoaderB0T/ng-dynamic-mf/main/schema.json\",\n  \"modules\": [\n    {\n      \"url\": \"http://localhost:4201/\",\n      \"name\": \"home\",\n      \"ngModuleName\": \"RemoteEntryModule\"\n    },\n    {\n      \"url\": \"http://localhost:4202/\",\n      \"name\": \"projects\",\n      \"ngModuleName\": \"RemoteEntryModule\"\n    }\n  ]\n}\n```\n\nor\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/LoaderB0T/ng-dynamic-mf/main/schema.json\",\n  \"modules\": [\n    {\n      \"url\": \"https://example.com/assets/modules/home/\",\n      \"name\": \"home\",\n      \"ngModuleName\": \"RemoteEntryModule\"\n    },\n    {\n      \"url\": \"https://example.com/assets/modules/projects/\",\n      \"name\": \"projects\",\n      \"ngModuleName\": \"RemoteEntryModule\"\n    }\n  ]\n}\n```\n\n(Mind that the `url`s must be absolute, not relative and end with a slash).\n\nYou also need to provide an `environment.json` file in the `src` folder of the host/shell application.\nExample of `environment.json`s:\n\n```json\n{\n  \"apiUrl\": \"http://localhost:5555\",\n  \"production\": false\n}\n```\n\nor\n\n```json\n{\n  \"apiUrl\": \"https://api.example.com/\",\n  \"production\": true\n}\n```\n\n### bootstrapping a module\n\nBootstrapping a module app is similar to a regular Angular app. The only difference is importing the `environment` from the `ng-dynamic-mf` module instead of the usual path.\n\n```typescript\nimport { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { environment } from 'ng-dynamic-mf/environment';\n\nimport { AppModule } from './app/app.module';\n\nif (environment.production) {\n  enableProdMode();\n}\n\nplatformBrowserDynamic()\n  .bootstrapModule(AppModule)\n  .catch(err =\u003e console.error(err));\n```\n\n### Assets resolution\n\nWhen using static assets such as images in a module, you need to teach your app how to resolve the asset.\nThis is due to the fact that the assets are loaded at runtime and not at build time and the shell app is not aware of the assets that a module provides.\n\nExample of how to resolve assets in a template of the home module:\n\n```html\n\u003cimg [alt]=\"contact.name\" [src]=\"'assets/img/logo.svg' | resourceMap:'home'\" /\u003e\n```\n\nWhen just serving the app this resolves to `${location.origin}/assets/img/logo.svg`. When loaded by the host this will resovle to `https://example.com/assets/modules/home/assets/img/logo.svg` (If this is the configured URL of the module).\n\nYou can also do this mapping in code:\n\n```typescript\nimport { resourceMapper } from 'ng-dynamic-mf';\n\nconst path = 'assets/img/logo.svg';\nconst url = resourceMapper('home', path);\n```\n\n### Handling translations (ngx-translate)\n\nWhen using translations in a module, they need to be loaded at runtime when the module is loaded.\n\nExample of how to register german and english translations in the home module:\n\n```typescript\nimport { DynamicTranslationService } from 'ng-dynamic-mf/translate';\n\nexport class HomeModule {\n  constructor(dynamicTranslationService: DynamicTranslationService) {\n    // Specify that assets/i18n/[..].json is the path to a translation file for each language. Other syntaxes are also supported (See inline documentation).\n    // (Resolved with the resource mapper to the home module's assets folder) -\u003e Works for standalone and shell use cases\n    dynamicTranslationService.registerTranslationSet('home-i18n', {\n      de: {\n        moduleName: 'home',\n        path: 'assets/i18n/home.de.json',\n      },\n      en: {\n        moduleName: 'home',\n        path: 'assets/i18n/home.en.json',\n      }}\n    );\n  }\n}\n```\n\n### Routing\n\nWhen a module provides routes that should be nested inside of another route of the shell, they need to be configured accordingly.\nYou dont need to do anything here, if the routes of your modules should all be resolved relative to the root of the shell app (no nesting).\nExample:\nRoute config of the shell:\n\n```typescript\nimport { Routes } from '@angular/router';\n\nexport const routes: Routes = [\n  {\n    path: '',\n    children: [\n      {\n        path: '#module-entry-point#',\n        redirectTo: '/'\n      }\n    ]\n  },\n  {\n    path: '**',\n    redirectTo: ''\n  }\n];\n```\n\nWe want all modules to be registered where `#module-entry-point#` is located.\n\n```typescript\nimport { RouterEntryService } from 'ng-dynamic-mf';\n\nconst routes: Routes = [\n  {\n    path: 'home',\n    loadChildren: () =\u003e import('./home.module').then(m =\u003e m.HomeModule),\n    data: { activePage: 'home' }\n  },\n  {\n    path: '',\n    pathMatch: 'full',\n    redirectTo: '/home'\n  }\n];\n\nexport class HomeEntryModule {\n  constructor(routerEntryService: RouterEntryService) {\n    routerEntryService.registerRoutes(routes);\n    // - OR - You can also register the routes under a specific entry point.\n    // This defaults to '#module-entry-point#'\n    routerEntryService.registerRoutes(routes, 'details');\n  }\n}\n```\n\n### Environment variables\n\nng-dynamic-mf provides a way to specify variables in an `environment.json` file (after the app has been built). This is different from the `environment.ts` file that is used by Angular CLI, as this will be baked into the app during the build. The `environment.json` file can be edited at any time and the change will be reflected in the app after a reload.\n\nYou do not have to use the `environment.json` file, you can also keep using the `environment.ts` file. (However you will need to create an empty `environment.json` file to prevent errors).\n\n### Global Styles\n\nng-dynamic-mf provides a way to load global styles into the app. This is useful for loading styles that are defined by a module but should be applied globally (no view encapsulation). To do so, you need to add a styles file (eg `global.scss`) to your module that defines the styles. Afterwards you need to adjust your modules `angular.json` file to include the styles file and bundle it separately with the name `global-styles`:\n\n```json\n{\n  [...],\n  \"targets\": {\n    \"build\": {\n      [...],\n      \"options\": {\n        [...],\n        \"styles\": [\n          \"apps/example-project/src/styles.scss\",\n          {\n            \"input\": \"apps/example-project/src/global.scss\",\n            \"bundleName\": \"global-styles\"\n          }\n        ],\n        [...]\n      }\n    }\n  }\n}\n```\n\nNote: The bundle name has to be `global-styles` for ng-dynamic-mf to recognize it.\n\nAfterwards you can tell ng-dynamic-mf to load the styles by adding `\"hasGlobalStyles\": true` to this modules config in the `modules.json` file.\n\nNote: Don't forget to disable output hashing in the `angular.json` file or make sure to rename the file after it gets created. (File name has to be `global-styles.css` in the root of the module, next to the entry point file). Alternatively you can use [nx-dynamic-mf](https://github.com/LoaderB0T/nx-dynamic-mf) which will adjust the `module.json` file automatically to the hashed file name.\n\n## Contributing 🧑🏻‍💻\n\nContributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n\nIf you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag \"enhancement\".\nDon't forget to give the project a star! Thanks again!\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n## License 🔑\n\nDistributed under the MIT License. See `LICENSE.txt` for more information.\n\n## Contact 📧\n\nJanik Schumacher - [@LoaderB0T](https://twitter.com/LoaderB0T) - [linkedin](https://www.linkedin.com/in/janikschumacher/)\n\nProject Link: [https://github.com/LoaderB0T/ng-dynamic-module-federation](https://github.com/LoaderB0T/ng-dynamic-module-federation)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLoaderB0T%2Fng-dynamic-mf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLoaderB0T%2Fng-dynamic-mf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLoaderB0T%2Fng-dynamic-mf/lists"}