@softarc/native-federation 4.2.0 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @softarc/native-federation
|
|
2
2
|
|
|
3
|
-
Native Federation is a "browser-native" implementation of the successful mental model behind
|
|
3
|
+
Native Federation is a "browser-native" implementation of the successful mental model behind webpack Module Federation. It can be **used with any framework and build tool** for implementing **Micro Frontends** and plugin-based architectures.
|
|
4
4
|
|
|
5
5
|
> [!WARNING]
|
|
6
6
|
> **This is our v4 version**. For the v3 version, check out the [module-federation-plugin repository](https://github.com/angular-architects/module-federation-plugin/tree/main/libs/native-federation-core).
|
|
@@ -35,7 +35,7 @@ For this, the mental model introduces several concepts:
|
|
|
35
35
|
|
|
36
36
|
- **Remote:** The remote is a separately built and deployed application. It can **expose EcmaScript** modules that can be loaded into other applications.
|
|
37
37
|
- **Host:** The host loads one or several remotes on demand. For your framework's perspective, this looks like traditional lazy loading. The big difference is that the host doesn't know the remotes at compilation time.
|
|
38
|
-
- **Shared Dependencies:** If
|
|
38
|
+
- **Shared Dependencies:** If 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.
|
|
39
39
|
- **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.
|
|
40
40
|
|
|
41
41
|
## Example
|
|
@@ -52,8 +52,8 @@ For this, the mental model introduces several concepts:
|
|
|
52
52
|
Big thanks to:
|
|
53
53
|
|
|
54
54
|
- [Zack Jackson](https://twitter.com/ScriptedAlchemy) for originally coming up with the great idea of Module Federation and its successful mental model
|
|
55
|
-
- [Florian Rappl](https://twitter.com/FlorianRappl) for
|
|
56
|
-
- [Michael Egger-Zikes](https://twitter.com/MikeZks) for contributing to our Module Federation efforts and
|
|
55
|
+
- [Florian Rappl](https://twitter.com/FlorianRappl) for a good discussion about these topics during a speakers dinner in Nuremberg
|
|
56
|
+
- [Michael Egger-Zikes](https://twitter.com/MikeZks) for contributing to our Module Federation efforts and bringing in valuable feedback
|
|
57
57
|
- The Angular CLI-Team, esp. [Alan Agius](https://twitter.com/AlanAgius4) and [Charles Lyding](https://twitter.com/charleslyding), for working on the experimental esbuild builder for Angular
|
|
58
58
|
|
|
59
59
|
## Using this Library
|
|
@@ -145,7 +145,7 @@ The method `federationBuilder.build` bundles the shared and exposed parts of you
|
|
|
145
145
|
|
|
146
146
|
The `withNativeFederation` function sets up a configuration for your applications. This is an example configuration for a host:
|
|
147
147
|
|
|
148
|
-
The `shareAll` helper shares all your dependencies defined in your `package.json
|
|
148
|
+
The `shareAll` helper shares all your dependencies defined in your `package.json`:
|
|
149
149
|
|
|
150
150
|
```typescript
|
|
151
151
|
// shell/federation.config.js
|
|
@@ -168,9 +168,9 @@ export default withNativeFederation({
|
|
|
168
168
|
|
|
169
169
|
The options passed to shareAll are applied to all dependencies found in your `package.json`.
|
|
170
170
|
|
|
171
|
-
This might come in handy in
|
|
171
|
+
This might come in handy in a monorepo scenario and when doing some experiments / troubleshooting.
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
You can also add overrides to `shareAll` for specific packages:
|
|
174
174
|
|
|
175
175
|
```typescript
|
|
176
176
|
// shell/federation.config.js
|
|
@@ -226,17 +226,17 @@ shared: share({
|
|
|
226
226
|
})
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
The added options are `
|
|
229
|
+
The added options are `requiredVersion: 'auto'` and `includeSecondaries`.
|
|
230
230
|
|
|
231
|
-
####
|
|
231
|
+
#### requiredVersion: 'auto'
|
|
232
232
|
|
|
233
|
-
If you set `
|
|
233
|
+
If you set `requiredVersion` to `'auto'`, the helper takes the version defined in your `package.json`.
|
|
234
234
|
|
|
235
|
-
This helps to solve issues with not (fully) met peer dependencies and secondary entry points
|
|
235
|
+
This helps to solve issues with not (fully) met peer dependencies and secondary entry points.
|
|
236
236
|
|
|
237
|
-
By default, it takes the `package.json` that is closest to the caller (normally the `
|
|
237
|
+
By default, it takes the `package.json` that is closest to the caller (normally the `federation.config.js`). However, you can pass the path to another `package.json` using the second optional parameter. Also, you need to define the shared library within the dependencies in your `package.json`.
|
|
238
238
|
|
|
239
|
-
Instead of setting
|
|
239
|
+
Instead of setting `requiredVersion` to `auto` time and again, you can also skip this option and call `setInferVersion(true)` before:
|
|
240
240
|
|
|
241
241
|
```typescript
|
|
242
242
|
setInferVersion(true);
|
|
@@ -268,7 +268,7 @@ shared: share({
|
|
|
268
268
|
})
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
Wildcard (`*`) export entry points are not expanded by default. To resolve them into concrete secondary entry points, enable the `resolveGlob` property:
|
|
272
272
|
|
|
273
273
|
```typescript
|
|
274
274
|
shared: share({
|
|
@@ -282,7 +282,7 @@ shared: share({
|
|
|
282
282
|
})
|
|
283
283
|
```
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
Resolving globs can create a bundle for every valid exported file it finds, **so it is recommended to keep the `ignoreUnusedDeps` feature enabled** (it is on by default) to drop the ones you don't use. If you want to specifically skip certain parts of the glob export, you can also use the wildcard in the skip section:
|
|
286
286
|
|
|
287
287
|
```typescript
|
|
288
288
|
shared: share({
|
|
@@ -296,7 +296,7 @@ shared: share({
|
|
|
296
296
|
})
|
|
297
297
|
```
|
|
298
298
|
|
|
299
|
-
Finally, it's also possible to break out of the
|
|
299
|
+
Finally, it's also possible to break out of the `ignoreUnusedDeps` feature for specific externals if desired, for example when sharing a whole suite of interconnected external dependencies like @angular/core. This can be handy when you want to avoid the chance of cross-version secondary entrypoints being used by the different micro frontends. E.g. mfe1 uses @angular/core v20.1.0 and mfe2 uses @angular/core/rxjs-interop v20.0.8, then you might want consistent use of v20.1.0 so rxjs-interop should be exported by mfe1. The `keepAll` prop allows you to enforce this:
|
|
300
300
|
|
|
301
301
|
```typescript
|
|
302
302
|
shared: share({
|
|
@@ -310,14 +310,13 @@ shared: share({
|
|
|
310
310
|
})
|
|
311
311
|
```
|
|
312
312
|
|
|
313
|
-
The API for configuring and using Native Federation is very similar to the one provided by our Module Federation plugin [@angular-architects/module-federation](https://www.npmjs.com/package/@angular-architects/
|
|
313
|
+
The API for configuring and using Native Federation is very similar to the one provided by our Module Federation plugin [@angular-architects/module-federation](https://www.npmjs.com/package/@angular-architects/module-federation). Hence, most of the articles on it are also valid for Native Federation.
|
|
314
314
|
|
|
315
315
|
### Sharing
|
|
316
316
|
|
|
317
317
|
The `shareAll`-helper used here shares all dependencies found in your `package.json`. Hence, they only need to be loaded once (instead of once per remote and host). If you don't want to share all of them, you can opt-out of sharing by using the `skip` option:
|
|
318
318
|
|
|
319
319
|
```typescript
|
|
320
|
-
n
|
|
321
320
|
export default withNativeFederation({
|
|
322
321
|
[...]
|
|
323
322
|
|
|
@@ -332,7 +331,7 @@ export default withNativeFederation({
|
|
|
332
331
|
|
|
333
332
|
### Sharing Mapped Paths (Monorepo-internal Libraries)
|
|
334
333
|
|
|
335
|
-
Paths mapped in your `tsconfig.json` are shared by default too. While they are part of your (mono) repository, they are
|
|
334
|
+
Paths mapped in your `tsconfig.json` are shared by default too. While they are part of your (mono) repository, they are treated like libraries:
|
|
336
335
|
|
|
337
336
|
```json
|
|
338
337
|
{
|
|
@@ -349,7 +348,7 @@ Paths mapped in your `tsconfig.json` are shared by default too. While they are p
|
|
|
349
348
|
|
|
350
349
|
If you don't want to share (all of) them, put their names into the skip array (see above).
|
|
351
350
|
|
|
352
|
-
###
|
|
351
|
+
### Determining which internal libraries are shared
|
|
353
352
|
|
|
354
353
|
In Nx/monorepo setups, Native Federation shares all libraries from your `tsconfig` path mappings by default.
|
|
355
354
|
|
|
@@ -376,7 +375,9 @@ Notes:
|
|
|
376
375
|
- Mapped paths are read from the workspace root tsconfig file: `tsconfig.base.json` if present, otherwise `tsconfig.json`.
|
|
377
376
|
- The workspace root is detected by searching upward from the current working directory until a `package.json` is found.
|
|
378
377
|
|
|
379
|
-
|
|
378
|
+
The `mappingVersion` feature flag controls whether mapped paths get a version. It is **enabled by default**: Native Federation reads the version from the mapped library's nearest `package.json` and shares it with strict versioning, just like a published library.
|
|
379
|
+
|
|
380
|
+
If your mapped paths point at plain internal source that isn't distributed as a versioned, buildable library, you can disable it. The mapped paths are then shared without a version constraint.
|
|
380
381
|
|
|
381
382
|
```js
|
|
382
383
|
module.exports = withNativeFederation({
|
|
@@ -394,11 +395,9 @@ module.exports = withNativeFederation({
|
|
|
394
395
|
});
|
|
395
396
|
```
|
|
396
397
|
|
|
397
|
-
If enabled, Native Federation tries to read the version from the mapped library's nearest `package.json`. By default this feature is set to `false`.
|
|
398
|
-
|
|
399
398
|
### Code-Splitting for Shared Dependencies
|
|
400
399
|
|
|
401
|
-
By default, Native Federation enables code-splitting (chunking) for shared dependencies. This means large libraries can be split into smaller chunks which reduces the
|
|
400
|
+
By default, Native Federation enables code-splitting (chunking) for shared dependencies. This means large libraries can be split into smaller chunks which reduces the overall size, improving initial load times.
|
|
402
401
|
|
|
403
402
|
You can configure code-splitting at two levels:
|
|
404
403
|
|
|
@@ -436,18 +435,19 @@ module.exports = withNativeFederation({
|
|
|
436
435
|
strictVersion: true,
|
|
437
436
|
requiredVersion: 'auto',
|
|
438
437
|
},
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
438
|
+
{
|
|
439
|
+
overrides: {
|
|
440
|
+
// Disable code-splitting for a specific package
|
|
441
|
+
'large-lib': {
|
|
442
|
+
singleton: true,
|
|
443
|
+
strictVersion: true,
|
|
444
|
+
requiredVersion: 'auto',
|
|
445
|
+
chunks: false,
|
|
446
|
+
build: 'package', // necessary for isolated bundles
|
|
447
|
+
},
|
|
446
448
|
},
|
|
447
449
|
}
|
|
448
450
|
),
|
|
449
|
-
// Disable code-splitting for a specific package
|
|
450
|
-
|
|
451
451
|
},
|
|
452
452
|
});
|
|
453
453
|
```
|
|
@@ -597,11 +597,11 @@ const { loadRemoteModule } = await initFederation(manifest, {
|
|
|
597
597
|
|
|
598
598
|
## React and Other CommonJS Libs
|
|
599
599
|
|
|
600
|
-
Native Federation uses Web Standards like EcmaScript Modules. Most libs and frameworks support them meanwhile. Unfortunately, React still uses CommonJS (
|
|
600
|
+
Native Federation uses Web Standards like EcmaScript Modules. Most libs and frameworks support them meanwhile. Unfortunately, React still uses CommonJS (and UMD). We do our best to convert these libs to EcmaScript Modules. In the case of React there are some challenges due to the dynamic way the React bundles use the `exports` object.
|
|
601
601
|
|
|
602
|
-
As the community is moving to
|
|
602
|
+
As the community is moving to EcmaScript Modules, we expect that these issues will vanish over time. In between, we provide some solutions for dealing with CommonJS-based libraries using `exports` in a dynamic way.
|
|
603
603
|
|
|
604
|
-
One of them is `
|
|
604
|
+
One of them is `fileReplacements`:
|
|
605
605
|
|
|
606
606
|
```javascript
|
|
607
607
|
import { reactReplacements } from '@softarc/native-federation-esbuild/src/lib/react-replacements';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SkipList, type PreparedSkipList } from '../domain/config/skip-list.contract.js';
|
|
2
2
|
import type { PackageJsonRepository } from '../domain/utils/package-json.contract.js';
|
|
3
3
|
import type { FileReaderPort, GlobPort } from '../domain/utils/io-port.contract.js';
|
|
4
|
-
import type { ExternalConfig, IncludeSecondariesOptions, ShareAllExternalsOptions, SharedExternalsConfig, ShareExternalsOptions } from '../domain/config/external-config.contract.js';
|
|
4
|
+
import type { ExternalConfig, IncludeSecondariesOptions, ResolvedSharedExternalsConfig, ShareAllExternalsOptions, SharedExternalsConfig, ShareExternalsOptions } from '../domain/config/external-config.contract.js';
|
|
5
5
|
export declare function findRootTsConfigJson(): string;
|
|
6
6
|
export declare function findRootTsConfigJsonCore(io: FileReaderPort): string;
|
|
7
7
|
export declare function getSecondaries(io: FileReaderPort & GlobPort, includeSecondaries: IncludeSecondariesOptions, libPath: string, key: string, shareObject: ExternalConfig, preparedSkipList: PreparedSkipList): SharedExternalsConfig | null;
|
|
@@ -9,12 +9,12 @@ export declare function shareAll(config: ShareAllExternalsOptions, opts?: {
|
|
|
9
9
|
skipList?: SkipList;
|
|
10
10
|
projectPath?: string;
|
|
11
11
|
overrides?: ShareExternalsOptions;
|
|
12
|
-
}):
|
|
12
|
+
}): ResolvedSharedExternalsConfig;
|
|
13
13
|
export declare function shareAllCore(io: FileReaderPort & GlobPort, config: ShareAllExternalsOptions, opts?: {
|
|
14
14
|
skipList?: SkipList;
|
|
15
15
|
projectPath?: string;
|
|
16
16
|
overrides?: ShareExternalsOptions;
|
|
17
|
-
}, repo?: PackageJsonRepository):
|
|
17
|
+
}, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
|
|
18
18
|
export declare function setInferVersion(infer: boolean): void;
|
|
19
|
-
export declare function share(configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList):
|
|
20
|
-
export declare function shareCore(io: FileReaderPort & GlobPort, configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList, repo?: PackageJsonRepository):
|
|
19
|
+
export declare function share(configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList): ResolvedSharedExternalsConfig;
|
|
20
|
+
export declare function shareCore(io: FileReaderPort & GlobPort, configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
|
|
@@ -56,7 +56,7 @@ function normalizeShared(config, skip, chunks) {
|
|
|
56
56
|
strictVersion: sharedConfig.strictVersion ?? false,
|
|
57
57
|
version: sharedConfig.version,
|
|
58
58
|
chunks: sharedConfig.chunks ?? chunks,
|
|
59
|
-
includeSecondaries: sharedConfig.includeSecondaries,
|
|
59
|
+
includeSecondaries: typeof sharedConfig.includeSecondaries === "object" ? !!sharedConfig.includeSecondaries.keepAll : sharedConfig.includeSecondaries,
|
|
60
60
|
packageInfo: sharedConfig.packageInfo,
|
|
61
61
|
platform: sharedConfig.platform ?? config.platform ?? "browser",
|
|
62
62
|
build: sharedConfig.build ?? "default",
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
export type IncludeSecondariesOptions = {
|
|
2
|
+
skip?: string | string[];
|
|
3
|
+
resolveGlob?: boolean;
|
|
4
|
+
keepAll?: boolean;
|
|
5
|
+
} | boolean;
|
|
1
6
|
export interface ExternalConfig {
|
|
2
7
|
singleton?: boolean;
|
|
3
8
|
strictVersion?: boolean;
|
|
4
9
|
requiredVersion?: string;
|
|
5
10
|
version?: string;
|
|
6
|
-
includeSecondaries?:
|
|
11
|
+
includeSecondaries?: IncludeSecondariesOptions;
|
|
7
12
|
platform?: 'browser' | 'node';
|
|
8
13
|
build?: 'separate' | 'package';
|
|
9
14
|
chunks?: boolean;
|
|
@@ -30,14 +35,11 @@ export interface NormalizedExternalConfig {
|
|
|
30
35
|
esm: boolean;
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
|
-
export type IncludeSecondariesOptions = {
|
|
34
|
-
skip?: string | string[];
|
|
35
|
-
resolveGlob?: boolean;
|
|
36
|
-
keepAll?: boolean;
|
|
37
|
-
} | boolean;
|
|
38
38
|
export type SharedExternalsConfig = Record<string, ExternalConfig>;
|
|
39
39
|
export type NormalizedSharedExternalsConfig = Record<string, NormalizedExternalConfig>;
|
|
40
|
-
export type ShareAllExternalsOptions =
|
|
41
|
-
|
|
40
|
+
export type ShareAllExternalsOptions = ExternalConfig;
|
|
41
|
+
export type ShareExternalsOptions = SharedExternalsConfig;
|
|
42
|
+
export type ResolvedExternalConfig = Omit<ExternalConfig, 'includeSecondaries'> & {
|
|
43
|
+
includeSecondaries?: boolean;
|
|
42
44
|
};
|
|
43
|
-
export type
|
|
45
|
+
export type ResolvedSharedExternalsConfig = Record<string, ResolvedExternalConfig>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { ExternalConfig, IncludeSecondariesOptions, SharedExternalsConfig, ShareAllExternalsOptions, ShareExternalsOptions, } from './external-config.contract.js';
|
|
1
|
+
export type { ExternalConfig, IncludeSecondariesOptions, ResolvedExternalConfig, ResolvedSharedExternalsConfig, SharedExternalsConfig, ShareAllExternalsOptions, ShareExternalsOptions, } from './external-config.contract.js';
|
|
2
2
|
export type { FederationConfig } from './federation-config.contract.js';
|
|
3
3
|
export type { PreparedSkipList, SkipFn, SkipList, SkipListEntry } from './skip-list.contract.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softarc/native-federation",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"globals": "^17.3.0",
|
|
27
27
|
"jiti": "^2.6.1",
|
|
28
28
|
"jsdom": "^29.0.0",
|
|
29
|
-
"knip": "^6.
|
|
29
|
+
"knip": "^6.20.0",
|
|
30
30
|
"prettier": "^3.8.1",
|
|
31
31
|
"tslib": "^2.3.0",
|
|
32
32
|
"typescript": "~6.0.0",
|