@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 wepback Module Federation for building Micro Frontends and plugin-based solutions. It can be **used with any framework and build tool** for implementing **Micro Frontends** and plugin-based architectures.
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 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.
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 an 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 brining in valuable feedback
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`. The `package.json` is look up as described above:
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 an mono repo scenario and when doing some experiments/ trouble shooting.
171
+ This might come in handy in a monorepo scenario and when doing some experiments / troubleshooting.
172
172
 
173
- > Since v21.1 it's also possible to add overrides to the shareAll for specific packages.
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 `requireVersion: 'auto'` and `includeSecondaries`.
229
+ The added options are `requiredVersion: 'auto'` and `includeSecondaries`.
230
230
 
231
- #### requireVersion: 'auto'
231
+ #### requiredVersion: 'auto'
232
232
 
233
- If you set `requireVersion` to `'auto'`, the helper takes the version defined in your `package.json`.
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 (see Pitfalls section below).
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 `webpack.config.js`). However, you can pass the path to an other `package.json` using the second optional parameter. Also, you need to define the shared libray within the node dependencies in your `package.json`.
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 requireVersion to auto time and again, you can also skip this option and call `setInferVersion(true)` before:
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
- By default, all entrypoints of a package are considered, you can disable expensive glob resolves using the `globResolve` property:
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
- This is enabled by default but might not always desirable since it will create a bundle of every valid exported file it finds, **Therefore it is recommended not to disable the `ignoreUnusedDeps` feature**. If you want to specifically skip certain parts of the glob export, you can also use the wildcard in the skip section:
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 "removeUnusedDep" 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 to use consistent use of v20.1.0 so rxjs-interop should be exported by mfe1. The "keepAll" prop allows you to enforce this:
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/native-federation). Hence, most the articles on it are also valid for Native Federation.
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 treaded like libraries:
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
- ### Detemining which internal libraries are shared
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
- If you don't want to share libraries within a monorepo and also distribute them as built libraries with a version, disable the `mappingVersion` feature flag in your `federation.config.js`. This ensures that the corresponding versions from your buildable libraries are used.
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 overal size, improving initial load times.
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
- overrides: {
440
- 'large-lib': {
441
- singleton: true,
442
- strictVersion: true,
443
- requiredVersion: 'auto',
444
- chunks: false,
445
- build: 'package' // necessary for isolated bundles
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 (und 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.
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 EcmaScrpt 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.
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 `fileReplacemnts`:
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
- }): ShareExternalsOptions | null;
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): ShareExternalsOptions | null;
17
+ }, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
18
18
  export declare function setInferVersion(infer: boolean): void;
19
- export declare function share(configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList): ShareExternalsOptions;
20
- export declare function shareCore(io: FileReaderPort & GlobPort, configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList, repo?: PackageJsonRepository): ShareExternalsOptions;
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?: boolean;
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 = Omit<ExternalConfig, 'includeSecondaries'> & {
41
- includeSecondaries?: IncludeSecondariesOptions;
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 ShareExternalsOptions = Record<string, ShareAllExternalsOptions>;
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.0",
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.16.1",
29
+ "knip": "^6.20.0",
30
30
  "prettier": "^3.8.1",
31
31
  "tslib": "^2.3.0",
32
32
  "typescript": "~6.0.0",