@softarc/native-federation 4.2.0 → 4.3.0

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
@@ -143,9 +143,78 @@ The method `federationBuilder.build` bundles the shared and exposed parts of you
143
143
 
144
144
  ### Configuring Hosts
145
145
 
146
- The `withNativeFederation` function sets up a configuration for your applications. This is an example configuration for a host:
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 `fromPackageJson` helper (recommended)
149
+
150
+ `fromPackageJson` is the recommended way to share your dependencies. It shares **all** dependencies found in your `package.json` and exposes a small fluent builder so you can fine-tune the result. The base options you pass are applied to every shared dependency; you then chain `.skip(...)`, `.override(...)` and `.patch(...)` as needed and finish with `.get()`:
151
+
152
+ ```typescript
153
+ // shell/federation.config.js
154
+
155
+ import { withNativeFederation, fromPackageJson } from '@softarc/native-federation/config';
156
+
157
+ export default withNativeFederation({
158
+ name: 'host',
159
+
160
+ shared: fromPackageJson({
161
+ singleton: true,
162
+ strictVersion: true,
163
+ requiredVersion: 'auto',
164
+ includeSecondaries: false,
165
+ }).get(),
166
+ });
167
+ ```
168
+
169
+ > [!TIP]
170
+ > If you omit the `shared` property entirely, Native Federation applies exactly this `fromPackageJson` configuration for you (with `singleton`, `strictVersion` and `requiredVersion: 'auto'`). So the snippet above is also a good description of the default behavior.
171
+
172
+ The builder returned by `fromPackageJson` offers three chainable methods, each of which returns the builder so you can combine them:
173
+
174
+ - **`.skip(externals)`** — exclude packages from sharing (added on top of the [default skip list](#sharing)).
175
+ - **`.override(externals)`** — replace the configuration for specific packages entirely. Use this when a package needs a completely different set of options.
176
+ - **`.patch(externals, cfg)`** — merge a partial configuration onto specific shared externals, keeping the base options for everything you don't touch.
177
+
178
+ ```typescript
179
+ // shell/federation.config.js
180
+
181
+ import { withNativeFederation, fromPackageJson } from '@softarc/native-federation/config';
182
+
183
+ export default withNativeFederation({
184
+ name: 'host',
185
+
186
+ shared: fromPackageJson({
187
+ singleton: true,
188
+ strictVersion: true,
189
+ requiredVersion: 'auto',
190
+ })
191
+ // Don't share these dependencies at all
192
+ .skip(['my-lib', 'some-dev-only-lib'])
193
+ // Give a package a completely different configuration
194
+ .override({
195
+ 'package-a/themes/xyz': {
196
+ singleton: true,
197
+ strictVersion: true,
198
+ requiredVersion: 'auto',
199
+ includeSecondaries: { skip: '@package-a/themes/xyz/*' },
200
+ build: 'package',
201
+ },
202
+ })
203
+ // Tweak a few options on specific packages while keeping the base config
204
+ .patch(['package-b'], {
205
+ singleton: false,
206
+ includeSecondaries: { skip: 'package-b/icons/*' },
207
+ build: 'package',
208
+ })
209
+ .get(),
210
+ });
211
+ ```
212
+
213
+ By default the closest `package.json` (relative to your `federation.config.js`) is used. You can point at a different one by passing its path as the second argument: `fromPackageJson(baseCfg, projectPath)`.
214
+
215
+ #### Alternative: the `shareAll` helper
216
+
217
+ `shareAll` is the older, object-spread style alternative to `fromPackageJson`. It also shares all dependencies defined in your `package.json`, but instead of a fluent builder it returns a plain object that you spread into `shared`:
149
218
 
150
219
  ```typescript
151
220
  // shell/federation.config.js
@@ -166,11 +235,9 @@ export default withNativeFederation({
166
235
  });
167
236
  ```
168
237
 
169
- The options passed to shareAll are applied to all dependencies found in your `package.json`.
238
+ The options passed to `shareAll` are applied to all dependencies found in your `package.json`. This might come in handy in a monorepo scenario and when doing some experiments / troubleshooting.
170
239
 
171
- This might come in handy in an mono repo scenario and when doing some experiments/ trouble shooting.
172
-
173
- > Since v21.1 it's also possible to add overrides to the shareAll for specific packages.
240
+ You can also add overrides to `shareAll` for specific packages, via the second argument:
174
241
 
175
242
  ```typescript
176
243
  // shell/federation.config.js
@@ -226,17 +293,17 @@ shared: share({
226
293
  })
227
294
  ```
228
295
 
229
- The added options are `requireVersion: 'auto'` and `includeSecondaries`.
296
+ The added options are `requiredVersion: 'auto'` and `includeSecondaries`.
230
297
 
231
- #### requireVersion: 'auto'
298
+ #### requiredVersion: 'auto'
232
299
 
233
- If you set `requireVersion` to `'auto'`, the helper takes the version defined in your `package.json`.
300
+ If you set `requiredVersion` to `'auto'`, the helper takes the version defined in your `package.json`.
234
301
 
235
- This helps to solve issues with not (fully) met peer dependencies and secondary entry points (see Pitfalls section below).
302
+ This helps to solve issues with not (fully) met peer dependencies and secondary entry points.
236
303
 
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`.
304
+ 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
305
 
239
- Instead of setting requireVersion to auto time and again, you can also skip this option and call `setInferVersion(true)` before:
306
+ Instead of setting `requiredVersion` to `auto` time and again, you can also skip this option and call `setInferVersion(true)` before:
240
307
 
241
308
  ```typescript
242
309
  setInferVersion(true);
@@ -268,7 +335,7 @@ shared: share({
268
335
  })
269
336
  ```
270
337
 
271
- By default, all entrypoints of a package are considered, you can disable expensive glob resolves using the `globResolve` property:
338
+ Wildcard (`*`) export entry points are not expanded by default. To resolve them into concrete secondary entry points, enable the `resolveGlob` property:
272
339
 
273
340
  ```typescript
274
341
  shared: share({
@@ -282,7 +349,7 @@ shared: share({
282
349
  })
283
350
  ```
284
351
 
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:
352
+ 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
353
 
287
354
  ```typescript
288
355
  shared: share({
@@ -296,7 +363,7 @@ shared: share({
296
363
  })
297
364
  ```
298
365
 
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:
366
+ 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
367
 
301
368
  ```typescript
302
369
  shared: share({
@@ -310,14 +377,13 @@ shared: share({
310
377
  })
311
378
  ```
312
379
 
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.
380
+ 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
381
 
315
382
  ### Sharing
316
383
 
317
384
  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
385
 
319
386
  ```typescript
320
- n
321
387
  export default withNativeFederation({
322
388
  [...]
323
389
 
@@ -332,7 +398,7 @@ export default withNativeFederation({
332
398
 
333
399
  ### Sharing Mapped Paths (Monorepo-internal Libraries)
334
400
 
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:
401
+ 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
402
 
337
403
  ```json
338
404
  {
@@ -349,7 +415,7 @@ Paths mapped in your `tsconfig.json` are shared by default too. While they are p
349
415
 
350
416
  If you don't want to share (all of) them, put their names into the skip array (see above).
351
417
 
352
- ### Detemining which internal libraries are shared
418
+ ### Determining which internal libraries are shared
353
419
 
354
420
  In Nx/monorepo setups, Native Federation shares all libraries from your `tsconfig` path mappings by default.
355
421
 
@@ -376,7 +442,9 @@ Notes:
376
442
  - Mapped paths are read from the workspace root tsconfig file: `tsconfig.base.json` if present, otherwise `tsconfig.json`.
377
443
  - The workspace root is detected by searching upward from the current working directory until a `package.json` is found.
378
444
 
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.
445
+ 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.
446
+
447
+ 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
448
 
381
449
  ```js
382
450
  module.exports = withNativeFederation({
@@ -394,11 +462,9 @@ module.exports = withNativeFederation({
394
462
  });
395
463
  ```
396
464
 
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
465
  ### Code-Splitting for Shared Dependencies
400
466
 
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.
467
+ 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
468
 
403
469
  You can configure code-splitting at two levels:
404
470
 
@@ -436,18 +502,19 @@ module.exports = withNativeFederation({
436
502
  strictVersion: true,
437
503
  requiredVersion: 'auto',
438
504
  },
439
- overrides: {
440
- 'large-lib': {
441
- singleton: true,
442
- strictVersion: true,
443
- requiredVersion: 'auto',
444
- chunks: false,
445
- build: 'package' // necessary for isolated bundles
505
+ {
506
+ overrides: {
507
+ // Disable code-splitting for a specific package
508
+ 'large-lib': {
509
+ singleton: true,
510
+ strictVersion: true,
511
+ requiredVersion: 'auto',
512
+ chunks: false,
513
+ build: 'package', // necessary for isolated bundles
514
+ },
446
515
  },
447
516
  }
448
517
  ),
449
- // Disable code-splitting for a specific package
450
-
451
518
  },
452
519
  });
453
520
  ```
@@ -475,6 +542,27 @@ module.exports = withNativeFederation({
475
542
 
476
543
  When enabled, instead of listing each chunk as a separate shared dependency, chunks are grouped by bundle name in a dedicated `chunks` object. Each shared dependency gets a `bundle` property linking it to its chunk bundle. This results in a smaller `remoteEntry.json` and allows chunks to be skipped if the dependency is not used in the final import map.
477
544
 
545
+ #### Dense Externals
546
+
547
+ The `denseExternals` feature flag reshapes the `shared` array in `remoteEntry.json` so that all entrypoints of a shared external (its primary import plus every secondary and shared mapping) are grouped under a single object:
548
+
549
+ ```js
550
+ module.exports = withNativeFederation({
551
+ shared: {
552
+ ...shareAll({
553
+ singleton: true,
554
+ strictVersion: true,
555
+ requiredVersion: 'auto',
556
+ }),
557
+ },
558
+ features: {
559
+ denseExternals: true,
560
+ },
561
+ });
562
+ ```
563
+
564
+ When enabled, instead of one flat entry per entrypoint, each package becomes one object whose `entries` map keys the full import name to its output file (e.g. `{ "@angular/common": "...", "@angular/common/http": "..." }`). Entrypoints whose sharing metadata (`singleton`, `strictVersion`, `requiredVersion`, `version`, `shareScope`) diverges are split into separate groups. Bundler chunks stay flat, and `importmap.json` is unaffected. The flag is opt-in and fully backward compatible: the runtime auto-detects each entry by shape, so old and new `remoteEntry.json` both load.
565
+
478
566
  ### Configuring Remotes
479
567
 
480
568
  When configuring a remote, you can expose files that can be loaded into the shell at runtime:
@@ -597,11 +685,11 @@ const { loadRemoteModule } = await initFederation(manifest, {
597
685
 
598
686
  ## React and Other CommonJS Libs
599
687
 
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.
688
+ 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
689
 
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.
690
+ 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
691
 
604
- One of them is `fileReplacemnts`:
692
+ One of them is `fileReplacements`:
605
693
 
606
694
  ```javascript
607
695
  import { reactReplacements } from '@softarc/native-federation-esbuild/src/lib/react-replacements';
package/dist/config.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './lib/domain/config/index.js';
2
2
  export { withNativeFederation } from './lib/config/with-native-federation.js';
3
- export { findRootTsConfigJson, share, shareAll, setInferVersion, } from './lib/config/share-utils.js';
3
+ export { findRootTsConfigJson } from './lib/config/project-paths.js';
4
+ export { setInferVersion } from './lib/config/version-lookup.js';
5
+ export { share, shareAll, fromPackageJson } from './lib/config/share-utils.js';
4
6
  export { DEFAULT_SKIP_LIST } from './lib/config/default-skip-list.js';
package/dist/config.js CHANGED
@@ -1,15 +1,13 @@
1
1
  export * from "./lib/domain/config/index.js";
2
2
  import { withNativeFederation } from "./lib/config/with-native-federation.js";
3
- import {
4
- findRootTsConfigJson,
5
- share,
6
- shareAll,
7
- setInferVersion
8
- } from "./lib/config/share-utils.js";
3
+ import { findRootTsConfigJson } from "./lib/config/project-paths.js";
4
+ import { setInferVersion } from "./lib/config/version-lookup.js";
5
+ import { share, shareAll, fromPackageJson } from "./lib/config/share-utils.js";
9
6
  import { DEFAULT_SKIP_LIST } from "./lib/config/default-skip-list.js";
10
7
  export {
11
8
  DEFAULT_SKIP_LIST,
12
9
  findRootTsConfigJson,
10
+ fromPackageJson,
13
11
  setInferVersion,
14
12
  share,
15
13
  shareAll,
@@ -10,5 +10,6 @@ export type { NormalizedFederationConfig } from './lib/domain/config/federation-
10
10
  export { getDefaultCachePath, getChecksum } from './lib/core/cache/cache-persistence.js';
11
11
  export { isESMExport, type ExportCondition, type ExportEntry, } from './lib/utils/package/package-info.js';
12
12
  export { isInSkipList, prepareSkipList } from './lib/config/default-skip-list.js';
13
- export type { NfFileWatcher, NfFileWatcherOptions } from './lib/domain/utils/file-watcher.contract.js';
13
+ export { densifyExternals } from './lib/core/output/densify-externals.js';
14
+ export type { NfFileWatcher, NfFileWatcherOptions, } from './lib/domain/utils/file-watcher.contract.js';
14
15
  export { syncNfFileWatcher, createNfWatcher } from './lib/utils/file-watcher.js';
package/dist/internal.js CHANGED
@@ -9,10 +9,12 @@ import {
9
9
  isESMExport
10
10
  } from "./lib/utils/package/package-info.js";
11
11
  import { isInSkipList, prepareSkipList } from "./lib/config/default-skip-list.js";
12
+ import { densifyExternals } from "./lib/core/output/densify-externals.js";
12
13
  import { syncNfFileWatcher, createNfWatcher } from "./lib/utils/file-watcher.js";
13
14
  export {
14
15
  RebuildQueue,
15
16
  createNfWatcher,
17
+ densifyExternals,
16
18
  getChecksum,
17
19
  getDefaultCachePath,
18
20
  hashFile,
@@ -0,0 +1,5 @@
1
+ import type { FileReaderPort } from '../domain/utils/io-port.contract.js';
2
+ export declare function findRootTsConfigJson(): string;
3
+ export declare function findRootTsConfigJsonCore(io: FileReaderPort): string;
4
+ export declare function findPackageJson(io: FileReaderPort, folder: string): string;
5
+ export declare function inferProjectPath(projectPath: string | undefined): string;
@@ -0,0 +1,49 @@
1
+ import * as path from "path";
2
+ import { cwd } from "process";
3
+ import { getConfigContext } from "./configuration-context.js";
4
+ import { nodeIo } from "../utils/io/node-io-adapter.js";
5
+ function findRootTsConfigJson() {
6
+ return findRootTsConfigJsonCore(nodeIo);
7
+ }
8
+ function findRootTsConfigJsonCore(io) {
9
+ const packageJson = findPackageJson(io, cwd());
10
+ const projectRoot = path.dirname(packageJson);
11
+ const tsConfigBaseJson = path.join(projectRoot, "tsconfig.base.json");
12
+ const tsConfigJson = path.join(projectRoot, "tsconfig.json");
13
+ if (io.exists(tsConfigBaseJson)) {
14
+ return tsConfigBaseJson;
15
+ } else if (io.exists(tsConfigJson)) {
16
+ return tsConfigJson;
17
+ }
18
+ throw new Error("Neither a tsconfig.json nor a tsconfig.base.json was found");
19
+ }
20
+ function findPackageJson(io, folder) {
21
+ while (!io.exists(path.join(folder, "package.json")) && path.dirname(folder) !== folder) {
22
+ folder = path.dirname(folder);
23
+ }
24
+ const filePath = path.join(folder, "package.json");
25
+ if (io.exists(filePath)) {
26
+ return filePath;
27
+ }
28
+ throw new Error(
29
+ "no package.json found. Searched the following folder and all parents: " + folder
30
+ );
31
+ }
32
+ function inferProjectPath(projectPath) {
33
+ if (!projectPath && getConfigContext().packageJson) {
34
+ projectPath = path.dirname(getConfigContext().packageJson || "");
35
+ }
36
+ if (!projectPath && getConfigContext().workspaceRoot) {
37
+ projectPath = getConfigContext().workspaceRoot || "";
38
+ }
39
+ if (!projectPath) {
40
+ projectPath = cwd();
41
+ }
42
+ return projectPath;
43
+ }
44
+ export {
45
+ findPackageJson,
46
+ findRootTsConfigJson,
47
+ findRootTsConfigJsonCore,
48
+ inferProjectPath
49
+ };
@@ -0,0 +1,5 @@
1
+ import { type PreparedSkipList } from '../domain/config/skip-list.contract.js';
2
+ import type { FileReaderPort, GlobPort } from '../domain/utils/io-port.contract.js';
3
+ import type { ExternalConfig, IncludeSecondariesOptions, SharedExternalsConfig } from '../domain/config/external-config.contract.js';
4
+ export declare function getSecondaries(io: FileReaderPort & GlobPort, includeSecondaries: IncludeSecondariesOptions, libPath: string, key: string, shareObject: ExternalConfig, preparedSkipList: PreparedSkipList): SharedExternalsConfig | null;
5
+ export declare function addSecondaries(secondaries: Record<string, ExternalConfig>, result: Record<string, ExternalConfig>): void;
@@ -0,0 +1,186 @@
1
+ import * as path from "path";
2
+ import { isInSkipList } from "./default-skip-list.js";
3
+ import { resolvePackageJsonExportsWildcardCore } from "../utils/package/resolve-wildcard-keys.js";
4
+ import { logger } from "../utils/logger.js";
5
+ function _findSecondaries(io, libPath, excludes, shareObject, acc, preparedSkipList) {
6
+ const files = io.readDir(libPath);
7
+ const secondaries = files.map((f) => path.join(libPath, f)).filter((f) => io.isDirectory(f) && !f.endsWith("node_modules"));
8
+ for (const s of secondaries) {
9
+ if (io.exists(path.join(s, "package.json"))) {
10
+ const secondaryLibName = s.replace(/\\/g, "/").replace(/^.*node_modules[/]/, "");
11
+ const inCustomSkipList = excludes.some(
12
+ (e) => e === secondaryLibName || e.endsWith("*") && secondaryLibName.startsWith(e.slice(0, -1))
13
+ );
14
+ if (inCustomSkipList) continue;
15
+ if (isInSkipList(secondaryLibName, preparedSkipList)) {
16
+ continue;
17
+ }
18
+ acc[secondaryLibName] = { ...shareObject };
19
+ }
20
+ _findSecondaries(io, s, excludes, shareObject, acc, preparedSkipList);
21
+ }
22
+ }
23
+ function findSecondaries(io, libPath, excludes, shareObject, preparedSkipList) {
24
+ const acc = {};
25
+ _findSecondaries(io, libPath, excludes, shareObject, acc, preparedSkipList);
26
+ return acc;
27
+ }
28
+ function getSecondaries(io, includeSecondaries, libPath, key, shareObject, preparedSkipList) {
29
+ let exclude = [];
30
+ let resolveGlob = false;
31
+ if (typeof includeSecondaries === "object") {
32
+ if (includeSecondaries.skip) {
33
+ if (Array.isArray(includeSecondaries.skip)) {
34
+ exclude = includeSecondaries.skip;
35
+ } else if (typeof includeSecondaries.skip === "string") {
36
+ exclude = [includeSecondaries.skip];
37
+ }
38
+ }
39
+ resolveGlob = !!includeSecondaries.resolveGlob;
40
+ }
41
+ if (!io.exists(libPath)) {
42
+ return {};
43
+ }
44
+ const configured = readConfiguredSecondaries(
45
+ io,
46
+ key,
47
+ libPath,
48
+ exclude,
49
+ shareObject,
50
+ preparedSkipList,
51
+ resolveGlob
52
+ );
53
+ if (configured) {
54
+ return configured;
55
+ }
56
+ const secondaries = findSecondaries(io, libPath, exclude, shareObject, preparedSkipList);
57
+ return secondaries;
58
+ }
59
+ function readConfiguredSecondaries(io, parent, libPath, exclude, shareObject, preparedSkipList, resolveGlob) {
60
+ const libPackageJson = path.join(libPath, "package.json");
61
+ if (!io.exists(libPackageJson)) {
62
+ return null;
63
+ }
64
+ const packageJson = JSON.parse(io.readText(libPackageJson));
65
+ const version = packageJson["version"];
66
+ const esm = packageJson["type"] === "module";
67
+ const exports = packageJson["exports"];
68
+ if (!exports) {
69
+ return null;
70
+ }
71
+ const keys = Object.keys(exports).filter(
72
+ (key) => key !== "." && key !== "./package.json" && key.startsWith("./") && (exports[key]?.["default"] || exports[key]?.["import"] || typeof exports[key] === "string")
73
+ );
74
+ const result = {};
75
+ const discoveredFiles = /* @__PURE__ */ new Set();
76
+ for (const key of keys) {
77
+ const secondaryName = path.join(parent, key).replace(/\\/g, "/");
78
+ const inCustomSkipList = exclude.some(
79
+ (e) => e === secondaryName || e.endsWith("*") && secondaryName.startsWith(e.slice(0, -1))
80
+ );
81
+ if (inCustomSkipList) continue;
82
+ if (isInSkipList(secondaryName, preparedSkipList)) {
83
+ continue;
84
+ }
85
+ const entry = getDefaultEntry(exports, key);
86
+ if (typeof entry !== "string") {
87
+ logger.warn("No entry point found for " + secondaryName);
88
+ continue;
89
+ }
90
+ if (!key.includes("*") && !isJsFile(entry)) {
91
+ continue;
92
+ }
93
+ const items = resolveGlobSecondaries(
94
+ io,
95
+ key,
96
+ libPath,
97
+ parent,
98
+ secondaryName,
99
+ entry,
100
+ { discovered: discoveredFiles, skip: exclude },
101
+ resolveGlob
102
+ );
103
+ items.forEach((e) => discoveredFiles.add(typeof e === "string" ? e : e.value));
104
+ for (const item of items) {
105
+ if (typeof item === "object") {
106
+ result[item.key] = {
107
+ ...shareObject,
108
+ packageInfo: {
109
+ entryPoint: item.value,
110
+ version: shareObject.version ?? version,
111
+ esm
112
+ }
113
+ };
114
+ } else {
115
+ result[item] = {
116
+ ...shareObject
117
+ };
118
+ }
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+ function resolveGlobSecondaries(io, key, libPath, parent, secondaryName, entry, excludes, resolveGlob) {
124
+ let items = [];
125
+ if (key.includes("*")) {
126
+ if (!resolveGlob) return items;
127
+ const expanded = resolvePackageJsonExportsWildcardCore(io, key, entry, libPath);
128
+ items = expanded.map((e) => ({
129
+ key: path.join(parent, e.key),
130
+ value: path.join(libPath, e.value)
131
+ })).filter((i) => {
132
+ if (!isJsFile(i.value)) {
133
+ return false;
134
+ }
135
+ if (excludes.skip.some(
136
+ (e) => e.endsWith("*") ? i.key.startsWith(e.slice(0, -1)) : e === i.key
137
+ )) {
138
+ return false;
139
+ }
140
+ if (excludes.discovered.has(i.value)) {
141
+ return false;
142
+ }
143
+ return true;
144
+ });
145
+ } else {
146
+ items = [secondaryName];
147
+ }
148
+ return items;
149
+ }
150
+ function isJsFile(file) {
151
+ return file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs");
152
+ }
153
+ function getDefaultEntry(exports, key) {
154
+ let entry = "";
155
+ if (typeof exports[key] === "string") {
156
+ entry = exports[key];
157
+ }
158
+ if (!entry) {
159
+ entry = exports[key]?.["default"];
160
+ if (typeof entry === "object") {
161
+ entry = entry["default"];
162
+ }
163
+ }
164
+ if (!entry) {
165
+ entry = exports[key]?.["import"];
166
+ if (typeof entry === "object") {
167
+ entry = entry["import"] ?? entry["default"];
168
+ }
169
+ }
170
+ if (!entry) {
171
+ entry = exports[key]?.["require"];
172
+ if (typeof entry === "object") {
173
+ entry = entry["require"] ?? entry["default"];
174
+ }
175
+ }
176
+ return entry;
177
+ }
178
+ function addSecondaries(secondaries, result) {
179
+ for (const key in secondaries) {
180
+ result[key] = secondaries[key];
181
+ }
182
+ }
183
+ export {
184
+ addSecondaries,
185
+ getSecondaries
186
+ };
@@ -1,20 +1,23 @@
1
- import { type SkipList, type PreparedSkipList } from '../domain/config/skip-list.contract.js';
1
+ import { type SkipList } 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';
5
- export declare function findRootTsConfigJson(): string;
6
- export declare function findRootTsConfigJsonCore(io: FileReaderPort): string;
7
- export declare function getSecondaries(io: FileReaderPort & GlobPort, includeSecondaries: IncludeSecondariesOptions, libPath: string, key: string, shareObject: ExternalConfig, preparedSkipList: PreparedSkipList): SharedExternalsConfig | null;
4
+ import type { ExternalConfig, ResolvedSharedExternalsConfig, ShareAllExternalsOptions, ShareExternalsOptions } from '../domain/config/external-config.contract.js';
5
+ export declare const fromPackageJson: (baseCfg: ShareAllExternalsOptions, projectPath?: string) => {
6
+ skip(externals: SkipList): /*elided*/ any;
7
+ override(externals: ShareExternalsOptions): /*elided*/ any;
8
+ patch(externals: string[], cfg: Partial<ExternalConfig>): /*elided*/ any;
9
+ get(): ResolvedSharedExternalsConfig;
10
+ };
8
11
  export declare function shareAll(config: ShareAllExternalsOptions, opts?: {
9
12
  skipList?: SkipList;
10
13
  projectPath?: string;
11
14
  overrides?: ShareExternalsOptions;
12
- }): ShareExternalsOptions | null;
15
+ }): ResolvedSharedExternalsConfig;
13
16
  export declare function shareAllCore(io: FileReaderPort & GlobPort, config: ShareAllExternalsOptions, opts?: {
14
17
  skipList?: SkipList;
15
18
  projectPath?: string;
16
19
  overrides?: ShareExternalsOptions;
17
- }, repo?: PackageJsonRepository): ShareExternalsOptions | null;
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;
20
+ patchList?: Record<string, Partial<ExternalConfig>>;
21
+ }, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
22
+ export declare function share(configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList): ResolvedSharedExternalsConfig;
23
+ export declare function shareCore(io: FileReaderPort & GlobPort, configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;