Skip to content

Commit 61414f7

Browse files
committed
docs(nf): update tutorial in readme
1 parent 36e4e16 commit 61414f7

File tree

6 files changed

+130
-119
lines changed

6 files changed

+130
-119
lines changed

libs/native-federation/README.md

Lines changed: 99 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,29 @@ We migrated our webpack Module Federation example to Native Federation:
5050

5151
![Example](https://raw.githubusercontent.com/angular-architects/module-federation-plugin/main/libs/native-federation/example.png)
5252

53-
Please find the example [here (branch: nf-solution)](https://github.com/manfredsteyer/module-federation-plugin-example/tree/nf-solution):
53+
Please find the example [here (branch: nf-standalone-solution)](https://github.com/manfredsteyer/module-federation-plugin-example/tree/nf-standalone-solution):
5454

5555
```
56-
git clone https://github.com/manfredsteyer/module-federation-plugin-example.git --branch nf-solution
56+
git clone https://github.com/manfredsteyer/module-federation-plugin-example.git --branch nf-standalone-solution
5757
5858
cd module-federation-plugin-example
5959
6060
npm i
61-
npm run build
62-
npm start
6361
```
6462

65-
Then, open http://localhost:3000 in your browser.
63+
Start the Micro Frontend:
6664

67-
Please note, that the current **experimental** version does **not** support `ng serve`. Hence, you need to build it and serve it from the `dist` folder (this is what npm run build && npm run start in the above shown example do).
65+
```
66+
ng serve mfe1 -o
67+
```
68+
69+
Wait until the Micro Frontend is started.
70+
71+
Open another console and start the shell:
72+
73+
```
74+
ng serve shell -o
75+
```
6876

6977
## About the Mental Model
7078

@@ -77,41 +85,43 @@ For this, the mental model introduces several concepts:
7785
- **Shared Dependencies:** If a several remotes and the host use the same library, you might not want to download it several times. Instead, you might want to just download it once and share it at runtime. For this use case, the mental model allows for defining such shared dependencies.
7886
- **Version Mismatch:** If two or more applications use a different version of the same shared library, we need to prevent a version mismatch. To deal with it, the mental model defines several strategies, like falling back to another version that fits the application, using a different compatible one (according to semantic versioning) or throwing an error.
7987

80-
## Usage
88+
## Usage/ Tutorial
8189

82-
> You can checkout the [nf-starter branch](https://github.com/manfredsteyer/module-federation-plugin-example/tree/nf-solution) to try out Native Federation.
83-
84-
### Adding Native Federation
90+
You can checkout the [nf-standalone-starter branch](https://github.com/manfredsteyer/module-federation-plugin-example/tree/nf-standalone-starter) to try out Native Federation:
8591

8692
```
87-
npm i @angular-architects/native-federation -D
93+
git clone https://github.com/manfredsteyer/module-federation-plugin-example.git --branch nf-standalone-starter
94+
95+
cd module-federation-plugin-example
96+
97+
npm i
8898
```
8999

90-
Making an application a host:
100+
### Adding Native Federation
91101

92102
```
93-
ng g @angular-architects/native-federation:init --project shell --type host
103+
npm i @angular-architects/native-federation -D
94104
```
95105

96-
A dynamic host is a host reading the configuration data at runtime from a `.json` file:
106+
Making an application a remote (Micro Frontend):
97107

98108
```
99-
ng g @angular-architects/native-federation:init --project shell --type dynamic-host
109+
ng g @angular-architects/native-federation:init --project mfe1 --port 4201 --type remote
100110
```
101111

102-
Making an application a remote:
112+
Making an application a host (shell):
103113

104114
```
105-
ng g @angular-architects/native-federation:init --project mfe1 --type remote
115+
ng g @angular-architects/native-federation:init --project shell --port 4200 --type dynamic-host
106116
```
107117

118+
A dynamic host reads the configuration data at runtime from a `.json` file.
119+
108120
### Configuring the Host
109121

110-
The host configuration looks like what you know from our Module Federation plugin:
122+
The host configuration (`projects/shell/federation.config.js`) looks like what you know from our Module Federation plugin:
111123

112124
```javascript
113-
// projects/shell/federation.config.js
114-
115125
const {
116126
withNativeFederation,
117127
shareAll,
@@ -125,16 +135,24 @@ module.exports = withNativeFederation({
125135
requiredVersion: 'auto',
126136
}),
127137
},
138+
139+
skip: [
140+
'rxjs/ajax',
141+
'rxjs/fetch',
142+
'rxjs/testing',
143+
'rxjs/webSocket',
144+
// Add further packages you don't need at runtime
145+
],
128146
});
129147
```
130148

149+
> Our `init` schematic shown above generates this file for you.
150+
131151
### Configuring the Remote
132152

133-
Also the remote configuration looks familiar:
153+
Also, the remote configuration (`projects/mfe1/federation.config.js`) looks familiar:
134154

135155
```javascript
136-
// projects/mfe1/federation.config.js
137-
138156
const {
139157
withNativeFederation,
140158
shareAll,
@@ -144,7 +162,7 @@ module.exports = withNativeFederation({
144162
name: 'mfe1',
145163

146164
exposes: {
147-
'./Module': './projects/mfe1/src/app/flights/flights.module.ts',
165+
'./Component': './projects/mfe1/src/app/app.component.ts',
148166
},
149167

150168
shared: {
@@ -154,37 +172,24 @@ module.exports = withNativeFederation({
154172
requiredVersion: 'auto',
155173
}),
156174
},
157-
});
158-
```
159-
160-
### Initializing the Host
161175

162-
Call `initFederation` before bootstrapping your `main.ts`:
163-
164-
```typescript
165-
// projects/shell/src/main.ts
166-
167-
import { initFederation } from '@angular-architects/native-federation';
168-
169-
initFederation({
170-
mfe1: 'http://localhost:3001/remoteEntry.json',
171-
})
172-
.catch((err) => console.error(err))
173-
.then((_) => import('./bootstrap'))
174-
.catch((err) => console.error(err));
176+
skip: [
177+
'rxjs/ajax',
178+
'rxjs/fetch',
179+
'rxjs/testing',
180+
'rxjs/webSocket',
181+
// Add further packages you don't need at runtime
182+
],
183+
});
175184
```
176185

177-
> Our `init` schematic shown above generates all of this if you pass `--type host`.
178-
179-
You can directly pass a mapping between remote names and their `remoteEntry.json`. The `remoteEntry.json` contains the necessary metadata. It is generated when compiling the remote.
186+
> Our `init` schematic shown above generates this file for you.
180187
181-
Please note that in Native Federation, the remote entry is just a `.json` file while its a `.js` file in Module Federation.
188+
### Initializing the Host
182189

183-
However, you don't need to hardcode this mapping. Feel free to point to the file name of a federation manifest:
190+
When bootstrapping the host (shell), Native Federation (`projects\shell\src\main.ts`) is initialized:
184191

185192
```typescript
186-
// projects/shell/src/main.ts
187-
188193
import { initFederation } from '@angular-architects/native-federation';
189194

190195
initFederation('/assets/federation.manifest.json')
@@ -193,23 +198,27 @@ initFederation('/assets/federation.manifest.json')
193198
.catch((err) => console.error(err));
194199
```
195200

196-
This manifest can be exchanged when deploying the solution. Hence, you can adopt the solution to the current environment.
201+
> This file is generated by the schematic described above.
197202
198-
> Our `init` schematic shown above generates this variation if you pass `--type dynamic-host`.
203+
The function points to a federation manifest. This manifest points to the individual Micro Frontends. It can be exchanged when deploying the solution. Hence, you can adopt the solution to the current environment.
199204

200-
Credits: The Nx team originally came up with the idea for the manifest.
205+
**Credits:** The Nx team originally came up with the idea for the manifest.
201206

202-
This is what the (also generated) federation manifest looks like:
207+
This is what the (also generated) federation manifest (`projects\shell\src\assets\federation.manifest.json`) looks like:
203208

204209
```json
205210
{
206-
"mfe1": "http://localhost:3001/remoteEntry.json"
211+
"mfe1": "http://localhost:4201/remoteEntry.json"
207212
}
208213
```
209214

215+
Native Federation generates the `remoteEntry.json`. It contains metadata about the individual remote.
216+
217+
If you follow this tutorial, ensure this entry points to port `4201` (!).
218+
210219
### Initializing the Remote
211220

212-
Also, the remote needs to be initialized. If a remote doesn't load further remotes, you don't need to pass any mappings to `initFederation`:
221+
When bootstrapping your remote (`projects\mfe1\src\main.ts`), Native Federation is initialized too:
213222

214223
```typescript
215224
import { initFederation } from '@angular-architects/native-federation';
@@ -220,77 +229,59 @@ initFederation()
220229
.catch((err) => console.error(err));
221230
```
222231

223-
### Loading a Remote
224-
225-
Use the helper function `loadRemoteModule` to load a configured remote:
232+
> Our `init` schematic shown above also generates this file.
226233
227-
```typescript
228-
import { loadRemoteModule } from '@angular-architects/native-federation';
229-
[...]
230-
231-
export const APP_ROUTES: Routes = [
232-
[...]
233-
234-
{
235-
path: 'flights',
236-
loadChildren: () => loadRemoteModule({
237-
remoteName: 'mfe1',
238-
exposedModule: './Module'
239-
}).then(m => m.FlightsModule)
240-
},
241-
242-
[...]
243-
}
244-
```
234+
After the initialization, it loads the file `bootstrap.ts` starting your Angular application.
245235

246-
This can be used with and without routing; with `NgModule`s but also with **standalone** building blocks. Just use it instead of dynamic imports.
236+
### Loading a Remote
247237

248-
For the sake of compatibility with our Module Federation API, you can also use the `remoteEntry` to identify the remote in question:
238+
For loading a component (or any other building block) exposed by a remote into the host, use Native Federation's `loadRemoteModule` function together with lazy loading (`projects\shell\src\app\app.routes.ts`):
249239

250240
```typescript
241+
import { Routes } from '@angular/router';
242+
import { HomeComponent } from './home/home.component';
243+
import { NotFoundComponent } from './not-found/not-found.component';
244+
245+
// Add this import:
251246
import { loadRemoteModule } from '@angular-architects/native-federation';
252-
[...]
253247

254248
export const APP_ROUTES: Routes = [
255-
[...]
256-
257-
{
258-
path: 'flights',
259-
loadChildren: () => loadRemoteModule({
260-
// Alternative: You can also use the remoteEntry i/o the remoteName:
261-
remoteEntry: 'http://localhost:3001/remoteEntry.json',
262-
exposedModule: './Module'
263-
}).then(m => m.FlightsModule)
264-
},
265-
266-
[...]
267-
}
268-
```
269-
270-
However, we prefer the first option where just the `remoteName` is passed.
249+
{
250+
path: '',
251+
component: HomeComponent,
252+
pathMatch: 'full',
253+
},
271254

272-
### Polyfill
255+
// Add this route:
256+
{
257+
path: 'flights',
258+
loadComponent: () =>
259+
loadRemoteModule('mfe1', './Component').then((m) => m.AppComponent),
260+
},
273261

274-
This library uses Import Maps. As of today, not all browsers support this emerging browser feature, we need a polyfill. We recommend the polyfill `es-module-shims` which has been developed for production use cases. Our schematics install it via npm and add it to your `polyfills.ts`.
262+
{
263+
path: '**',
264+
component: NotFoundComponent,
265+
},
275266

276-
Also, the schematics add the following to your `index.html`:
267+
// DO NOT insert routes after this one.
268+
// { path:'**', ...} needs to be the LAST one.
269+
];
270+
```
277271

278-
```html
279-
<script type="esms-options">
280-
{
281-
"shimMode": true,
282-
"mapOverrides": true
283-
}
284-
</script>
272+
### Starting your example
285273

286-
<script type="module" src="polyfills.js"></script>
274+
Start the remote:
287275

288-
<script type="module-shim" src="main.js"></script>
276+
```
277+
ng serve mfe1 -o
289278
```
290279

291-
The script with the type `esms-options` configures the polyfill. This library was built for shim mode. In this mode, the polyfill provides some additional features beyond the proposal for Import Maps. These features, for instance, allow for dynamically creating an import map after loading the first EcmaScript module. Native Federation uses this possibility.
280+
Once, the remote is started, start the shell:
292281

293-
To make the polyfill to load your EcmaScript modules (bundles) in shim mode, assign the type `module-shim`. However, please just use module for the polyfill bundle itself to prevent an hen/egg-issue.
282+
```
283+
ng serve shell -o
284+
```
294285

295286
## FAQ
296287

libs/native-federation/src/builders/build/builder.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,20 @@ export async function runBuilder(
5757

5858
options.externalDependencies = externals.filter((e) => e !== 'tslib');
5959

60-
const builderRun = await context.scheduleBuilder(
61-
'@angular-devkit/build-angular:browser-esbuild',
62-
options as any,
63-
{ target }
64-
);
60+
// const builderRun = await context.scheduleBuilder(
61+
// '@angular-devkit/build-angular:browser-esbuild',
62+
// options as any,
63+
// { target }
64+
// );
65+
66+
const builderRun = await context.scheduleTarget(target, options as any);
6567

6668
let first = true;
67-
builderRun.output.subscribe(async () => {
69+
builderRun.output.subscribe(async (output) => {
70+
if (!output.success) {
71+
return;
72+
}
73+
6874
updateIndexHtml(fedOptions);
6975

7076
if (first) {
@@ -83,7 +89,8 @@ export async function runBuilder(
8389
rebuildEvents.rebuildExposed.emit(),
8490
]);
8591
logger.info('Done!');
86-
reloadShell(nfOptions.shell);
92+
93+
setTimeout(() => reloadShell(nfOptions.shell), 0);
8794
}, nfOptions.rebuildDelay);
8895
}
8996

libs/native-federation/src/builders/build/schema.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { JsonObject } from '@angular-devkit/core';
33
export interface NfBuilderSchema extends JsonObject {
44
target: string;
55
dev: boolean;
6-
devServerPort: number;
6+
port: number;
7+
open: boolean;
78
rebuildDelay: number;
89
shell: string;
910
} // eslint-disable-line

libs/native-federation/src/builders/build/schema.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@
1313
"description": "Set this to true to start the builder in dev mode",
1414
"default": false
1515
},
16-
"devServerPort": {
16+
"port": {
1717
"type": "number",
1818
"default": 4200
1919
},
20+
"open": {
21+
"type": "boolean",
22+
"default": true,
23+
"description": "Open browser?",
24+
"alias": "o"
25+
},
2026
"rebuildDelay": {
2127
"type": "number",
2228
"default": 2000,

0 commit comments

Comments
 (0)