|
1 |
| -# nx-angular-mf |
| 1 | +# Custom Angular Builder for Microfrontend Architecture |
2 | 2 |
|
3 |
| -This library was generated with [Nx](https://nx.dev). |
| 3 | +This repository contains a custom builder for Angular projects designed to simplify the development and production of microfrontend applications using native esm module. |
| 4 | +The builder integrates with [NX](https://nx.dev/) to provide seamless support for development, testing, and deployment of Angular microfrontends. |
4 | 5 |
|
5 |
| -## Building |
| 6 | +## Features |
6 | 7 |
|
7 |
| -Run `nx build nx-angular-mf` to build the library. |
| 8 | +- **Microfrontend Support**: Built-in support of native esm module with dynamic imports them. |
| 9 | +- **Server-Side Rendering (SSR)**: Improved SSR compatibility, including custom module loaders and import maps. |
| 10 | +- **Customizable Build Process**: Flexible options for managing dependencies and extending build configurations. |
| 11 | +- **Dev Server Enhancements**: |
| 12 | + - Incremental hydration support. |
| 13 | + - Dynamic `importmap` generation. |
| 14 | + - Automatic dependency resolution and rebuilds. |
| 15 | +- **Seamless NX Integration**: Fully compatible with NX workspace for streamlined project management. |
8 | 16 |
|
9 |
| -## Running unit tests |
| 17 | +### Prerequisites |
10 | 18 |
|
11 |
| -Run `nx test nx-angular-mf` to execute the unit tests via [Jest](https://jestjs.io). |
| 19 | +- Node.js (v19 or higher) |
| 20 | +- Angular CLI (v19 or higher) |
| 21 | +- NX CLI (latest version) |
| 22 | + |
| 23 | +### Installation |
| 24 | + |
| 25 | +Clone this repository and install the dependencies: |
| 26 | + |
| 27 | +```bash |
| 28 | +npm install @klerick/nx-angular-mf |
| 29 | + ``` |
| 30 | +### Usage |
| 31 | + |
| 32 | +Update your project.json or angular.json file to use the custom builder: |
| 33 | + |
| 34 | +```json |
| 35 | +{ |
| 36 | + "name": "mf1-application", |
| 37 | + ... |
| 38 | + "targets": { |
| 39 | + "build": { |
| 40 | + "executor": "@klerick/nx-angular-mf:build", |
| 41 | + "options": { |
| 42 | + ..., |
| 43 | + "mf": {} |
| 44 | + } |
| 45 | + }, |
| 46 | + "serve": { |
| 47 | + "executor": "@klerick/nx-angular-mf:serve", |
| 48 | + "options": { |
| 49 | + ..., |
| 50 | + "mf": {} |
| 51 | + } |
| 52 | + } |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | +### Configuration Options `mf: {}` |
| 57 | +This section explains the configuration options for the custom builder. |
| 58 | +The type is structured as follows: |
| 59 | + |
| 60 | +```typescript |
| 61 | +type mf = { |
| 62 | + skipList?: string | string[]; |
| 63 | + externalList?: string | string[]; |
| 64 | + esPlugins?: string[]; |
| 65 | + indexHtmlTransformer?: string; |
| 66 | + exposes?: Record<string, string>; |
| 67 | + remoteEntry?: Record<string, string>; |
| 68 | + deployUrlEnvName?: string; |
| 69 | +} |
| 70 | +``` |
| 71 | +- ```skipList``` - A list of dependencies of path to json file to be excluded from processing during the build process. |
| 72 | +- ```externalList``` - A list of dependencies of path to json file hat should be treated as external and not bundled with the application. These are loaded dynamically at runtime. |
| 73 | +- ```esPlugins``` - An array of path to custom plugins to extend or modify the esbuild bundler behavior during the build process. |
| 74 | +- ```indexHtmlTransformer``` - Path to a custom transformer script for modifying index.html during the build process. This allows advanced customizations like injecting additional tags or modifying existing ones. |
| 75 | +- ```exposes``` - A map of module names to file paths that define which modules will be exposed by the application for remote use in a microfrontend architecture. |
| 76 | +- ```remoteEntry``` - A map defining remote entry points for other microfrontend applications to consume. This is typically used to declare where other remote applications can be accessed. |
| 77 | +- ```deployUrlEnvName``` - The name of an environment variable that specifies the deployUrl for the application. If this variable is set, it overrides the deployUrl configured elsewhere. |
| 78 | +
|
| 79 | +### Example Configuration |
| 80 | +Here’s an example of how the configuration might look in practice: |
| 81 | +```json |
| 82 | +{ |
| 83 | + "skipList": ["dependency-to-skip"] // or 'dependency-to-skip.json', |
| 84 | + "externalList": ["@angular/core", "@angular/platform-browser"], // or 'external-list-dependency.json', |
| 85 | + "esPlugins": ["path/to/esbuild/plugin1.ts", "path/to/esbuild/plugin2.ts"], |
| 86 | + "indexHtmlTransformer": "path/to/transform-index.ts", |
| 87 | + "exposes": { |
| 88 | + "./ComponentA": "./src/component-a.ts" |
| 89 | + }, |
| 90 | + "remoteEntry": { |
| 91 | + "remoteApp": "https://remoteapp.example.com/remoteEntry.js" |
| 92 | + }, |
| 93 | + "deployUrlEnvName": "MY_APP_DEPLOY_URL" |
| 94 | +} |
| 95 | + |
| 96 | +``` |
| 97 | +This configuration excludes a dependency from processing, treats Angular core modules as external, includes custom plugins, modifies index.html, exposes a component, specifies a remote entry, and supports deployment via an environment variable. |
| 98 | + |
| 99 | +### Import dinamic remote module |
| 100 | + |
| 101 | +```typescript |
| 102 | + |
| 103 | +import { loadModule } from '@klerick/nx-angular-mf/loadModule'; |
| 104 | + |
| 105 | +export const appRoutes: Route[] = [ |
| 106 | + { |
| 107 | + path: 'some-url', |
| 108 | + loadChildren: () => |
| 109 | + loadModule<{ firstRoutes: Route[] }>('remoteApp/ComponentA').then( |
| 110 | + (r) => r.firstRoutes |
| 111 | + ), |
| 112 | + }, |
| 113 | +]; |
| 114 | +``` |
| 115 | + |
| 116 | +Or you can load dynamic components |
| 117 | + |
| 118 | +```typescript |
| 119 | +import { |
| 120 | + DestroyRef, |
| 121 | + Directive, |
| 122 | + ExperimentalPendingTasks, |
| 123 | + inject, |
| 124 | + ViewContainerRef, |
| 125 | +} from '@angular/core'; |
| 126 | +import { loadModule } from '@klerick/nx-angular-mf/loadModule'; |
| 127 | + |
| 128 | +@Directive({ |
| 129 | + selector: '[appDynamic]', |
| 130 | + standalone: true, |
| 131 | +}) |
| 132 | +export class DynamicDirective { |
| 133 | + private viewContainerRef = inject(ViewContainerRef); |
| 134 | + |
| 135 | + ngOnInit(): void { |
| 136 | + this.render(); |
| 137 | + } |
| 138 | + |
| 139 | + private async render() { |
| 140 | + |
| 141 | + const component = await loadModule<{ firstRoutes: Route[] }>('remoteApp/ComponentA').then( |
| 142 | + ({ DynamicComponent }) => ComponentA |
| 143 | + ); |
| 144 | + if (this.destroyed) return; |
| 145 | + |
| 146 | + const ref = this.viewContainerRef.createComponent(component); |
| 147 | + ref.changeDetectorRef.detectChanges(); |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +``` |
| 152 | + |
| 153 | + |
| 154 | +### Contribution |
| 155 | + |
| 156 | +Contributions are welcome! Please submit a pull request or open an issue to suggest improvements or report bugs. |
| 157 | + |
| 158 | +### License |
| 159 | + |
| 160 | +This project is licensed under the MIT License. See the [LICENSE](../../LICENSE) file for details. |
0 commit comments