From ecb4c996d2b9f0ae34fd0e189e555bd04018658d Mon Sep 17 00:00:00 2001 From: Nikita Demchenko Date: Fri, 15 Mar 2024 16:51:53 +0000 Subject: [PATCH] Major doc additions --- apps/docs/src/en/antora.yml | 5 +- apps/docs/src/en/modules/ROOT/nav.adoc | 104 +++-- .../ROOT/pages/agnostic-way/index.adoc | 242 ++++++++++++ .../modules/ROOT/pages/angular-way/auth0.adoc | 158 ++++++++ .../ROOT/pages/angular-way/i18n-angular.adoc | 0 .../modules/ROOT/pages/angular-way/index.adoc | 268 +++++++++++++ .../ROOT/pages/angular-way/index2.adoc | 351 +++++++++++++++++ .../pages/angular-way/mf-ssr-angular.adoc | 108 ++++++ .../ROOT/pages/angular-way/okta-auth.adoc | 237 ++++++++++++ .../pages/angular-way/service-workers-mf.adoc | 319 ++++++++++++++++ .../angular-way/splitting-to-mf-part1.adoc | 322 ++++++++++++++++ .../angular-way/splitting-to-mf-part2.adoc | 154 ++++++++ .../pages/composable-commerce-simple.adoc | 360 ------------------ .../ROOT/pages/concepts/antipatterns.adoc | 77 ++++ .../pages/{ => concepts}/brown-green.adoc | 10 +- .../component-level-ownership.adoc | 2 +- .../ROOT/pages/concepts/component-vs-mf.adoc | 48 +++ .../{ => core-features}/cache_busting.adoc | 2 +- .../{ => core-features}/delegate-modules.adoc | 22 +- .../{ => core-features}/dynamic-remotes.adoc | 38 +- .../pages/{ => core-features}/shared-api.adoc | 140 ++++++- .../versioned-shared-modules.adoc | 70 ++-- .../{ => ROOT/pages}/enhanced_api/about.adoc | 2 +- .../enhanced_api/features_default_async.adoc | 2 +- .../features_module_name_map.adoc | 2 +- .../features_remote_object_definition.adoc | 8 +- .../features_remotes_name_map.adoc | 10 +- .../enhanced_api/features_remotes_url.adoc | 2 +- .../pages}/enhanced_api/getting_started.adoc | 5 +- .../ROOT/pages/enhanced_api/helper_about.adoc | 17 + .../enhanced_api/helper_dynamicLoad.adoc | 4 +- .../pages}/enhanced_api/helper_getModule.adoc | 2 +- .../enhanced_api/helper_installation.adoc | 4 +- .../pages}/enhanced_api/installation.adoc | 7 +- .../{ => ROOT/pages}/enhanced_api/nav.adoc | 2 +- .../{ => getting-started}/best-practices.adoc | 16 +- .../getting-started-practical.adoc | 32 +- .../pages/{ => getting-started}/history.adoc | 0 .../ROOT/pages/getting-started/index.adoc | 43 +++ .../getting-started/loc-dev-deployment.adoc | 132 +++++++ .../{ => getting-started}/pros-cons.adoc | 2 +- .../pages/{ => getting-started}/setup.adoc | 9 +- .../{ => getting-started}/use-cases.adoc | 8 +- .../what-is-mf.adoc} | 2 +- ...le-federation-native-federation-tests.adoc | 0 ...deration-native-federation-typescript.adoc | 2 +- .../module-federation-node.adoc | 0 .../module-federation-typescript.adoc | 0 .../module-federation-utilities.adoc | 0 .../ROOT/pages/react-way/i18n-react.adoc | 210 ++++++++++ .../modules/ROOT/pages/react-way/index.adoc | 141 +++++++ .../composable-commerce-PBC-edition.adoc | 139 ++++--- .../pages/{ => recipes}/examples-demos.adoc | 2 +- .../ROOT/pages/recipes/mf-enterprise.adoc | 226 +++++++++++ .../ROOT/pages/{ => recipes}/mf-rollupjs.adoc | 2 +- .../pages/{ => recipes}/mf-split-chunks.adoc | 28 +- .../ROOT/pages/{ => recipes}/mf-ssr.adoc | 43 +-- .../pages/recipes/mf-typescript-plugin.adoc | 128 +++++++ .../{ => recipes}/naming-convention-tips.adoc | 47 ++- .../{ => recipes}/public-path-dynamic.adoc | 16 +- .../{ => recipes}/resiliency-failovers.adoc | 74 ++-- .../ROOT/pages/recipes/sentry-logging.adoc | 236 ++++++++++++ .../{ => recipes}/shared-header-footer.adoc | 18 +- .../pages/recipes/state-management-redux.adoc | 235 ++++++++++++ .../ROOT/pages/recipes/tailwind-mf.adoc | 354 +++++++++++++++++ .../pages/{ => recipes}/unit-testing.adoc | 10 +- .../ROOT/pages/team-collaboration-review.adoc | 212 ----------- .../en/modules/ROOT/pages/vue-way/index.adoc | 332 ++++++++++++++++ .../pages/weblate_contribution_guide.adoc | 93 +++++ .../en/modules/enhanced_api/helper_about.adoc | 19 - 70 files changed, 4956 insertions(+), 959 deletions(-) create mode 100644 apps/docs/src/en/modules/ROOT/pages/agnostic-way/index.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/auth0.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/i18n-angular.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/index.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/index2.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/mf-ssr-angular.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/okta-auth.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/service-workers-mf.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part1.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part2.adoc delete mode 100644 apps/docs/src/en/modules/ROOT/pages/composable-commerce-simple.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/concepts/antipatterns.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => concepts}/brown-green.adoc (96%) rename apps/docs/src/en/modules/ROOT/pages/{ => concepts}/component-level-ownership.adoc (99%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/concepts/component-vs-mf.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => core-features}/cache_busting.adoc (97%) rename apps/docs/src/en/modules/ROOT/pages/{ => core-features}/delegate-modules.adoc (95%) rename apps/docs/src/en/modules/ROOT/pages/{ => core-features}/dynamic-remotes.adoc (78%) rename apps/docs/src/en/modules/ROOT/pages/{ => core-features}/shared-api.adoc (64%) rename apps/docs/src/en/modules/ROOT/pages/{ => core-features}/versioned-shared-modules.adoc (84%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/about.adoc (98%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/features_default_async.adoc (96%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/features_module_name_map.adoc (96%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/features_remote_object_definition.adoc (91%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/features_remotes_name_map.adoc (88%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/features_remotes_url.adoc (97%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/getting_started.adoc (86%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/enhanced_api/helper_about.adoc rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/helper_dynamicLoad.adoc (92%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/helper_getModule.adoc (98%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/helper_installation.adoc (88%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/installation.adoc (82%) rename apps/docs/src/en/modules/{ => ROOT/pages}/enhanced_api/nav.adoc (90%) rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/best-practices.adoc (87%) rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/getting-started-practical.adoc (93%) rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/history.adoc (100%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/getting-started/index.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/getting-started/loc-dev-deployment.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/pros-cons.adoc (98%) rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/setup.adoc (91%) rename apps/docs/src/en/modules/ROOT/pages/{ => getting-started}/use-cases.adoc (81%) rename apps/docs/src/en/modules/ROOT/pages/{getting-started.adoc => getting-started/what-is-mf.adoc} (95%) rename apps/docs/src/en/modules/ROOT/pages/{ => packages}/module-federation-native-federation-tests.adoc (100%) rename apps/docs/src/en/modules/ROOT/pages/{ => packages}/module-federation-native-federation-typescript.adoc (99%) rename apps/docs/src/en/modules/ROOT/pages/{ => packages}/module-federation-node.adoc (100%) rename apps/docs/src/en/modules/ROOT/pages/{ => packages}/module-federation-typescript.adoc (100%) rename apps/docs/src/en/modules/ROOT/pages/{ => packages}/module-federation-utilities.adoc (100%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/react-way/i18n-react.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/react-way/index.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/composable-commerce-PBC-edition.adoc (96%) rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/examples-demos.adoc (98%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/recipes/mf-enterprise.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/mf-rollupjs.adoc (98%) rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/mf-split-chunks.adoc (86%) rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/mf-ssr.adoc (83%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/recipes/mf-typescript-plugin.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/naming-convention-tips.adoc (98%) rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/public-path-dynamic.adoc (85%) rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/resiliency-failovers.adoc (93%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/recipes/sentry-logging.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/shared-header-footer.adoc (97%) create mode 100644 apps/docs/src/en/modules/ROOT/pages/recipes/state-management-redux.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/recipes/tailwind-mf.adoc rename apps/docs/src/en/modules/ROOT/pages/{ => recipes}/unit-testing.adoc (98%) delete mode 100644 apps/docs/src/en/modules/ROOT/pages/team-collaboration-review.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/vue-way/index.adoc create mode 100644 apps/docs/src/en/modules/ROOT/pages/weblate_contribution_guide.adoc delete mode 100644 apps/docs/src/en/modules/enhanced_api/helper_about.adoc diff --git a/apps/docs/src/en/antora.yml b/apps/docs/src/en/antora.yml index f28a5503be5..4f90ca38590 100644 --- a/apps/docs/src/en/antora.yml +++ b/apps/docs/src/en/antora.yml @@ -1,11 +1,10 @@ name: mf-docs -title: Module Federation Documentation (Beta) +title: Module Federation Documentation version: 0.20 display_version: '0.20' -start_page: ROOT:getting-started.adoc +start_page: ROOT:getting-started/getting-started-practical.adoc nav: - modules/ROOT/nav.adoc - - modules/enhanced_api/nav.adoc asciidoc: attributes: idprefix: '@' diff --git a/apps/docs/src/en/modules/ROOT/nav.adoc b/apps/docs/src/en/modules/ROOT/nav.adoc index 13f3fd44991..28d61dd0c33 100644 --- a/apps/docs/src/en/modules/ROOT/nav.adoc +++ b/apps/docs/src/en/modules/ROOT/nav.adoc @@ -1,38 +1,86 @@ .Getting Started -* xref:history.adoc[History of Module Federation] -* xref:getting-started.adoc[What is Module Federation?] -* xref:setup.adoc[Setting up Environment] -* xref:getting-started-practical.adoc[Getting Started] -* xref:use-cases.adoc[Use Cases] -* xref:pros-cons.adoc[Pros and Cons of Module Federation] -* xref:best-practices.adoc[Best Practices and Tips] +* xref:getting-started/what-is-mf.adoc[What is Module Federation?] +* xref:getting-started/history.adoc[History of Module Federation] +* xref:getting-started/use-cases.adoc[Use Cases] +* xref:getting-started/pros-cons.adoc[Pros and Cons of Module Federation] +* xref:getting-started/setup.adoc[Setting up Environment] +* xref:getting-started/getting-started-practical.adoc[Practical Getting Started] +* xref:getting-started/best-practices.adoc[Best Practices and Tips] +* xref:getting-started/loc-dev-deployment.adoc[Simplifying Local Development and Deployment] .Core Features -* xref:dynamic-remotes.adoc[Dynamic Remotes in Module Federation] -* xref:shared-api.adoc[] -* xref:delegate-modules.adoc[] -* xref:versioned-shared-modules.adoc[] -* xref:cache_busting.adoc[] +* xref:core-features/dynamic-remotes.adoc[Using Dynamic Remotes] +* xref:core-features/shared-api.adoc[Module Federation Shared API] +* xref:core-features/delegate-modules.adoc[Delegate Modules] +* xref:core-features/versioned-shared-modules.adoc[Versioned Shared Modules] +* xref:core-features/cache_busting.adoc[Cache Busting] .Recipes -* xref:examples-demos.adoc[] -* xref:mf-ssr.adoc[] -* xref:team-collaboration-review.adoc[] -* xref:unit-testing.adoc[] -* xref:public-path-dynamic.adoc[] -* xref:resiliency-failovers.adoc[] -* xref:naming-convention-tips.adoc[Naming Conventions for Modules and Exports ] -* xref:shared-header-footer.adoc[] -* xref:composable-commerce-PBC-edition.adoc[] +* xref:recipes/examples-demos.adoc[Examples and Demos] +* xref:recipes/mf-ssr.adoc[Server-Side Rendering with Module Federation] +* xref:recipes/unit-testing.adoc[Unit Tests for Distributed Code] +* xref:recipes/public-path-dynamic.adoc[Setting the Dynamic Public Path] +* xref:recipes/resiliency-failovers.adoc[Improving Resiliency and Failovers] +* xref:recipes/naming-convention-tips.adoc[Strategies for Naming Modules and Exports] +* xref:recipes/sentry-logging.adoc[Sentry Logging in Module Federation] +* xref:recipes/shared-header-footer.adoc[Shared Header and Footer] +// * xref:composable-commerce-simple.adoc[Composable Commerce: Simple Edition] +* xref:recipes/composable-commerce-PBC-edition.adoc[Composable Commerce with Module Federation] +* xref:recipes/state-management-redux.adoc[State Management with Redux in Module Federation] +* xref:recipes/tailwind-mf.adoc[Tailwind CSS in Module Federation] +* xref:agnostic-way/index.adoc[Framework Agnostic Approaches in Module Federation] + +* xref:recipes/mf-enterprise.adoc[Module Federation for Enterprise] +* xref:recipes/mf-rollupjs.adoc[Implementing Module Federation in Rollup.JS] +* xref:recipes/mf-typescript-plugin.adoc[TypeScript with Module Federation in Next.js Applications] +* xref:recipes/mf-split-chunks.adoc[Consolidating Multiple Vendor Bundles with SplitChunksPlugin] .Concepts -* xref:component-level-ownership.adoc[Component Level Ownership] -* xref:brown-green.adoc[] +* xref:concepts/component-level-ownership.adoc[Component Level Ownership] +* xref:concepts/brown-green.adoc[Brownfield vs. Greenfield Development] +* xref:concepts/component-vs-mf.adoc[Difference Between a Component and a Micro-Frontend] +* xref:concepts/antipatterns.adoc[Antipatterns in Module Federation] .Packages -* xref:module-federation-node.adoc[@module-federation/node] -* xref:module-federation-typescript.adoc[@module-federation/typescript - v2.4.2] -* xref:module-federation-utilities.adoc[@module-federation/utilities] -* xref:module-federation-native-federation-typescript.adoc[@module-federation/native-federation-typescript - v0.2.6] -* xref:module-federation-native-federation-tests.adoc[@module-federation/native-federation-tests - v0.2.1] +* xref:packages/module-federation-node.adoc[@module-federation/node] +* xref:packages/module-federation-typescript.adoc[@module-federation/typescript - v2.4.2] +* xref:packages/module-federation-utilities.adoc[@module-federation/utilities] +* xref:packages/module-federation-native-federation-typescript.adoc[@module-federation/native-federation-typescript - v0.2.6] +* xref:packages/module-federation-native-federation-tests.adoc[@module-federation/native-federation-tests - v0.2.1] + +.Plugins +* Enhanced API Plugin +** xref:enhanced_api/about.adoc[About] +** xref:enhanced_api/installation.adoc[Installation] +** xref:enhanced_api/getting_started.adoc[Getting Started] +** xref:enhanced_api/features_default_async.adoc[Feature: Default Async] +** xref:enhanced_api/features_module_name_map.adoc[Feature: Modules' Name Map] +** xref:enhanced_api/features_remotes_name_map.adoc[Feature: Remotes' Name Map] +** xref:enhanced_api/features_remotes_url.adoc[Feature: Remotes' URL] +** xref:enhanced_api/features_remote_object_definition.adoc[Feature: Object Definition] +* Helper +** xref:enhanced_api/helper_about.adoc[About] +** xref:enhanced_api/helper_installation.adoc[Install] +** xref:enhanced_api/helper_getModule.adoc[Feature: getModule] +** xref:enhanced_api/helper_dynamicLoad.adoc[Feature: dynamicLoad] + +.Angular Specific +* xref:angular-way/index.adoc[Setting Up MF in an Angular CLI Application] +* xref:angular-way/index2.adoc[Setting Up MF in an Angular Application] +* xref:angular-way/auth0.adoc[Auth0 Authentication with Angular Module Federation] +* xref:angular-way/okta-auth.adoc[Okta Authentication with Angular Module Federation] +// * xref:angular-way/i18n-angular.adoc[Internationalization in Angular with Module Federation] +* xref:angular-way/mf-ssr-angular.adoc[Server-Side Rendering in Angular with Module Federation] +* xref:angular-way/service-workers-mf.adoc[Service Workers in Angular with Module Federation] +* xref:angular-way/splitting-to-mf-part1.adoc[Splitting an Angular App to Module Federation: Part 1] +* xref:angular-way/splitting-to-mf-part2.adoc[Splitting an Angular App to Module Federation: Part 2] + +.React Specific +* xref:react-way/index.adoc[Micro-frontend Projects with Module Federation and React] +* xref:react-way/i18n-react.adoc[Internationalization in React with Module Federation] + +.Vue Specific +* xref:vue-way/index.adoc[Setting Up Module Federation in a Vue 2 Application] +.Website Contribution and Tools +* xref:weblate_contribution_guide.adoc[Weblate Contribution Guide] diff --git a/apps/docs/src/en/modules/ROOT/pages/agnostic-way/index.adoc b/apps/docs/src/en/modules/ROOT/pages/agnostic-way/index.adoc new file mode 100644 index 00000000000..581e3eacf92 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/agnostic-way/index.adoc @@ -0,0 +1,242 @@ += Framework-agnostic Micro-frontends with Angular, React, and Vue + +This article explores the technical implementation of injecting React and Vue remote modules into an Angular shell app using Webpack 5's ModuleFederationPlugin. + +== Case for framework-agnostic Micro-frontends + +Consider a scenario where multiple applications share common components, such as a PROFILE page and a SETTINGS page. Micro-frontends offer compelling advantages in such cases: + +- Decentralized Development: By delegating the development of PROFILE and SETTINGS modules to standalone teams, they can work independently and potentially at a faster pace. +- Framework Agnosticism: If the main app uses a different framework or version, Module Federation enables the PROFILE and SETTINGS modules to be developed and maintained independently, even if they use a different framework. +- Modularity: In the case of a large application, Module Federation helps break it down into smaller, more manageable pieces. This makes it easier to upgrade one module at a time without affecting the rest of the app. +- Faster Deployment: Deploying the PROFILE and SETTINGS modules separately from the main app allows for more flexibility and faster deployment times. The main app doesn't need to be rebuilt and redeployed every time a change is made to one of these modules. + +Now, let's dive into the technical implementation of using Module Federation to achieve these benefits. + +=== Developing the React "Profile" Remote Module + +The React "Profile" module contains a simple form where users can edit their name and email. The implementation is straightforward, but it's worth noting that the bootstrap.js file is mandatory due to a https://stackoverflow.com/questions/71228191/shared-module-is-not-available-for-eager-consumption-angular-13[bug]. + +To run the Profile module, navigate to the app folder and execute the following commands: + +[source, bash] +---- +yarn +yarn start +---- + +You can access the form at http://localhost:3001. + +==== ModuleFederationPlugin Settings (React) + +In the webpack.config.js file, the ModuleFederationPlugin settings for the React "Profile" module are as follows: + +[source, javascript] +---- +new ModuleFederationPlugin({ + name: 'profile_user', + filename: 'remoteEntry.js', + exposes: { + './ProfileReactComponent': './src/ProfileReactComponent', + }, + shared: { + react: { + singleton: true, + requiredVersion: deps.react, + eager: true, + }, + 'react-dom': { + singleton: true, + requiredVersion: deps['react-dom'], + eager: true, + }, + }, +}), +---- + +In this context, the `ReactProfileComponent` was made available for external use, and the React libraries were shared. It's important to highlight that any imported code, including elements like CSS styles (`profile-style.css` in our example), will be packaged within the exposes module alongside the `ReactProfileComponent.tsx`. + +=== Developing the Vue Settings Remote Module + +The Vue "Settings" module is created to run as an isolated element within the shadow DOM to avoid conflicts between React and Vue in the Angular shell app. + +To run the Vue Settings module, navigate to the app folder and execute the following commands: + +[source, bash] +---- +yarn +yarn start +---- + +You can access the module at `http://localhost:3002.` + +==== ModuleFederationPlugin Settings (Vue) + +In the `webpack.config.js` file for the Vue Settings component, the ModuleFederationPlugin settings are as follows: + +[source, javascript] +---- +new ModuleFederationPlugin({ + name: 'settings_user', + filename: 'remoteEntry.js', + exposes: { + './Settings': './src/components/Settings', + }, + shared: { + vue: { + eager: true, + requiredVersion: deps.vue, + }, + }, +}), +---- + +The configuration ensures that the Vue module can be used within the Angular shell app. + +NOTE: To mitigate potential conflicts between React and Vue in the Angular shell app, Vue 3 introduces the use of `defineCustomElement`. This feature enables the creation of Vue components as self-contained elements within the shadow DOM. + +=== Integrating React and Vue into the Angular Shell + +In the Angular shell application, we'll integrate the React "Profile" module and Vue "Settings" module as remote components. + +The timing of when these remote components should be loaded into the Angular shell app varies based on the specific app's needs. In our scenario, we want the React and Vue components to be visible on the homepage of the Angular shell app, so we run the `LoadRemoteModule` methods as part of the app's initialization process. + +In the app.module.ts file, the initialization process is defined: + +[source, typescript] +---- +export function initializeApp(): () => void { +return () => { + loadRemoteModule({ + remoteEntry: "http://localhost:3001/remoteEntry.js", + remoteName: "profile_user", + exposedModule: "./ProfileReactComponent", + }); + loadRemoteModule({ + remoteEntry: "http://localhost:3002/remoteEntry.js", + remoteName: "settings_user", + exposedModule: "./Settings", + }); +}; +} + +@NgModule({ +declarations: [AppComponent], +imports: [BrowserModule, RouterModule.forRoot(routes)], +providers: [ + { + provide: APP_INITIALIZER, + useFactory: initializeApp, + multi: true, + }, +], +---- + +Webpack config settings in `webpack.config.ts` for the Angular shell define remotes and shared React libraries, allowing the shell to be a federated module + +[source, typescript] +---- +new container.ModuleFederationPlugin({ + name: "angular-shell", + filename: "remoteEntry.js", + remotes: { + profile_user: `profile_user@http://localhost:3001/remoteEntry.js`, + settings_user: `settings_user@http://localhost:3002/remoteEntry.js`, + }, + shared: { + react: { + singleton: true, + requiredVersion: deps.react, + }, + "react-dom": { + singleton: true, + requiredVersion: deps["react-dom"], + }, + }, +}), +---- + +Additionally, Webpack devServer headers with `"Access-Control-Allow-Origin": "*"` are set to avoid CORS errors. + +To adapt the Angular application, you'll need to modify the `angular.json` file, replacing the default Webpack configuration with a custom one. + +The changes are the following: + +[source, json] +---- +... + "builder": "@angular-builders/custom-webpack:browser", +... + "scripts": [], + "customWebpackConfig": { + "path": "webpack.config.ts", + "replaceDuplicatePlugins": true + } +... + "cli": { + "packageManager": "yarn" + } +---- + +Next, we'll focus on injecting the React component into the Angular shell. To achieve this, a `profile-user.component.ts` container has been created to integrate the React component. The ProfileReactComponent is loaded asynchronously within the `ngAfterViewInit` lifecycle hook. Additionally, a `decl.d.ts` file has been introduced to inform Angular that `"profile_user"` is a valid import directory. + +Here's the code from `angular-shell/src/app/profile-user/profile-user.component.ts``: + +[source, typescript] +---- +ngAfterViewInit() { + this.root = createRoot(this.containerRef.nativeElement); + this.root.render("Loading script..."); + try { + import("profile_user/ProfileReactComponent").then((val) => { + this.root.render( + React.createElement(val.ProfileReactComponent, { + ...this.user, + onClick: this.updateCurrentUser, + }) + ); + }); + } catch (error) { + console.log("Error", error); + } +} +---- + +The React Profile component interacts with the Angular shell through the `onClick` function. This allows the Angular shell app to update user data when changes are made to the name and email in the React component. + +Similarly, the Vue Settings component is injected into the `settings.component.ts` wrapper. The approach closely resembles that of the React Profile component. Here's the code from angular-shell/src/app/settings/settings.component.ts: + +[source, typescript] +---- +import("settings_user/Settings").then((val) => { + this.renderer.appendChild( + this.containerVueRef.nativeElement, + new val.default() + ); +}); +---- + +To run the Angular shell app, please take note of using `yarn`. This is necessary to override the webpack version for the Angular CLI. In the `angular-shell` folder, execute the following commands: + +1. Create a new Angular app (skip the installation step): ++ +[source, bash] +---- +ng new --skip-install +---- ++ +2. Configure the CLI to use `yarn` as the package manager: ++ +[source, bash] +---- +ng config cli.packageManager yarn +---- ++ +3. Install the dependencies: ++ +[source, bash] +---- +yarn install +---- + +The app can be accessed via http://localhost:4201. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/auth0.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/auth0.adoc new file mode 100644 index 00000000000..0c346451ba8 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/auth0.adoc @@ -0,0 +1,158 @@ += Authentication for Micro Frontends: Integrating Shared User Context + +This documentation outlines the process for sharing authentication states, such as the current user context, across micro-frontends within an application shell using Auth0 and Angular. + +== Overview + +In micro frontend architectures, it's common to encounter the need to share context information such as the current user's data—across different parts of the application. This document describes how to achieve seamless authentication state sharing using `@auth0/auth0-angular`, a package that facilitates integration with Auth0, an identity management provider. + +== Prerequisites + +Before proceeding, ensure you have: + +- A basic understanding of Angular and micro frontend concepts. +- An Auth0 account and a registered web application on https://manage.auth0.com/[Auth0's management dashboard]. +- Angular applications set up with Module Federation. + +== Step-by-Step Guide + +=== Step 1: Installing the Auth0 Package + +Install `@auth0/auth0-angular` via npm to use Auth0 for authentication in your Angular applications: + +[source, bash] +---- +npm i @auth0/auth0-angular +---- + +=== Step 2: Configuring Auth0 + +1. Navigate to https://manage.auth0.com/[Auth0's management dashboard] and register your Single Page Application (SPA). +2. Note down the `domain` and `clientId` provided after registration. +3. Add your application's URL (e.g., `http://localhost:4200`) to the Allowed Callback URLs section. + +=== Step 3: Importing the AuthModule + +Import the `AuthModule` from `@auth0/auth0-angular` into the AppModule of both the shell application and the micro frontend(s). + +[source, typescript] +---- +// In both the shell and micro frontend's app.module.ts files + +import { AuthModule } from '@auth0/auth0-angular'; + +@NgModule({ + imports: [ + AuthModule.forRoot({ + domain: 'YOUR_AUTH0_DOMAIN', + clientId: 'YOUR_AUTH0_CLIENT_ID' + }), + // other imports... + ], + // other module properties... +}) +export class AppModule { } +---- + +Replace `YOUR_AUTH0_DOMAIN` and `YOUR_AUTH0_CLIENT_ID` with the actual values from your Auth0 application settings. + +=== Step 4: Implementing Login Functionality + +In your shell application, use the `AuthService` to implement the login functionality. + +[source, typescript] +---- +// projects/shell/src/app/home/home.component.ts + +import { Component } from '@angular/core'; +import { AuthService } from '@auth0/auth0-angular'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'] +}) +export class HomeComponent { + user$ = this.auth.user$; + + constructor(private auth: AuthService) {} + + login(): void { + this.auth.loginWithRedirect(); + } +} +---- + +=== Step 5: Displaying User Information + +In the shell's home component, display the logged-in user's name. + +[source, html] +---- + + +

Welcome!

+ +

+ User: {{user.name}} +

+ +
+ +
+---- + +==== Step 6: Using AuthService in Micro Frontends + +In your micro frontend components, you can similarly use `AuthService` to access the current user's information. + +[source, typescript] +---- +// Example from a micro frontend's address.component.ts + +import { AuthService } from '@auth0/auth0-angular'; +import { FormBuilder } from '@angular/forms'; +import { take } from 'rxjs/operators'; + +@Component({ + // component metadata... +}) +export class AddressComponent { + // component properties... + + constructor( + private auth: AuthService, + private fb: FormBuilder) { + + this.auth.user$.pipe(take(1)).subscribe(user => { + if (!user) return; + // Use user information to pre-fill form, etc. + }); + } + + // other component methods... +} +---- + +=== Step 7: Configuring Module Federation for Shared Authentication + +Ensure the `@auth0/auth0-angular` package is shared across your shell and micro frontends to maintain a single authentication state. + +[source, typescript] +---- +// In webpack.config.js of both shell and micro frontends + +module.exports = { + // existing configuration... + shared: share({ + "@auth0/auth0-angular": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, + // other shared packages... + }) +}; +---- + +This configuration ensures a single instance of the `AuthService` is used across your application, facilitating a shared authentication state. + +==== Conclusion + +Following these steps, you can achieve seamless authentication state sharing across micro-frontends in your Angular applications. This setup not only simplifies managing user contexts but also enhances the overall user experience by maintaining consistent authentication states across your application's modular components. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/i18n-angular.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/i18n-angular.adoc new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/index.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/index.adoc new file mode 100644 index 00000000000..34bf3af6cec --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/index.adoc @@ -0,0 +1,268 @@ += Setting Up Module Federation in an Angular CLI Application + +The integration of Module Federation with Angular CLI facilitates a modular development approach, allowing for dynamic code loading and sharing across different Angular projects. This guide dives deep into the steps required for this integration, aided by the `@angular-architects/module-federation` plugin. + +== Prerequisites + +- **Angular CLI**: Ensure that you have Angular CLI version 10 or later installed in your development environment. +- **Plugin Installation**: Having the `@angular-architects/module-federation` plugin installed in your project is crucial as it extends the Angular CLI to support Module Federation. + +== Installation + +=== 1. Installing the Plugin: + +The initial step towards leveraging Module Federation is instructing the Angular CLI to employ Module Federation during the build process. However, given the CLI's encapsulation of Webpack, a custom builder is necessitated to unveil the power of Module Federation. + +The `@angular-architects/module-federation` package graciously provides this custom builder, simplifying the integration process. To initiate, simply employ the ng add command to integrate it into your projects: + +[source, bash] +---- +ng add @angular-architects/module-federation --project shell --port 4200 --type host +ng add @angular-architects/module-federation --project mfe1 --port 4201 --type remote +---- + +For those utilizing Nx, the procedure deviates slightly. First, execute an npm install to acquire the library, followed by invoking the init schematic: + +[source, bash] +---- +npm i @angular-architects/module-federation -D +ng g @angular-architects/module-federation:init --project shell --port 4200 --type host +ng g @angular-architects/module-federation:init --project mfe1 --port 4201 --type remote +---- + +NOTE: The --type argument, introduced in version 14.3, which meticulously ensures that only the requisite configuration is generated. + +In our representation, the project labeled 'shell' encapsulates the code for the shell, whereas 'mfe1' denotes Micro Frontend 1. The execution of the aforementioned commands orchestrates several pivotal tasks: + +*Skeleton Generation:* +A rudimentary webpack.config.js skeleton is crafted to facilitate Module Federation integration. + +*Custom Builder Installation:* +A custom builder is installed, empowering Webpack within the CLI to utilize the generated webpack.config.js. + +*Port Assignment:* +A distinct port is allocated for the ng serve command, enabling simultaneous serving of multiple projects. + +It's imperative to acknowledge that the generated `webpack.config.js` is a partial configuration solely dedicated to governing Module Federation. The remaining configurations are autonomously generated by the CLI, maintaining the usual workflow intact. + +This setup lays down a robust foundation for harnessing Module Federation, enabling seamless code sharing and dynamic loading across your Angular CLI projects. + +== The Shell (Host) Configuration + +In the realm of Module Federation, the Shell, also referred to as the Host, serves as a pivotal point of integration. This segment delineates the configuration of the Shell to support the lazy-loading of a `FlightModule` through routing. + +== Routing Configuration + +Commence by defining the application routes, specifying a lazy-loaded `FlightModule` via a virtual path: + +[source, javascript] +---- +export const APP_ROUTES: Routes = [ + { + path: '', + component: HomeComponent, + pathMatch: 'full' + }, + { + path: 'flights', + loadChildren: () => import('mfe1/Module').then(m => m.FlightsModule) + }, + ]; +---- + +In this configuration, the path `'mfe1/Module'` is a virtual representation, as it doesn't physically reside within the Shell. It's a pointer towards a module in a separate project. + +=== TypeScript Typing + +To appease the TypeScript compiler, create a type definition for the virtual path: + +[source, typescript] +---- +// decl.d.ts +declare module 'mfe1/Module'; +---- + +This declaration guides the TypeScript compiler in understanding the virtual path, easing the import process. + +== Webpack Configuration + +Further, instruct Webpack to resolve all paths prefixed with `mfe1` to a remote project. This is achieved within the `webpack.config.js` file, generated earlier: + +[source, javascript] +---- +const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack'); + +module.exports = withModuleFederationPlugin({ + + remotes: { + "mfe1": "http://localhost:4201/remoteEntry.js", + }, + + shared: { + ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }), + }, + +}); +---- + +In the `remotes` section, the path `mfe1` is mapped to the remote micro-frontend, specifically to its remote entry point. This remote entry, generated by Webpack, holds essential information for interaction with the micro-frontend. + +Although hardcoding the remote entry's URL suffices for development, a dynamic approach is necessary for production. The topic of xref:core-features/dynamic-remotes.adoc[Dynamic Remotes] is explored in a dedicated documentation page. + +- The `shared` property outlines the npm packages to be shared between the Shell and the micro-frontend(s). Utilizing the `shareAll` helper method, all dependencies found in your `package.json` are shared. While this expedites a functional setup, it might result in excessive shared dependencies, a concern to be addressed later. + +- The duo of `singleton: true` and `strictVersion: true` instructs Webpack to raise a runtime error when version incompatibility is detected between the Shell and the micro-frontend(s). Altering `strictVersion` to `false` would merely trigger a runtime warning. + +- The `requiredVersion: 'auto'` setting, a provision of the `@angular-architects/module-federation` plugin, auto-resolves the version from your `package.json`, averting potential issues. + +== Configuring the Remote + +The Micro-frontend, termed as the Remote in the context of Module Federation, essentially mirrors a typical Angular application setup. It comprises defined routes within the `AppModule` and encapsulates a `FlightsModule` to manage flight-related functionalities. This section outlines the configuration steps to ensure the seamless loading of the `FlightsModule` into the Shell (Host). + +=== Route Definition + +Start by defining the basic routes within the `AppModule`: + +[source, typescript] +---- +export const APP_ROUTES: Routes = [ + { path: '', component: HomeComponent, pathMatch: 'full'} + ]; +---- + +This simple routing setup navigates to a `HomeComponent` when the application is accessed. + +=== Module Creation + +Proceed to create a `FlightsModule` to handle flight-related operations: + +[source, typescript] +---- +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(FLIGHTS_ROUTES) + ], + declarations: [ + FlightsSearchComponent + ] + }) + export class FlightsModule { } +---- + +This module contains a route to a `FlightsSearchComponent` defined as follows: + +[source, typescript] +---- +export const FLIGHTS_ROUTES: Routes = [ + { + path: 'flights-search', + component: FlightsSearchComponent + } + ]; +---- + +=== Exposing Modules via Webpack Configuration + +To enable the loading of `FlightsModule` into the Shell, it's imperative to expose it through the Remote's Webpack configuration: + +[source, javascript] +---- +const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack'); + +module.exports = withModuleFederationPlugin({ + + name: 'mfe1', + + exposes: { + './Module': './projects/mfe1/src/app/flights/flights.module.ts', + }, + + shared: { + ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }), + }, + +}); +---- + +In this configuration: + +- The `name` property identifies the micro-frontend as `mfe1`. +- The `exposes` property signifies the exposure of `FlightsModule` under the public name `Module`, allowing its consumption by the Shell. +- The `shared` property lists the libraries to be shared with the Shell, using the `shareAll` method to share all dependencies found in your `package.json`. The `singleton: true` and `strictVersion: true` properties ensure that a single version of shared libraries is used, and a runtime error is triggered in case of version incompatibility, respectively. + +== Testing the Configuration + +Having set up the Shell (Host) and Micro-frontend (Remote), it's time to test the configuration to ensure the seamless integration of Module Federation. + +=== Running the Applications + +==== 1. Starting the Shell and Micro-frontend: + +Kickstart both the Shell and micro-frontend using the following commands: + +[source, bash] +---- +ng serve shell -o +ng serve mfe1 -o +---- + +Upon executing these commands, the Shell and Micro-frontend will be served, and the respective applications will open in your default web browser. + +==== 2. Loading the Micro-frontend: + +Navigate to the Flights section in the Shell, and observe the Micro-frontend being dynamically loaded. + +//TODO: Add Image + +//![Shell Interface with Flights Section](screenshot) + +The plugin also installs a handy npm script `run:all` during the `ng-add` and `init` schematics, allowing for simultaneous serving of all applications: + +[source, bash] +---- +npm run run:all +---- + +For serving selected applications, append their names as command line arguments: + +[source, bash] +---- +npm run run:all shell mfe1 +---- + +=== A Closer Look at Main.ts + +Delving into the `main.ts` file, you might notice a slight deviation from the usual: + +[source, typescript] +---- +import('./bootstrap') + .catch(err => console.error(err)); +---- + +The typical code in `main.ts` has been relocated to a new `bootstrap.ts` file, a modification orchestrated by the `@angular-architects/module-federation` plugin. This change facilitates Module Federation's library version decision-making process during runtime. The asynchronous nature of dynamic imports, as seen above, allows Module Federation to ascertain and load the appropriate version of shared libraries. + +=== Optimizing Dependency Sharing + +The initial setup using `shareAll` provides a quick, working configuration but might result in excessive shared bundles. To optimize this aspect, consider transitioning from `shareAll` to the `share` helper for more granular control over shared dependencies: + +[source, javascript] +---- +// Replace shareAll with share: +const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack'); + +module.exports = withModuleFederationPlugin({ + + // Specify the packages to share: + shared: share({ + "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, + "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, + "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, + "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, + }), + +}); +---- + +In this configuration, the `share` helper allows for explicit sharing of selected packages, enabling a more optimized bundle sharing, and a potential reduction in the load times. diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/index2.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/index2.adoc new file mode 100644 index 00000000000..9f6641734eb --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/index2.adoc @@ -0,0 +1,351 @@ += Micro-frontends with Angular and Webpack Module Federation + +== Overview + +Module Federation has been a game-changer in the micro-frontends landscape. When combined with Angular, it offers a robust and scalable solution for distributed front-end architecture. This guide goes beyond the basics and explores a practical example, detailing how to configure the Webpack `ModuleFederationPlugin` in an Angular shell and a remote application. + +Prerequisites +Before diving into the setup, ensure you meet the following prerequisites: + +- *Node.js and npm:* Make sure Node.js and npm are installed on your machine. +- *Angular and Webpack Knowledge:* A basic understanding of Angular and Webpack is required. +- *Module Federation:* Since this article assumes you're familiar with Module Federation, make sure you've covered its foundational concepts. + +== Preparatory Steps + +=== Force Resolution of Webpack 5 + +==== Why Force Webpack 5? + +Angular CLI projects often come pre-configured with Webpack, but to ensure that Module Federation is fully supported, you need to opt-in to Webpack 5. + +==== How to Force? + +Open your `package.json` and add a `resolutions` key to force the use of Webpack 5: + +[source, json] +---- +{ + "resolutions": { + "webpack": "^5.0.0" + } +} +---- + +NOTE: The `resolutions` key is not natively supported by npm. It's advisable to use Yarn as your package manager. Alternatively, you can try using the `npm-force-resolutions` package, although it hasn't been extensively tested for this setup. + +=== Specify Package Manager in Angular CLI + +==== Why Specify? + +If you opt to use Yarn, you'll need to inform the Angular CLI to use it as the default package manager. + +==== How to Specify? + +In your `angular.json` file, add the following configuration: + +[source, json] +---- +{ + "cli": { + "packageManager": "yarn" + } +} +---- + +=== Add Customizable Webpack Configuration + +==== Why Customize? + +While Angular does use Webpack internally, it doesn't expose the Webpack configuration for customization by default. + +==== How to Customize? + +You have a couple of options for exposing the Webpack configuration, such as using `Ngx-build-plus` or `@angular-builders/custom-webpack`. In this example, we'll use the latter. + +First, install the package: + +[source, bash] +---- +yarn add @angular-builders/custom-webpack -D +# or +npm i -D @angular-builders/custom-webpack +---- + +Then, update your `angular.json` file to use this custom builder for both the build and serve commands: + +[source, json] +---- +{ + "projects": { + "your-project-name": { + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "customWebpackConfig": { + "path": "webpack.config.ts" + } + } + }, + "serve": { + "builder": "@angular-builders/custom-webpack:dev-server" + } + } + } + } +} +---- + +NOTE: The custom Webpack configuration will be merged with Angular's default configuration, allowing you to specify only the changes needed for Module Federation. + +== Webpack Configuration for Shell Application + +=== Unique Output Name Configuration + +==== Why Define a Unique Name? + +Webpack uses the name from the `package.json` by default. However, to avoid conflicts, especially in monorepos, it's recommended to manually define a unique name. + +==== How to Define? + +In your `webpack.config.ts`, set the `uniqueName` of the output configuration: + +[source, javascript] +---- +config.output.uniqueName = 'shell'; +---- + +NOTE: If you're not using a monorepo and your `package.json` already has unique names, you can skip this step. + +=== Runtime Chunk Optimization + +==== Why Disable? + +Due to a current bug, setting the `runtimeChunk` optimization to `false` is essential; otherwise, the Module Federation setup will break. + +==== How to Disable? + +In your `webpack.config.ts`, disable the `runtimeChunk` optimization: + +[source, javascript] +---- +config.optimization.runtimeChunk = false; +---- + +=== Adding Module Federation Plugin Configuration + +==== Why Add This Plugin? + +The `ModuleFederationPlugin` is crucial for defining how modules from remote applications will be consumed in the shell application. + +==== How to Add? + +In your `webpack.config.ts`, add the `ModuleFederationPlugin` to the plugins array: + +[source, typescript] +---- +import { CustomWebpackBrowserSchema, TargetOptions } from '@angular-builders/custom-webpack'; +import { Configuration, container } from 'webpack'; + +export default (config: Configuration, options: CustomWebpackBrowserSchema, targetOptions: TargetOptions) => { + // ... existing configuration + + config.plugins.push( + new container.ModuleFederationPlugin({ + remotes: { + 'mf1': 'mf1@http://localhost:4300/mf1.js' + }, + shared: { + '@angular/animations': {singleton: true, strictVersion: true}, + '@angular/core': {singleton: true, strictVersion: true}, + // ... other shared modules + } + }) + ); + + return config; +}; +---- + +Here, in the `remotes` object, we map remote module names to their respective locations. The key ('mf1' in this example) is the name used to import the module in the shell application. The value specifies the location of the remote file, which in this example is `http://localhost:4300/mf1.js`. + +== Shared Dependencies between Shell and Remote Module + +=== Importance of Shared Dependencies + +The `shared` section in the Webpack configuration plays a pivotal role in defining modules that are common between the shell and the remote module. Doing so can significantly reduce the bundle size, enhancing the user experience. + +=== Handling Version Mismatches + +Webpack will emit runtime errors if there are major version incompatibilities between the shell and remote apps. Harmonizing versioning across development teams is essential to prevent such issues. + +=== Semantic Versioning and Flexibility + +Webpack adheres to semantic versioning when resolving shared dependencies. It’s advisable to allow some flexibility in version selection using operators like `^` or `>=`. This ensures that only the necessary versions are loaded, minimizing the risk of loading multiple conflicting versions of a library. + +== Configuring the Remote Module/Application + +=== Webpack Configuration for Remote Module + +==== Setting Unique Output Name and Disabling Runtime Chunk + +Similar to the shell application, define a unique output name and disable the `runtimeChunk` optimization: + +[source, typescript] +---- +config.output.uniqueName = 'contact'; +config.optimization.runtimeChunk = false; +---- + +==== Adding Module Federation Plugin + +Configure the `ModuleFederationPlugin` as follows: + +[source, typescript] +---- +import { CustomWebpackBrowserSchema, TargetOptions } from '@angular-builders/custom-webpack'; +import { Configuration, container } from 'webpack'; +import * as path from 'path'; + +export default (config: Configuration, options: CustomWebpackBrowserSchema, targetOptions: TargetOptions) => { + // ... existing configuration + + config.plugins.push( + new container.ModuleFederationPlugin({ + filename: "mf1.js", + name: "mf1", + exposes: { + './Contact': path.resolve(__dirname, './src/app/contact/contact.module.ts'), + './Clock': path.resolve(__dirname, './src/app/clock/index.ts'), + }, + shared: { + '@angular/animations': {singleton: true, strictVersion: true}, + // ... other shared modules + } + }) + ); + + return config; +}; +---- + +Here, the `filename` and `name` properties specify the JavaScript file's name and the namespace for the module container in the global window object. These are the exact values used by the shell application when loading the remote module. + +==== Exposing Modules + +The `exposes` object specifies the modules to be exported. In this example: + +- `./Contact` exports an Angular `NgModule` with child routes. +- `./Clock` exports an Angular component for runtime rendering. + +== Using Module Federation in Angular Routing + +=== Declare Remote Modules + +Before you can use the remote modules, you need to inform TypeScript about their existence as they will be loaded dynamically at runtime. + +==== How to Declare? + +Create a new TypeScript definition file, `remote-modules.d.ts`, next to your routing module: + +[source, typescript] +---- +declare module 'mf1/Contact'; +declare module 'mf1/Clock'; +---- + +=== Lazy-Loading Remote Modules in Routes + +Just like you would with native lazy-loaded modules, you can now import remote modules into your Angular routing configuration. + +==== How to Load? + +Modify your route configuration as follows: + +[source, typescript] +---- +const routes: Routes = [ + { + path: '', + loadChildren: () => HomeModule + }, + { + path: 'contact', + loadChildren: () => import('mf1/Contact').then(m => m.ContactModule) + }, + // ... other routes +]; +---- + +== Dynamic Component Creation of Remote Modules + +Creating components dynamically from remote modules offers a more advanced level of integration. This involves setting up a service and a directive to handle the dynamic rendering. + +=== The Remote Module Loader Service + +This service is responsible for dynamically loading remote modules and resolving component factories. + +[source, typescript] +---- +@Injectable({ + providedIn: 'root' +}) +export class RemoteModuleLoader { + constructor(private _componentFactoryResolver: ComponentFactoryResolver) {} + + async loadRemoteModule(name: string) { + const [scope, moduleName] = name.split('/'); + const moduleFactory = await window[scope].get('./' + moduleName); + return moduleFactory(); + } + + getComponentFactory(component: Type): ComponentFactory { + return this._componentFactoryResolver.resolveComponentFactory(component); + } +} +---- + +=== The Remote Component Renderer Directive + +This structural directive dynamically creates components within its own view container using the component factory obtained from the Remote Module Loader Service. + +[source, typescript] +---- +@Directive({ + selector: '[remoteComponentRenderer]' +}) +export class RemoteComponentRenderer implements OnInit { + @Input() set remoteComponentRenderer(componentName: string) { /* ... */ } + @Input() set remoteComponentRendererModule(moduleName: RemoteModule) { /* ... */ } + + // ... other code + + private async renderComponent() { + const module = await this.remoteModuleLoaderService.loadRemoteModule(this._moduleName); + const componentFactory = this.remoteModuleLoaderService.getComponentFactory(module[this._componentName]); + this.viewContainerRef.createComponent(componentFactory, undefined, this.injector); + } +} +---- + +==== Usage in View + +In your Angular view, you can use the directive as follows: + +[source, html] +---- + +---- + +== Summary + +This guide has walked you through the dynamic integration of remote modules in an Angular application leveraging Webpack's Module Federation. Specifically, you've learned: + +- How to set up Yarn as your package manager. +- Customizing the Webpack configuration for your Angular build. +- Utilizing Module Federation in both shell and micro-frontend applications. +- Lazy-loading remote modules in Angular routing. +- Dynamically creating components from remote modules. + +For a production-ready setup, additional steps are necessary, which will be covered in a future guide. Feel free to reach out to us via our social networks with any questions you may have on this technique. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/mf-ssr-angular.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/mf-ssr-angular.adoc new file mode 100644 index 00000000000..79c8f3ef7c7 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/mf-ssr-angular.adoc @@ -0,0 +1,108 @@ += Angular Module Federation with Server-Side Rendering + +Historically, Module Federation was limited to Client-Side Rendering (CSR), primarily benefiting Single Page Applications (SPAs). However, the evolving web development landscape has increasingly favored Server-Side Rendering (SSR) for its myriad benefits. + +== Nx's Push into SSR with Module Federation (From Version 15.4) + +With the advent of version 15.4, Nx has introduced Module Federation with SSR support, marking a significant milestone. This fusion of Module Federation and SSR in Nx Workspaces leverages the strengths of both technologies. + +For a comprehensive understanding of Nx and its integration with Module Federation, we highly recommend consulting the official Nx documentation. It offers in-depth insights and practical guidance to effectively leverage these technologies. You can access this valuable resource here: + +- https://nx.dev/recipes/module-federation:[Official Nx Documentation on Module Federation]. + +This guide is focused on guiding you through the setup of Module Federation with Server-Side Rendering specifically for Angular applications. + +== How It Works + +In a traditional SSR application, the server is responsible for rendering the application based on the route requested by the browser. When combining Module Federation with SSR, this concept is expanded. If a route points to a remote module, the host server delegates the route processing to the remote server, which then renders the HTML and sends it back to the browser. + +This integration not only harnesses the full potential of SSR but also maintains the flexibility of dividing the build into smaller segments. It facilitates independent deployment of features within the application, allowing updates to the remote server without necessitating a full redeployment of the host server. + +== Step-by-Step Guide + +We'll start by creating a host application named 'dashboard' and a remote application called 'login'. + +=== Creating the Nx Workspace + +1. Begin by executing the following command to create a new Nx workspace: + +[source, bash] +---- +npx create-nx-workspace@latest myorganization +---- + +During this process, you'll encounter prompts regarding the workspace type and preset. + +Respond to the prompts as follows: + +- Workspace type: *integrated* +- Workspace content: *apps* +- Enable distributed caching (CI speed enhancement): *No* + +**Opting for Nx Cloud (Optional)** + +While setting up your workspace, you'll also be asked about adding Nx Cloud. This guide doesn't cover Nx Cloud, but its integration with Module Federation is advantageous for shared caching across teams and CI, enhancing build times. Learn more about Nx Cloud at https://nx.app/[Nx Cloud Website] + +=== Installing Nx Angular Plugin + +1. After creating the workspace, navigate to it: ++ +[source, bash] +---- +cd myorganization +---- ++ +2. Install the Official Nx Angular Plugin: ++ +[source, bash] +---- +npm install @nrwl/angular +---- + +=== Generating the Applications + +1. Use a single command to scaffold your Module Federation architecture with SSR: + +[source, bash] +---- +npx nx g host dashboard --remotes=login --ssr +---- + +This command generates two Angular Universal (SSR) applications and configures Webpack for both browser and server with Module Federation. + +=== Serving the Applications + +1. To serve the 'dashboard' (host) and 'login' (remote) applications, run: + +[source, bash] +---- +npx nx serve-ssr dashboard +---- + +This builds the browser and server bundles for the 'login' application and runs it using Node.js. Note that the 'login' application runs without file watchers, so changes in its code won't automatically reflect. + +=== Caching and Real-Time Updates + +- Nx caches the build of the browser and server bundles for the 'login' application. On subsequent runs, it uses this cache instead of rebuilding the application. +- For the 'dashboard' application, the server build includes file watchers, enabling real-time updates to the code. + +=== Verifying the Setup + +1. Upon successful compilation, you'll see a success message indicating the server's listening address, typically `http://localhost:4200`. +2. Open this address in your browser and inspect the Network tab in DevTools. + +=== Module Federation in Action + +- Initially, Angular Universal will handle rendering, and subsequent navigation will switch to Client-Side Rendering (CSR). +- Navigating to http://localhost:4200/login will reveal the server-rendered HTML for the login page, showcasing Module Federation's ability to resolve and render modules from remote servers. + +=== Live Updates for the Login Application + +If you are actively working on the 'login' application and need to see the results of your changes in real time, you can enable server rebuilds upon code modification. This can be achieved by using the `devRemotes` flag: + +[source, bash] +---- +npx nx serve-ssr dashboard --devRemotes=login +---- + +Using this command, the server will rebuild the 'login' application whenever you make changes, allowing for an iterative and efficient development process. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/okta-auth.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/okta-auth.adoc new file mode 100644 index 00000000000..f41b383e0e0 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/okta-auth.adoc @@ -0,0 +1,237 @@ += Integrating Authentication with OpenID Connect in Module Federation + +== Introduction + +Module Federation enhances the management of shared code and state across micro frontends. This guide will walk you through adding authentication to your project using OpenID Connect with Okta. By the end of this documentation, your application will be able to handle authenticated states both within the existing application and a newly integrated micro frontend. + +== Prerequisites + +- A free Okta developer account. If you do not have one, you can sign up using the Okta CLI. +- Okta CLI installed on your machine. + +== Setting Up Okta Authentication + +=== Create an Okta Application + +1. Register or Log In to Okta: ++ +- To register for a new account, execute `okta register` in your terminal. +- If you already have an account, log in by running `okta login`. + +2. Create Your Application: ++ +- Execute `okta apps create`. +- When prompted, accept the default application name or provide a new one. +- Select *Single-Page App (SPA)* and confirm by pressing Enter. +- For *Redirect URI*, use `http://localhost:4200/login/callback` and set the Logout *Redirect URI* to `http://localhost:4200`. + +=== Configure Application in Okta + +- The Okta CLI creates an OIDC SPA in your Okta Org, configures redirect URIs, grants access to the Everyone group, and adds `http://localhost:4200 `as a trusted origin. ++ +NOTE: The *Okta Admin Console* can also be used for app creation. For Angular apps, refer to the Okta documentation on creating an Angular application. + +*Okta Application Configuration Example:* + +- *Issuer:* `https://dev-12345.okta.com/oauth2/default` +- *Client ID:* `0oab12345CDEF` ++ +NOTE: Ensure you note down the *Issuer* and *Client ID*; these are crucial for your application's configuration. + +=== Install Required Libraries + +Add Okta Angular and Okta Auth JS libraries to your project: + +[source, bash] +---- +npm install @okta/okta-angular@5.2 @okta/okta-auth-js@6.4 +---- + +=== Configure Okta in Angular Module + +Import and configure `OktaAuthModule` and `OktaAuth` in your shell project's `AppModule`. Replace `{yourOktaDomain}` and `{yourClientID}` with your specific Okta domain and client ID. + +[source, typescript] +---- +import { NgModule } from '@angular/core'; +import { OKTA_CONFIG, OktaAuthModule } from '@okta/okta-angular'; +import { OktaAuth } from '@okta/okta-auth-js'; + +const oktaAuth = new OktaAuth({ + issuer: 'https://{yourOktaDomain}/oauth2/default', + clientId: '{yourClientID}', + redirectUri: window.location.origin + '/login/callback', + scopes: ['openid', 'profile', 'email'] +}); + +@NgModule({ + imports: [ + OktaAuthModule, + // other imports + ], + providers: [ + { provide: OKTA_CONFIG, useValue: { oktaAuth } } + ], + // other module properties +}) +---- + +=== Configure Routing for Authentication + +Update the `app-routing.module.ts` to include the login callback route. + +[source, typescript] +---- +import { Routes } from '@angular/router'; +import { OktaCallbackComponent } from '@okta/okta-angular'; + +const routes: Routes = [ + { path: '', component: ProductsComponent }, + { path: 'basket', loadChildren: () => import('mfeBasket/Module').then(m => m.BasketModule) }, + { path: 'login/callback', component: OktaCallbackComponent } +]; +---- + +== Implementing Authentication Logic + +=== Update Application Component + +Modify `app.component.ts` to include sign-in and sign-out logic, utilizing the Okta libraries. Update the authentication state variables accordingly. + +[source, typescript] +---- +import { Component, Inject } from '@angular/core'; +import { Observable } from 'rxjs'; +import { filter, map, shareReplay } from 'rxjs/operators'; +import { OKTA_AUTH, OktaAuthStateService } from '@okta/okta-angular'; +import { OktaAuth } from '@okta/okta-auth-js'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html' +}) +export class AppComponent { + public isAuthenticated$: Observable; + public name$: Observable; + + constructor(private oktaStateService: OktaAuthStateService, @Inject(OKTA_AUTH) private oktaAuth: OktaAuth) { + this.isAuthenticated$ = this.oktaStateService.authState$ + .pipe( + filter(authState => !!authState), + map(authState => authState.isAuthenticated ?? false), + shareReplay() + ); + + this.name$ = this.oktaStateService.authState$ + .pipe( + filter(authState => !!authState && !!authState.isAuthenticated), + map(authState => authState.idToken?.claims.name ?? '') + ); + } + + public async signIn(): Promise { + await this.oktaAuth.signInWithRedirect(); + } + + public async signOut(): Promise { + await this.oktaAuth.signOut(); + } +} +---- + +=== Handle Sign-In and Sign-Out in the UI + +In `app.component.html`, add the UI logic for sign-in and sign-out buttons. + +[source, html] +---- +
  • + + + + + +
  • +---- + +=== Testing the Application + +Run the project using npm run start (or the appropriate command for your setup) to test authentication functionality. Successful implementation allows users to sign in and out, with the profile information being accessible upon signing in. + +== Adding User Profiles with Module Federation + +This section expands on incorporating Module Federation to share authenticated state across the main application and the micro-frontend. We'll explore how to set up a new Angular application, configure routing, and update components to include profile details. + +=== Creating a New Angular Application + +1. **Generate a New Angular Application:** Stop the current project execution and run the following command to create a new Angular application named `mfe-profile`: ++ +[source, bash] +---- +ng generate application mfe-profile --routing --style css --inline-style --skip-tests +---- ++ +This command accomplishes several tasks: +- Generates a new application with a module and component. +- Adds a separate routing module. +- Defines CSS styles to be inline within components. +- Skips the creation of test files for the initial component. + +2. **Generate HomeComponent and ProfileModule:**Execute the following commands to create a `HomeComponent` and a `ProfileModule` within the `mfe-profile` application: ++ +[source, bash] +---- +ng generate component home --project mfe-profile +ng generate module profile --project mfe-profile --module app --routing --route profile +---- ++ +These commands create: +- A `HomeComponent` for the default route. +- A `ProfileModule` with routing and a default `ProfileComponent`, added as a lazy-loaded route to the `AppModule`. + +=== Updating the Application Code + +1. **Configure Routing:** Update `projects/mfe-profile/src/app/app-routing.module.ts` to include a route for `HomeComponent` and a lazy-loaded route for `ProfileModule`: ++ +[source, typescript] +---- +const routes: Routes = [ + { path: '', component: HomeComponent }, + { path: 'profile', loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule) } +]; +---- ++ +2. **Update AppComponent and HomeComponent Templates:** +- For `app.component.html`, replace the content with a message of your choice and a `router-outlet` for navigation. +- For `home.component.html`, provide a message guiding users to the Profile page with a router link to `/profile`. + +=== Profile Component Configuration + +1. **Implement Profile Logic:** Update `projects/mfe-profile/src/app/profile/profile.component.ts` to include properties for user profile information and authentication state, utilizing `OktaAuthStateService`. + +2. **Update Profile Template:** Modify the template to display user profile details, such as name and email, and the last sign-in time. + +=== Integrating Module Federation + +1. **Add Module Federation to `mfe-profile`:** Use the `@angular-architects/module-federation` schematic to prepare `mfe-profile` for Module Federation, specifying port 4202. ++ +[source, bash] +---- +ng add @angular-architects/module-federation --project mfe-profile --port 4202 +---- ++ +2. **Configure `mfe-profile` as a Remote:** Update `webpack.config.js` in `mfe-profile` to expose `ProfileModule` for the host application. ++ +3. **Update Host Application Configuration:** Modify the shell application's `webpack.config.js` to include `mfe-profile` as a remote, enabling the host to access the Profile micro-frontend. ++ +4. **Share Authenticated State:** +- Update `webpack.config.js` in the shell application to share Okta libraries as singletons. +- Ensure `mfe-profile` also shares the Okta libraries to utilize the authenticated state. + +### Running the Integrated Application + +After configuring Module Federation and updating both the shell and micro-frontend applications, you can run the project using `npm run run:all`. This setup allows you to log in, view your profile, log out, and interact with other parts of the application seamlessly across the main and micro-frontend parts. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/service-workers-mf.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/service-workers-mf.adoc new file mode 100644 index 00000000000..96d048b6a35 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/service-workers-mf.adoc @@ -0,0 +1,319 @@ += Introduction + +The use of the Module Federation plugin in Angular projects can often lead to challenges, particularly when such projects dynamically consume large federated code chunks from remote containers. These scenarios may result in downtime of remote servers or lags during route navigation. A viable solution to these issues is the integration of Angular service workers for caching and serving federated code. + +Service workers play a crucial role in enhancing the user experience for Angular applications utilizing Module Federation. They ensure that remote containers remain accessible, even in instances where the remote server is offline. This capability is essential for maintaining application performance and user interaction, reducing load times, and smoothing transitions between routes. + +== Simplified Explanation: Angular Service Worker + +A service worker in Angular is a small JavaScript script that runs in the background of a web application. Its main job is to manage network requests, like those for loading web pages or data. + +Think of a service worker as a kind of network manager. It steps in when your app tries to send or receive data. It can decide how to handle these requests. One of its key roles is to help with caching. This means it can store some of the data on the user's device. Later, if the app needs that data again, the service worker can quickly retrieve it from the cache instead of fetching it from the internet again. This makes the app faster and more reliable, especially when the internet connection is slow or unavailable. + +Service workers are especially useful in Angular applications, which are often single-page applications (SPAs). Since Angular version 5.0, Angular apps have been able to easily use service workers. This helps these apps to load faster and work more smoothly, without needing complex code to handle network requests and caching. + +To learn more about Angular Service Workers and their inner workings, it's recommended to refer to the https://angular.io/guide/service-worker-intro[official Angular documentation] on the topic. + +== The Benefits of Using Service Workers with Module Federation + +=== Enhancing Performance and Reliability + +==== Caching Strategies for Speed and Efficiency + +- **Quick Access to Resources**: Service workers cache necessary resources, including JavaScript modules and other assets. This caching mechanism means that once a user has downloaded these resources, they are stored locally, leading to significantly faster load times on subsequent visits. + +- **Reduced Server Load**: By serving cached content, service workers decrease the amount of data fetched from the server, thus reducing server load and bandwidth usage. This is especially beneficial for applications that use Module Federation, as it can lower the overhead involved in loading modules from remote locations. + +===== Caching Strategies + +In Angular, the service worker can adopt one of two primary https://angular.io/guide/service-worker-config#installmode[caching strategies] for managing data resources. These strategies are designed to balance the trade-offs between data freshness and response speed. + +==== Offline Capabilities + +- **Resilience to Network Fluctuations**: Service workers enable Angular applications to function effectively even in unstable network conditions. This is particularly advantageous for Module Federation, ensuring that the app remains functional and provides a consistent user experience, regardless of network reliability. + +=== Streamlining Application Updates + +- **Background Updates**: Service workers facilitate the background updating of federated modules. When a module is updated on the server, the service worker can fetch and cache the new version without interrupting the user's current session. + +- **Version Management**: They help maintain version consistency across federated modules, ensuring that the application runs smoothly without any dependency conflicts. + + +=== Enhancing User Experience + +- **Seamless Navigation**: For applications using Module Federation, service workers can prefetch necessary modules, reducing the loading time when navigating between different parts of the application. This results in a smoother, more responsive user experience. + +- **Reduced Latency**: Since service workers serve cached content, they play a crucial role in reducing latency, especially in applications where modules are fetched from various remote servers. + +== Implementing Service Workers in Angular Applications + +This section provides a concise guide on setting up service workers in Angular. For comprehensive details, refer to https://angular.io/guide/service-worker-getting-started[the official Angular documentation.] + +To effectively integrate service workers into an Angular application, the initial step is to transform the application into a Progressive Web App (PWA). PWAs are known for their ability to enhance user experience by providing features similar to native apps. To understand the concept of PWAs, their unique benefits, impact on business, and development methodologies, it's recommended to consult the https://web.dev/explore/progressive-web-apps[Progressive Web Apps" page on web.dev] + +=== PWA-ing your Application + +This is achieved by executing the command `ng add @angular/pwa` in the root directory of your application. This command performs several essential actions: + +1. **Adding the Service Worker Package**: It installs the `@angular/service-worker` package into your project, equipping it with the necessary tools to handle service workers. + +2. **Service Worker Configuration**: The command generates a `ngsw-config.json` file. This file is central to configuring service worker behavior in your application, dictating how caching is handled, among other settings. + +3. **Updating the Index File**: It modifies the `index.html` file to include a reference to the `manifest.webmanifest` file, which is pivotal for PWA functionality. + +Having successfully set up your application to use service workers, the next step involves configuring caching strategies. This is crucial for optimizing the performance and efficiency of your application. + +=== Caching Strategies for Angular Service Workers + +Defining the caching strategy in an Angular application involves deciding which files or assets to cache, aiming to enhance the app's performance. Service workers facilitate caching, allowing the application to handle navigation requests (made when a new URL is entered in the navigation bar) and other API URL requests, even when offline. Thus, choosing the right caching strategy is crucial and depends on the specific setup of the Angular application. + +It's important to note that if your application includes lazy-loaded modules, these should be incorporated into your caching strategy. The following example in the `ngsw-config.json` file illustrates how caching strategies might be configured: + +[source, json] +---- +{ + "$schema": "./node_modules/@angular/service-worker/config/schema.json", + "index": "/index.html", + "assetGroups": [ + { + "name": "main-assets", + "installMode": "prefetch", + "updateMode": "lazy", + "resources": { + "files": [ + "/favicon.ico", + "/index.html", + "/*.css", + "/main.js", // main file + "/polyfills.js", // polyfills file + "/styles.css", // styles file + "/lazy-module-1.js", // Example of Lazy Chunk File + "/lazy-module-2.js" // Another Lazy Chunk File + ] + } + }, + { + "name": "additional-assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": [ + "/assets/**", + "/*.(png|jpg|jpeg|svg|gif|webp|woff2|woff|ttf|otf)" + ] + } + } + ] +} +---- + +=== Service Workers with Module Federation + +When working with Module Federation in Angular applications, setting up effective caching strategies for remote containers can be complex. A thorough understanding of the required files for the remote's operation is essential to devise an appropriate caching approach. + +==== Dynamic Loading and Dependency Management + +In scenarios where remote containers are dynamically loaded, Webpack handles the downloading of necessary dependencies. + +You can verify all downloaded dependencies by inspecting the Network tab in your browser's developer tools. This inspection allows you to see all the files fetched during the loading process, providing a clear view of what might need caching. Identifying all these dependencies is the first crucial step. When the remote container is dynamically loaded, Webpack fetches any required dependencies that are not already present. + +image:https://angular.io/generated/images/guide/service-worker/sw-active.png[] + +==== Adjusting Strategies for Remote Containers + +Directly caching individual files in a remote container may not be effective due to potential file name changes in new builds. A more efficient approach is to use a wildcard pattern to cache all `*.js` files from the remote's URL. This method is implemented in the `ngsw-config.json` file. + +[source, json] +---- +{ + "name": "RemoteAssets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "urls": [ + "https://your-remote-container-url/*.js" // Using a wildcard to cache all JS files + ] + } +} +---- + +Understanding Configuration Parameters + +- **Name**: Identifies an asset group, linked with manifestHash for cache location. +- **InstallMode**: Determines initial caching behavior (`prefetch` for immediate, `lazy` for on-demand). +- **UpdateMode**: Dictates caching during updates (`prefetch` for immediate update, `lazy` for delayed caching). +- **Resources**: Describes the cache scope, including `files` and/or `urls`. + +==== Updating Cached Federated Chunks + +===== Ensuring Data Freshness + +Angular Service Workers include features like the SwUpdate Service and Hard Refresh methods to keep data current. + +To gain a deeper understanding of the SwUpdate Service and Hard Refresh methods used in Angular Service Workers, it's recommended to consult the https://angular.io/guide/service-worker-communications[official Angular documentation]. This resource provides comprehensive details and guidance on these specific features. + +*Hard Refresh Implementation Example*: + +[source, javascript] +---- +function hardRefresh() { + navigator.serviceWorker.getRegistration().then(async (registration) => { + if (!registration) return; + await registration.unregister(); + window.location.reload(); + }); +} +---- + +This implementation ensures the application serves the most current content to users. + +When performing a Hard Refresh, the following actions are executed: + +1. Unregister the Service Worker. +2. Clear all files cached by the Service Worker. +3. Reload the webpage. + +=== Building and Running the Application + +After configuring your caching strategies, the next steps are to build and serve your application: + +[source, bash] +---- +ng build +http-server -p 8080 -c-1 dist/your-app-directory +---- + +== Workbox for Advanced Service Worker Management + +Workbox is a collection of JavaScript libraries for Progressive Web Apps. Its capabilities extend beyond what's typically offered by framework-specific solutions like Angular Service Worker (ngsw). Particularly in complex architectures such as Module Federation in Angular applications, understanding the benefits of Workbox can be pivotal for developers aiming to optimize performance and user experience beyond the standard set of tools. + +=== Key Features of Workbox: + +1. **Enhanced Flexibility and Customization with Webpack Integration**: Workbox distinguishes itself with its adaptability and customizable options. Notably, it seamlessly integrates with Webpack through the `workbox-webpack-plugin`, aligning perfectly with the requirements of projects utilizing Webpack, such as those in Module Federation setups. This integration enables developers to harness the full potential of Workbox's features directly within their Webpack configuration, adding a layer of efficiency and precision to service worker management. + +2. **Framework Agnostic**: Unlike solutions tailored to specific frameworks, Workbox can be employed across various JavaScript frameworks and libraries. This versatility makes it an ideal choice for projects that span multiple frameworks or for developers seeking a more universally applicable tool. + +3. **Granular Control Over Caching**: Workbox provides developers with granular control over caching strategies. It allows for the writing of custom service worker scripts, offering nuanced management of resource caching and network strategies. + +In the context of Module Federation, where different parts of an application may have varied caching needs and networking strategies, Workbox's flexibility and extensive feature set make it a standout choice. Its capability to handle complex scenarios and provide custom solutions aligns well with the demands of modern, sophisticated web applications. + +With this understanding of Workbox's advantages, integrating it into an Angular application using Module Federation will be explored in the following section. + +=== Installation and Configuration + +==== Step 1: Installing Workbox + +Start by adding Workbox to your Angular project: + +[source, bash] +---- +npm install workbox-webpack-plugin --save-dev +---- + +==== Step 2: Configuring Workbox in Webpack + +Given that Module Federation heavily relies on Webpack, configure Workbox as a plugin in your Webpack configuration. This step is crucial for ensuring that the service worker strategies align with the distributed nature of Module Federation. + +*Example Webpack Configuration:* + +[source, javascript] +---- +const { GenerateSW } = require('workbox-webpack-plugin'); + +module.exports = { + // ... other webpack config relevant to Module Federation + plugins: [ + new GenerateSW({ + // Configurations specific to your Module Federation setup + // these options encourage the ServiceWorkers to get in there fast + // and not allow any straggling "old" SWs to hang around + clientsClaim: true, + skipWaiting: true, + }) + ], +}; +---- + +==== Step 3: Tailoring Caching Strategies and Exposing the Workbox Service Worker + +In a Module Federation setup, the correct exposure of shared resources like the Workbox service worker is crucial. Here's how to achieve this: + +1. *Define Workbox as a Shared Module:* In your Module Federation plugin configuration within Webpack, declare the Workbox service worker as a shared module. This step ensures that the service worker is accessible across all federated modules. ++ +*Example Module Federation Config in webpack.config.js:* ++ +[source, javascript] +---- +const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); + +module.exports = { + plugins: [ + new ModuleFederationPlugin({ + // Other Module Federation settings + shared: { + // Share Workbox configuration as a module + 'workbox-webpack-plugin': { + singleton: true, + requiredVersion: 'your-workbox-version' + } + } + }) + ], +}; +---- ++ +2. *Dynamically Import Workbox Service Worker:* Utilize dynamic import capabilities to load the Workbox service worker within your Angular application. This can typically be done in the main entry file of your application. ++ +*Example of Dynamically Loading in main.ts:* ++ +[source, typescript] +---- +import { Workbox } from 'workbox-window'; + +if ('serviceWorker' in navigator) { + const wb = new Workbox('/service-worker.js'); + + wb.register(); +} +---- ++ +In this example, `workbox-window` is used for simplifying the service worker registration process in a client-side application. Ensure that this package is installed and included in your project dependencies. For more information on registering Service Worker in Webpack we suggest reading https://webpack.js.org/guides/progressive-web-application/#registering-our-service-worker[official documentation on the subject] + +==== Step 4: Customize Workbox Strategies + +Workbox offers a variety of strategies for caching and network requests. Configure these strategies in a service worker file to cater to your application's specific needs. + +*Example `service-worker.js`:* + +[source, javascript] +---- +import { precacheAndRoute } from 'workbox-precaching'; +import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'; + +// Precaching for fast load of initial resources +precacheAndRoute(self.__WB_MANIFEST); + +// Example runtime caching strategies +self.addEventListener('fetch', event => { + if (event.request.url.includes('/api/')) { + // Network-first strategy for API requests + event.respondWith(new NetworkFirst().handle({ event })); + } else { + // Stale-while-revalidate for other resources + event.respondWith(new StaleWhileRevalidate().handle({ event })); + } +}); +---- + +In this setup, Workbox provides a network-first strategy for API calls and a stale-while-revalidate strategy for other resources, ensuring efficient data fetching and caching. + +== Conclusion + +In summary, we explored two distinct approaches to configuring service workers in Angular applications: using Angular's built-in Service Worker (ngsw) and leveraging Workbox. + +1. **Angular Service Worker (ngsw)**: This approach offers a straightforward, Angular-centric method for integrating service workers. It's ideal for developers seeking a quick setup with minimal configuration, providing essential functionalities aligned with Angular's ecosystem. + +2. **Workbox**: Workbox presents a more flexible solution, allowing for customized caching strategies and integration across various frameworks. Its compatibility with Webpack makes it particularly suitable for complex architectures like Module Federation in Angular applications. + +Both methods have their unique strengths. Angular's Service Worker is well-suited for standard use cases in Angular applications, while Workbox offers greater control and customization, especially in multifaceted environments. The choice depends on the specific requirements of your project and the desired level of control over the service worker's behavior. + diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part1.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part1.adoc new file mode 100644 index 00000000000..a69e5f65a77 --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part1.adoc @@ -0,0 +1,322 @@ += Splitting an Angular App into Micro-Frontend Applications: A Step-by-Step Guide + +== Introduction to Micro-Frontends + +In the dynamic landscape of modern application development, teams often find themselves managing large applications that grow in complexity and size. This can lead to the formation of smaller, specialized teams each focusing on different sections of the application. However, a challenge arises when each team needs to download, run, build, and test the entire application. To address this, the micro-frontend architecture offers an efficient and scalable solution. + +=== What is a Micro-Frontend? + +Micro-frontends extend the principles of microservices to front-end development. In traditional microservices architecture, a large monolithic application is divided into smaller, independent services. Each service can be developed, tested, built, and delivered individually. Similarly, micro-frontends divide a large front-end application into smaller, manageable pieces. These pieces, or "micro-apps," can be developed, tested, and built independently, and then integrated into a larger application, often using routing. + +For a more comprehensive understanding of how micro-frontends work, particularly in the context of module federation, we recommend reading our https://test.com[separate article on module federation]. This additional resource provides in-depth insights and foundational knowledge that can enhance your grasp of micro-frontend concepts and their practical applications in modern web development. + +==== User and Developer Perspectives + +From a user's perspective, the experience is seamless—they interact with one unified application. For developers, micro-frontends offer flexibility in how applications are served. Options range from serving them individually to bundling them into a single application using advanced techniques like module federation. + +In our example, we will construct an application that imports and integrates these individual micro-apps, allowing each to be served, tested, and deployed independently. + +== Current State of Micro-Frontend Technology + +The micro-frontend approach permits the use of various frameworks and libraries in developing different sections of an application. This flexibility comes with its pros and cons. For instance, using different technologies across teams can make it challenging to shift developers between teams. However, tools like Single SPA facilitate the integration of diverse technologies into a cohesive application. + +Alternatively, one can opt for a single frontend framework like React, Vue, or Angular. Each framework offers unique ways to implement micro-frontends. For Angular, a tool like Nx provides features like dependency graphs and linked packages, along with strong support for Module Federation and micro-frontend development. + +Ultimately, the goal is to build, test, and deploy each micro-app independently while providing users with a unified application experience. + +== Tackling the Transition to Micro-Frontends + +=== The Challenge of Large Applications + +Large applications can suffer from lengthy test and build times, and slow local serving. Transitioning to micro-frontends by splitting the application into smaller parts can significantly alleviate these issues. + +=== Approaches to Implementing Micro-Frontends + +There are numerous methods for implementing micro-frontends, with various guides and resources available. Some focus on building micro-frontends from scratch, which is ideal for new projects expected to scale significantly. + +==== Refactoring an Existing Application + +This guide, however, concentrates on refactoring an existing application, potentially built using the Angular CLI, into shared libraries and individual apps. These components are then seamlessly integrated within the larger application, maintaining the unified user experience while enhancing development efficiency. + +== Reviewing the Base Application for Micro-Frontend Implementation + +=== Experiment Setup with a Sample Application + +For our exploration into micro-frontends, we will utilize a sample application. This application, while not large or complex, incorporates several services that demonstrate how they can be integrated with each micro-app in a micro-frontend architecture. + +==== Application Structure + +The application's structure mirrors what one would typically get from creating a new project using the Angular CLI. Key elements include: + +- **Feature Modules**: These are specific functionalities or pages within the app, such as 'add-user', 'dashboard', 'login', and 'user-list'. +- **Models**: Data structures that define the shape of data within the app. +- **Shared Services**: Common functionalities used across different parts of the application, such as authentication (`auth`) and user management (`users`). + +The directory structure is as follows: + +[source, bash] +---- +- src + + features + - add-user + - dashboard + - login + - user-list + - models + + shared + - auth + - users +---- + +=== Refactoring Strategy + +The goal is to refactor the services and models into libraries that can be shared across micro-apps. The feature modules, on the other hand, will be transformed into their individual apps. This approach ensures modularity and facilitates independent development and deployment of each feature. + +==== Routing Considerations + +The current routing setup in the application utilizes lazy loading, enhancing performance and user experience. Here's an extract showcasing the routing strategy: + +[source, javascript] +---- +// app-routing.module.ts +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { isLogged } from './shared/auth/is-logged.guard'; +import { isNotLogged } from './shared/auth/is-not-logged.guard'; + +const routes: Routes = [ + { + path: '', + canMatch: [isLogged], + loadChildren: () => + import('./features/dashboard/dashboard.module').then((m) => m.DashboardModule), + }, + { + path: '', + canMatch: [isNotLogged], + loadChildren: () => import('./features/login/login.module').then((m) => m.LoginModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], +}) +export class AppRoutingModule {} +---- + +This routing module demonstrates the use of guards (`isLogged` and `isNotLogged`) to manage access to different routes based on user authentication status, and dynamic imports for lazy loading of feature modules. + +== Starting with Simple Steps in Micro-Frontend Migration + +=== Approach to Migrating a Complex Application + +Migrating a complex application to a micro-frontend architecture can be challenging. To facilitate this process, clear steps and possibly additional tooling (for better dependency management) are essential. + +==== Initial Focus: Dependency-Free Files + +1. **Identify Independent Files**: Start by pinpointing files that do not have dependencies but are used across services and feature modules. For example, in our sample application, this applies to the `models` folder, which contains models used throughout the application but doesn't depend on any other files. + +2. **Create a New Library for these Files**: Use Angular CLI to generate a library that will house these independent files. The command for this is: + +[source, bash] +---- +ng generate library models +---- + +This command results in the following actions: + - Creation of a new `projects` folder containing the `models` library. + - Installation of `ng-packagr` as a dependency. + - Updates to `angular.json` and `tsconfig.json` to include the new library. + +==== Customizing the Library + +- **Path Mapping**: Modify the path mapping in `tsconfig.json` to avoid potential conflicts with npm modules. A suggested prefix is `@@`, as npm module names cannot include double at signs. +- **Package Naming**: If you intend to publish the library to a registry, ensure the name in `package.json` is registry-safe. Otherwise, you can choose a convenient name. + +==== Refactoring the Models + +1. **Move the Models**: Transfer the contents of the `models` folder into the new library. The generated library will contain default components, modules, and services, which can be removed. Place the user model file inside the `lib` folder of the library. +2. **Update Public API**: Modify the `public-api.ts` file to export the appropriate content from the `lib` folder. + +==== Managing File Movement + +- **Using Git for File Transfers**: Employ `git mv` instead of operating system features for moving files. This ensures better tracking of changes by Git. + +==== Library Folder Structure + +The folder structure of the `models` library should be organized for clarity and easy access. + +#### Updating Imports + +1. **Change Import Paths**: Replace existing import paths with the new library path. For instance, change: + +[source, javascript] +---- +import { User } from 'src/app/models/user'; +---- + +to: + +[source, javascript] +---- +import { User } from '@@models'; +---- +Ensure that the path matches what is specified in `tsconfig.json`. + +#### Building the Library + +- **Resolving Errors**: Initially, you may encounter errors due to the library not being built. Resolve this by running: + +[source, bash] +---- +ng build models +---- + +This command compiles the `models` library, allowing TypeScript to correctly reference `@@models`. + +== Progressing Further in Micro-Frontend Migration + +=== Advancing Beyond Basic Migration + +After successfully migrating independent code into libraries, the next step involves tackling dependent code that can also be isolated. Continuing with our example, we'll focus on migrating the `auth` service, including its guards, into a separate library. This process mirrors the steps taken for the `models` library. + +==== Creating the Auth Library + +1. **Generate the Library**: Use Angular CLI to create the `auth` library with the command: ++ +[source, bash] +---- +ng generate library auth +---- ++ +2. **Refactor the Library Content**: Remove the default content from the `lib` folder and transfer all contents of the `auth` folder into it. Update the `public-api.ts` file to reflect these changes. ++ +The `public-api.ts` should look something like this: ++ +[source, javascript] +---- +/* + * Public API Surface of auth + */ +export * from './lib/auth.service'; +export * from './lib/is-logged.guard'; +export * from './lib/is-not-logged.guard'; +---- ++ +3. **Build the Library**: Compile the `auth` library using Angular CLI: ++ +[source, bash] +---- +ng build auth +---- ++ +4. **Update Imports**: Change the application's import statements to use the new `auth` library. + +=== Celebrating Milestones + +Congratulations are in order! Parts of your application have been successfully migrated into libraries. This not only keeps the application running but potentially speeds it up, as Angular doesn't need to rebuild the entire application. You now have several libraries with isolated testing and building, suitable for larger applications where components, services, pipes, and other elements are shared. + +==== Advanced Library Organization + +For more complex applications, consider using ng-packagr's Secondary Entrypoints feature to group common items like components or services. This requires additional configuration adjustments. + +=== Migrating the First Application + +Having learned to package libraries, we now turn to migrating a feature module into its own application. + +==== Choosing a Module for Migration + +In our example, the `Login` feature module is selected for migration. Ensure that all shared artifacts used by this module are already in their respective libraries. Other services, like the `users` service, can be migrated later. + +==== Creating the Login Application + +1. **Generate the Application**: Use Angular CLI to create the `login` application: ++ +[source, bash] +---- +ng generate application login --style=scss --routing +---- ++ +The `--routing` flag is essential for navigation within the app. ++ +2. **Serve the Application**: Test the newly created application with: ++ +[source, bash] +---- +ng serve login +---- ++ +If the main application is running, you may need to use a different port. + +==== Refactoring the Feature Module + +1. **Transfer the Feature Module**: Instead of replacing files as done with libraries, create a `feature` folder in the `src` of the new application and move the `login` folder into it. + +2. **Update Routing**: Modify the `app-routing.module.ts` of the `login` application to serve the `Login` module at the root route. + +3. **Adjust the AppComponent**: Ensure the `app.component.html` of the `login` app contains a `` tag for routing to function correctly. + +=== Addressing Styling + +Initially, the new application might appear unstyled. Copy the `styles.scss` file from the main application to the new one to resolve this. Future posts will delve into sharing styles across applications. + +=== Outcome + +If done correctly, the `login` application should now be running independently, styled, and functional, marking a significant step in your journey towards a fully implemented micro-frontend architecture. + +== Integrating the Login App into the Main Application + +After setting up the `Login` application as a standalone entity, the next critical step is integrating it with the main application. This involves building the `Login` app as a library and importing it using the existing lazy load feature in the main module. + +=== Updating `angular.json` + +1. **Modify Project Configuration**: In `angular.json`, duplicate a library configuration, placing it under the `login` configuration for better organization. Rename the key to `login-lib` and update references (such as `root`, `sourceRoot`) to point to the `login` application. ++ +The updated configuration should be adjusted to reflect the specific paths and settings of the `login` application. ++ +2. **Creating `ng-package.json`**: This file is essential for instructing `ng-packagr` on how to package the application. Copy an existing `ng-package.json` file from another library into the root folder of the `Login` application. Update the `dest` key to `"../../dist/login-lib"` to specify the build output directory. + +=== Establishing the Public API + +1. **Create `public-api.ts`**: This file, different from typical library setups, should be created under the `src` folder. It should export only the `Login` feature module: +[source, javascript] +---- +// public-api.ts +export { LoginModule } from './app/feature/login/login.module'; +---- + +=== Building the Library + +1. **Prepare `package.json`**: Copy a `package.json` from another library to the root of the `Login` application, ensuring to update the project name. +2. **Build Command**: Execute the Angular CLI build command: +[source, bash] +---- +ng build login-lib +---- + +This process compiles the `Login` module as a library. + +=== Importing the Library + +1. **Update `tsconfig.json`**: Add a new path mapping for the `login-lib` in `tsconfig.json`, pointing to `"dist/login-lib"`. This step is crucial for TypeScript to locate the library correctly. + +2. **Modify Application Routing**: In `app-routing.module.ts`, update the lazy loading path to use the new library: + +[source, javascript] +---- +loadChildren: () => import('@@login').then((m) => m.LoginModule), +---- + +=== Running the Integrated Application + +With these changes, you can serve the application as usual. The `Login` module, now a separate project, should function seamlessly within the main app. + +=== Further Opportunities and Considerations + +- **Automating Builds**: To streamline the process, consider using tools like Lerna for automated build and dependency management. This can help especially when dealing with multiple libraries and applications. +- **Version Management**: Currently, the setup does not utilize versioning for libraries, which could complicate the introduction of breaking changes. Implementing a versioning strategy could be beneficial for long-term maintenance. + +=== Conclusion and Next Steps + +Thank you for following along! This guide aims to provide a clear pathway for migrating to a micro-frontend architecture. While further posts on topics like sharing styles or configuring ng-packagr secondary endpoints might be beneficial, feedback on specific areas of interest would be greatly appreciated. Your engagement and suggestions are crucial in shaping future content. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part2.adoc b/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part2.adoc new file mode 100644 index 00000000000..6d86c13dfbd --- /dev/null +++ b/apps/docs/src/en/modules/ROOT/pages/angular-way/splitting-to-mf-part2.adoc @@ -0,0 +1,154 @@ += Extending Angular Apps with Module Federation: A Step-by-Step Guide + +Welcome to the second part of our series on enhancing Angular applications using Module Federation (MF). In this guide, we'll delve into how to seamlessly incorporate MF into an existing plain Angular application. + +== Understanding Our Tools: @angular-architects/module-federation + +It's important to note that, by default, Angular CLI does not facilitate the loading of custom Webpack configurations. To address this, our approach involves updating the Angular CLI builder with a specialized custom builder. This modification enables the extension of the Webpack config file, crucial for integrating advanced features like Module Federation. + +While this guide focuses on utilizing the custom builder provided by the `@angular-architects/module-federation` library, you have the flexibility to replace it with an alternative builder, should your expertise permit. For those interested in exploring how to implement custom Webpack configurations within Angular CLI builders, we suggest reading the following article https://www.digitalocean.com/community/tutorials/angular-custom-webpack-config["How To Use Custom webpack Configurations with Angular CLI Builders"]. This resource offers valuable insights and practical steps for customizing your Angular build process beyond the standard configurations. + +=== Library Functions + +- **Updates Build Configuration**: It enables the extension of the Webpack configuration file. +- **Application Serving**: Facilitates serving applications individually, either through standard browser interaction or as a micro-frontend. + +== Setting Up Module Federation in the Main App + +1. **Initialization with Schematics**: ++ +Run the following command in your project's root directory: ++ +[source, bash] +---- +ng add @angular-architects/module-federation --project app-micro --type dynamic-host --port 4200 +---- ++ +This command configures your main project for Module Federation, updates Angular CLI configurations, creates a manifest file for loading remote entries, and reorganizes the bootstrap process. ++ +2. **Handling Schematic Conflicts**: ++ +Some Angular schematics like `@angular/pwa` and `@angular/material` expect bootstrapping in `main.ts`. If you plan to use these, toggle the bootstrapping process temporarily: ++ +[source, bash] +---- +ng g @angular-architects/module-federation:boot-async false --project [YOUR_MAIN_PROJECT] +ng add [YOUR_LIBRARIES_OF_CHOICE] --project yourProject +ng g @angular-architects/module-federation:boot-async true --project [YOUR_MAIN_PROJECT] +---- ++ +3. **Updating the Manifest File**: ++ +After implementing the schematic changes, it's essential to modify the manifest file (`mf.manifest.json`) and the application routing configuration (`app-routing.module.ts`) to ensure proper functioning of your Angular application. ++ +The default configuration in `mf.manifest.json` is set to serve a file on port `4200`. However, this port is typically used for the main application. ++ +To avoid conflicts, update the port for serving the secondary application. For example, change the port to 4201. Your `mf.manifest.json` should reflect this change as follows: ++ +[source, json] +---- +{ +"login": "http://localhost:4201/remoteEntry.js" +} +---- ++ +By updating these configurations, you establish a clear and functional structure for running your main and secondary Angular applications on distinct ports, facilitating their independent operation and integration. ++ +- *Simplified Example:* In this guide, the manifest configuration is statically defined in a JSON file for simplicity. ++ +- *Dynamic Configuration in Production:* For real-life applications, it's recommended to serve the manifest configuration dynamically. This can be achieved by either: ++ +Replacing the static JSON file with a production version. +Setting up an endpoint on your server that returns the manifest configuration. ++ +NOTE: When opting for dynamic serving, update the main.ts file to modify the URL used by the initFederation function to retrieve the manifest data. Be aware that in this configuration, if there's no internet connection, the application will fail to load. However, with a static configuration, you can handle connection errors, particularly if your app has been previously cached. ++ +4. **Integrating the Manifest in Routing**: + +*Modify the Manifest File:* + +- Adjust the port in the mf.manifest.json file if necessary, as described in previous sections. + +*Update the Application Routing (`app-routing.module.ts)`:* + +- Remove the existing import code (e.g., `import('@@login')`). +- Replace it with a call to the `loadRemoteModule` function (imported from the `@angular-architects/module-federation module`). +- Configure the `loadRemoteModule` function with the following object: ++ +[source, json] +--- +{ + "type": "manifest", + "remoteName": "login", + "exposedModule": "./Module", +} +--- + +- `type`: Denotes the remote configuration type (`'manifest'` in this case). +- `remoteName`: The module's name as declared in the manifest. +- `exposedModule`: Path of the module in the secondary app. + +The final result should look similar to the following example: + +[source, typescript] +---- +const routes: Routes = [ + ... + { + path: '', + canMatch: [isNotLogged], + loadChildren: () => + loadRemoteModule({ type: 'manifest', remoteName: 'login', exposedModule: './Module' }).then( + (m) => m.LoginModule, + ), + }, + ... +]; +---- + +Having followed these steps, the main application is now configured to load other applications remotely. The next phase involves setting up the secondary applications to ensure they can be loaded in this manner. + +== Configuring Secondary Apps for Remote Loading + +Upon configuring the secondary applications, similar to the main app, several project components will be updated. Notably, this process does not create a manifest file, but it does modify the `webpack.config.js` file, adding more details than what is typically found in the main application's configuration. + +[source, bash] +---- +ng add @angular-architects/module-federation --project login --type remote --port 4201 +---- + +NOTE: Instead of using `--type dynamic-host`, we are going to use `--type remote` instead. + +=== Understanding the 'exposes' Property + +- *Role of 'exposes':* The exposes property within the `webpack.config.js` file plays a crucial role. It contains an object configuration where each key-value pair resembles a route. This property dictates the modules to be exposed to the loader. +- *Linking to 'exposedModule':* This relates to our earlier step where we used the exposedModule property in the route configuration, signifying the specific module route to load. + +=== Necessary Adjustments in Webpack Configuration + +*Identifying Configuration Mismatch:* In the current setup, the route configuration attempts to load a module named './Module', but this isn't specified in the existing Webpack configuration. + +To rectify this, modify the `webpack.config.js` file: + +- Remove the existing './Component' key. +- Add a new './Module' key, ensuring it points to the correct path for loading the application module. For example: ++ +[source, javascript] +---- +exposes: { + './Module': './projects/login/src/app/feature/login/login.module.ts', +}, +---- + +With this update, the secondary applications are correctly configured to expose the necessary modules, aligning with the remote loading requirements set in the main application. + +== Testing and Running the Applications + +- **Run the Main App**: `ng serve` +- **Serve the Secondary App**: `ng serve login` +- After serving both apps, refresh the main application to see the integration in action. The secondary app should now load correctly within the main app. +- Moreover, by navigating to `localhost:4201` in your browser, you can observe the login application operating as a standalone entity. + +== Next Steps: Optimizing Build Configuration + +While we currently use `shareAll` from the `@angular-architects/module-federation/webpack` package, a more refined approach involves sharing only necessary dependencies using the `share` function. This selective sharing optimizes the application's performance and resource utilization. \ No newline at end of file diff --git a/apps/docs/src/en/modules/ROOT/pages/composable-commerce-simple.adoc b/apps/docs/src/en/modules/ROOT/pages/composable-commerce-simple.adoc deleted file mode 100644 index 7af77f83f64..00000000000 --- a/apps/docs/src/en/modules/ROOT/pages/composable-commerce-simple.adoc +++ /dev/null @@ -1,360 +0,0 @@ -= Composable Commerce with Module Federation - -Composable Commerce is a new approach to building e-commerce applications that allows you to customize and extend every aspect of your online store. With Composable Commerce, you can leverage best-of-breed solutions from different vendors and combine them into a unified shopping experience for your customers. - -In this guide, you will learn how to use Composable Commerce and Module Federation to create a modular and scalable e-commerce application. You will learn see how to: - -- Figure out Composable Commerce concept; -- Benefits of Composable Commerce to e-commerce; -- Define the architecture and components of your application; -- Configure Webpack to enable Module Federation; -- Expose and consume modules from different bundles; -- Implement common e-commerce features such as product catalog, cart, checkout, and payment using Module Federation; - -== What is Composable Commerce? - -Composable commerce is an approach to e-commerce development that follows the principles of microservices, domain-driven design and API-first design. It breaks down the monolithic e-commerce application into smaller, loosely coupled components that can be composed together to create a customized and adaptable user experience. Each component is responsible for a specific business domain or functionality, such as product catalog, shopping cart, checkout, payment, etc. These components communicate with each other through well-defined APIs and events. - -Composable commerce enables you to: - -- Choose the best components for your business needs from different vendors or develop your own. -- Mix and match components from different sources and technologies without worrying about compatibility or integration issues. -- Replace or upgrade components without affecting the rest of the application or requiring downtime. -- Experiment with new features or functionalities without risking the stability of the application. -- Scale each component independently according to the demand and traffic. - -For example, suppose you have an e-commerce application that sells books online. You can use composable commerce to create your application from different components such as: - -- A product catalog component that provides the data and UI for browsing and searching books by title, author, genre, etc. -- A shopping cart component that provides the data and UI for adding, removing and updating items in the cart. -- A checkout component that provides the data and UI for entering shipping and billing information, applying discounts and coupons, etc. -- A payment component that provides the data and UI for choosing and processing payment methods such as credit card, PayPal, etc. -- A review component that provides the data and UI for rating and reviewing books after purchase. - -Each component can be developed by a different team using a different technology stack. For example, the product catalog component can be developed using React and GraphQL, the shopping cart component can be developed using Angular and RESTful APIs, the checkout component can be developed using Vue and Firebase, etc. Each component can also be deployed and updated separately on different servers or domains. - -To compose your application from these components, you need to define how they interact with each other through APIs and events. For example, you can define an API for the product catalog component that exposes endpoints for querying books by various criteria. You can also define an event for the shopping cart component that emits a message when an item is added to or removed from the cart. You can then use these APIs and events to connect your components together and create a seamless user experience. - -== What are the benefits of Module Federation + Composable Commerce? - -By using Module Federation + Composable Commerce, you can achieve the following benefits: - -- *Faster time to market*: You can reuse existing code and components from different sources without having to rebuild or redeploy them. You can also update or replace any component at any time without affecting the rest of the system. -- *Greater flexibility and scalability*: You can choose the best-of-breed vendors that provide the functionality you need for each business capability, such as product catalog, checkout, payment, etc. You can also scale each component independently according to your traffic and performance needs. -- *Better customer experience*: You can deliver consistent and engaging product experiences across multiple channels and touchpoints, such as web, mobile, social media, etc. You can also customize and optimize each component according to your customer preferences and behavior. - -For example, suppose you want to add a new feature to your e-commerce application that allows customers to create personalized book recommendations based on their preferences. You can use composable commerce to achieve this by: - -- Reusing an existing component that provides a survey service to collect customer preferences. -- Developing a new component that provides a recommendation engine to generate book suggestions based on customer preferences. -- Integrating these components with your existing product catalog component using APIs and events. -- Deploying these components separately without affecting the rest of the application or requiring downtime. - -This way, you can deliver this new feature faster, cheaper and more reliably than if you had to modify your monolithic application. - -== How does Module Federation + Composable Commerce work? - -Module Federation + Composable Commerce works by breaking down your ecommerce solution into modular building blocks called Packaged Business Capabilities (PBCs). Each PBC represents a specific business function, such as product search, cart, reviews, etc. Each PBC can be deployed and scaled independently, and can communicate with other PBCs via APIs. You can choose the PBCs that suit your business requirements from different vendors or sources, or create your own PBCs if needed. You can then compose them together into a custom ecommerce application that meets your specific needs. - -== Migration to Composable Commerce using Module Federation - -Module federation is a feature of Webpack 5 that allows you to dynamically load modules from remote applications at runtime. It also enables you to share modules across different applications without duplicating code or creating dependencies. Module federation can help you migrate your existing e-commerce application to a composable commerce architecture by allowing you to: - -- Split your monolithic application into smaller modules that can be loaded on demand by different applications. -- Create a shell application that acts as a container for loading and rendering different modules based on the user's context and actions. -- Share common modules such as authentication, navigation, UI components, etc.across different applications to ensure consistency and avoid duplication. -- Leverage existing modules from third-party vendors or open source projects to enhance your application's functionality and user experience. - -To use module federation, you need to: - -- Configure Webpack 5 in each module and shell application to expose and consume remote modules using the `ModuleFederationPlugin`. -- Define the entry points, dependencies and fallbacks for each remote module using the `exposes`, `remotes` and `shared` options in the plugin configuration. -- Use dynamic imports or custom hooks to load and use remote modules in your shell or module applications. - -In this example, you have four bundles: - -- **Shell**: This is the main bundle that provides the layout and navigation of your application. It also acts as the host for the other bundles -and consumes their modules. -- **Catalog**: This bundle exposes a product list component that displays the products from a product catalog service. -- **Cart**: This bundle exposes a cart component that displays the items in the shopping cart and allows the user to update or remove them. It also communicates with a cart service to persist the cart data. -- **Checkout**: This bundle exposes a checkout component that guides the user through the checkout process. It also communicates with a checkout service to handle the payment and order confirmation. - -You can follow these steps to migrate your e-commerce application using these bundles: - -1. Create four separate module applications for each bundle: `shell-app`, `catalog-app`, `cart-app` and `checkout-app`. Each module application should contain the logic, data and UI components related to its bundle. For example, the `catalog-app` should contain the product catalog service, the product list component, etc. - -[source, javascript] ----- -// src/ProductList.js -import React from "react"; -import { useQuery } from "@apollo/client"; -import { GET_ALL_BOOKS } from "./queries"; - -function ProductList() { - const { loading, error, data } = useQuery(GET_ALL_BOOKS); - - if (loading) return
    Loading...
    ; - if (error) return
    Error: {error.message}
    ; - - const { books } = data; - - return ( -
    -

    Books

    - -
    - ); -} - -export default ProductList; ----- - -2. Create a shell application that will act as a container for loading and rendering different module applications based on the user's context and actions. The shell application should contain the common modules such as authentication, navigation, UI components, etc.that are shared across all bundles. The shell application should also contain a router that will determine which module application to load based on the URL path. - -[source, javascript] ----- -// src/Shell.js - -import React from "react"; -import { BrowserRouter as Router } from "react-router-dom"; -import Header from "./Header"; -import Footer from "./Footer"; - -function Shell() { - return ( - -
    -
    - {/* render module applications based on route */} -
    -
    -
    - ); -} - -export default Shell; ----- - -The previous chapter was: - -3. Configure Webpack 5 in each module and shell application using the `ModuleFederationPlugin`. For example, in the `catalog-app`, you should expose the product list component as a remote module using the `exposes` option: - -[source, javascript] ----- -// webpack.config.js -const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); - -module.exports = { - // ... - plugins: [ - new ModuleFederationPlugin({ - name: "catalog", - filename: "remoteEntry.js", - exposes: { - "./ProductList": "./src/ProductList", - }, - shared: { - // specify shared dependencies - react: { singleton: true }, - "react-router-dom": { singleton: true }, - "@apollo/client": { singleton: true }, - }, - }), - ], -}; ----- - -In the shell application, you should consume the product list component as a remote module using the `remotes` option: - -[source, javascript] ----- -// webpack.config.js -const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); - -module.exports = { - // ... - plugins: [ - new ModuleFederationPlugin({ - name: "shell", - filename: "remoteEntry.js", - remotes: { - catalog: "catalog@http://localhost:3001/remoteEntry.js", - }, - shared: { - // specify shared dependencies - react: { singleton: true }, - "react-router-dom": { singleton: true }, - }, - }), - ], -}; ----- - -4. Use dynamic imports or custom hooks to load and use remote modules in your shell or module applications. For example, in the shell application, you can use React.lazy() to load and render the product list component from the `catalog-app` when the user navigates to `/products`: - -[source, javascript] ----- -// src/Shell.js -import React from "react"; -import { BrowserRouter as Router, Route } from "react-router-dom"; -import Header from "./Header"; -import Footer from "./Footer"; - -const ProductList = React.lazy(() => - import("catalog/ProductList") -); - -function Shell() { - return ( - -
    -
    - {/* render module applications based on route */} - - Loading...
    }> - - - - {/* render other routes */} -