@softarc/native-federation 4.2.1 → 4.3.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
@@ -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`:
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 a monorepo scenario and when doing some experiments / troubleshooting.
172
-
173
- You can also add overrides to `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
@@ -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:
@@ -526,7 +614,7 @@ For a zero-build integration, declare your remotes in a manifest and include the
526
614
  <script>
527
615
  window.addEventListener(
528
616
  'mfe-loader-available',
529
- (e) => {
617
+ e => {
530
618
  e.detail.loadRemoteModule('team/mfe1', './Button');
531
619
  e.detail.loadRemoteModule('team/mfe2', './Header');
532
620
  },
@@ -535,7 +623,7 @@ For a zero-build integration, declare your remotes in a manifest and include the
535
623
  </script>
536
624
 
537
625
  <!-- Include the orchestrator -->
538
- <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.3.0/quickstart.mjs"></script>
626
+ <script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.5.1/quickstart.mjs"></script>
539
627
  ```
540
628
 
541
629
  The `mfe-loader-available` event signals that the orchestrator has fetched the
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,
@@ -0,0 +1,6 @@
1
+ export * from '../lib/utils/errors.js';
2
+ export { densifyExternals, toDenseSharedInfoFormat } from '../lib/core/output/densify-externals.js';
3
+ export { isInSkipList, prepareSkipList } from '../lib/config/default-skip-list.js';
4
+ export type { PathToImport } from '../lib/domain/utils/mapped-path.contract.js';
5
+ export type { NormalizedExternalConfig, NormalizedSharedExternalsConfig, } from '../lib/domain/config/external-config.contract.js';
6
+ export type { NormalizedFederationConfig } from '../lib/domain/config/federation-config.contract.js';
@@ -0,0 +1,9 @@
1
+ export * from "../lib/utils/errors.js";
2
+ import { densifyExternals, toDenseSharedInfoFormat } from "../lib/core/output/densify-externals.js";
3
+ import { isInSkipList, prepareSkipList } from "../lib/config/default-skip-list.js";
4
+ export {
5
+ densifyExternals,
6
+ isInSkipList,
7
+ prepareSkipList,
8
+ toDenseSharedInfoFormat
9
+ };
@@ -1,14 +1,15 @@
1
+ /**
2
+ * Internal API's of the native-federation builder.
3
+ *
4
+ * Might change between minors.
5
+ */
6
+ export * from './internal/browser.js';
1
7
  export * from './lib/core/build/build-result-map.js';
2
8
  export { hashFile } from './lib/utils/hash-file.js';
3
- export * from './lib/utils/errors.js';
4
9
  export { logger, setLogLevel } from './lib/utils/logger.js';
5
- export type { PathToImport } from './lib/domain/utils/mapped-path.contract.js';
6
10
  export { RebuildQueue, type TrackResult } from './lib/core/rebuild-queue.js';
7
11
  export { writeImportMap } from './lib/core/output/write-import-map.js';
8
- export type { NormalizedExternalConfig, NormalizedSharedExternalsConfig, } from './lib/domain/config/external-config.contract.js';
9
- export type { NormalizedFederationConfig } from './lib/domain/config/federation-config.contract.js';
10
12
  export { getDefaultCachePath, getChecksum } from './lib/core/cache/cache-persistence.js';
11
13
  export { isESMExport, type ExportCondition, type ExportEntry, } from './lib/utils/package/package-info.js';
12
- export { isInSkipList, prepareSkipList } from './lib/config/default-skip-list.js';
13
- export type { NfFileWatcher, NfFileWatcherOptions } from './lib/domain/utils/file-watcher.contract.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
@@ -1,6 +1,6 @@
1
+ export * from "./internal/browser.js";
1
2
  export * from "./lib/core/build/build-result-map.js";
2
3
  import { hashFile } from "./lib/utils/hash-file.js";
3
- export * from "./lib/utils/errors.js";
4
4
  import { logger, setLogLevel } from "./lib/utils/logger.js";
5
5
  import { RebuildQueue } from "./lib/core/rebuild-queue.js";
6
6
  import { writeImportMap } from "./lib/core/output/write-import-map.js";
@@ -8,7 +8,6 @@ import { getDefaultCachePath, getChecksum } from "./lib/core/cache/cache-persist
8
8
  import {
9
9
  isESMExport
10
10
  } from "./lib/utils/package/package-info.js";
11
- import { isInSkipList, prepareSkipList } from "./lib/config/default-skip-list.js";
12
11
  import { syncNfFileWatcher, createNfWatcher } from "./lib/utils/file-watcher.js";
13
12
  export {
14
13
  RebuildQueue,
@@ -17,9 +16,7 @@ export {
17
16
  getDefaultCachePath,
18
17
  hashFile,
19
18
  isESMExport,
20
- isInSkipList,
21
19
  logger,
22
- prepareSkipList,
23
20
  setLogLevel,
24
21
  syncNfFileWatcher,
25
22
  writeImportMap
@@ -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,10 +1,13 @@
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, ResolvedSharedExternalsConfig, 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;
@@ -14,7 +17,7 @@ export declare function shareAllCore(io: FileReaderPort & GlobPort, config: Shar
14
17
  skipList?: SkipList;
15
18
  projectPath?: string;
16
19
  overrides?: ShareExternalsOptions;
20
+ patchList?: Record<string, Partial<ExternalConfig>>;
17
21
  }, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
18
- export declare function setInferVersion(infer: boolean): void;
19
22
  export declare function share(configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList): ResolvedSharedExternalsConfig;
20
23
  export declare function shareCore(io: FileReaderPort & GlobPort, configuredShareObjects: ShareExternalsOptions, projectPath?: string, skipList?: SkipList, repo?: PackageJsonRepository): ResolvedSharedExternalsConfig;
@@ -1,240 +1,48 @@
1
1
  import * as path from "path";
2
- import { cwd } from "process";
3
2
  import { DEFAULT_SKIP_LIST, isInSkipList, prepareSkipList } from "./default-skip-list.js";
4
3
  import {
5
4
  sharedPackageJsonRepository,
6
5
  findDepPackageJson,
7
6
  getVersionMaps
8
7
  } from "../utils/package/package-info.js";
9
- import { getConfigContext } from "./configuration-context.js";
10
8
  import { logger } from "../utils/logger.js";
11
9
  import { nodeIo } from "../utils/io/node-io-adapter.js";
12
- import { resolvePackageJsonExportsWildcardCore } from "../utils/package/resolve-wildcard-keys.js";
13
- let inferVersion = false;
14
- function findRootTsConfigJson() {
15
- return findRootTsConfigJsonCore(nodeIo);
16
- }
17
- function findRootTsConfigJsonCore(io) {
18
- const packageJson = findPackageJson(io, cwd());
19
- const projectRoot = path.dirname(packageJson);
20
- const tsConfigBaseJson = path.join(projectRoot, "tsconfig.base.json");
21
- const tsConfigJson = path.join(projectRoot, "tsconfig.json");
22
- if (io.exists(tsConfigBaseJson)) {
23
- return tsConfigBaseJson;
24
- } else if (io.exists(tsConfigJson)) {
25
- return tsConfigJson;
26
- }
27
- throw new Error("Neither a tsconfig.json nor a tsconfig.base.json was found");
28
- }
29
- function findPackageJson(io, folder) {
30
- while (!io.exists(path.join(folder, "package.json")) && path.dirname(folder) !== folder) {
31
- folder = path.dirname(folder);
32
- }
33
- const filePath = path.join(folder, "package.json");
34
- if (io.exists(filePath)) {
35
- return filePath;
36
- }
37
- throw new Error(
38
- "no package.json found. Searched the following folder and all parents: " + folder
39
- );
40
- }
41
- function lookupVersion(key, workspaceRoot, repo) {
42
- const versionMaps = getVersionMaps(workspaceRoot, workspaceRoot, repo);
43
- for (const versionMap of versionMaps) {
44
- const version = lookupVersionInMap(key, versionMap);
45
- if (version) {
46
- return version;
47
- }
48
- }
49
- throw new Error(
50
- `Shared Dependency ${key} has requiredVersion:'auto'. However, this dependency is not found in your package.json`
51
- );
52
- }
53
- function lookupVersionInMap(key, versions) {
54
- const parts = key.split("/");
55
- if (parts.length >= 2 && parts[0].startsWith("@")) {
56
- key = parts[0] + "/" + parts[1];
57
- } else {
58
- key = parts[0];
59
- }
60
- if (!versions[key]) {
61
- return null;
62
- }
63
- return versions[key];
64
- }
65
- function _findSecondaries(io, libPath, excludes, shareObject, acc, preparedSkipList) {
66
- const files = io.readDir(libPath);
67
- const secondaries = files.map((f) => path.join(libPath, f)).filter((f) => io.isDirectory(f) && !f.endsWith("node_modules"));
68
- for (const s of secondaries) {
69
- if (io.exists(path.join(s, "package.json"))) {
70
- const secondaryLibName = s.replace(/\\/g, "/").replace(/^.*node_modules[/]/, "");
71
- const inCustomSkipList = excludes.some(
72
- (e) => e === secondaryLibName || e.endsWith("*") && secondaryLibName.startsWith(e.slice(0, -1))
73
- );
74
- if (inCustomSkipList) continue;
75
- if (isInSkipList(secondaryLibName, preparedSkipList)) {
76
- continue;
77
- }
78
- acc[secondaryLibName] = { ...shareObject };
79
- }
80
- _findSecondaries(io, s, excludes, shareObject, acc, preparedSkipList);
81
- }
82
- }
83
- function findSecondaries(io, libPath, excludes, shareObject, preparedSkipList) {
84
- const acc = {};
85
- _findSecondaries(io, libPath, excludes, shareObject, acc, preparedSkipList);
86
- return acc;
87
- }
88
- function getSecondaries(io, includeSecondaries, libPath, key, shareObject, preparedSkipList) {
89
- let exclude = [];
90
- let resolveGlob = false;
91
- if (typeof includeSecondaries === "object") {
92
- if (includeSecondaries.skip) {
93
- if (Array.isArray(includeSecondaries.skip)) {
94
- exclude = includeSecondaries.skip;
95
- } else if (typeof includeSecondaries.skip === "string") {
96
- exclude = [includeSecondaries.skip];
97
- }
98
- }
99
- resolveGlob = !!includeSecondaries.resolveGlob;
100
- }
101
- if (!io.exists(libPath)) {
102
- return {};
103
- }
104
- const configured = readConfiguredSecondaries(
105
- io,
106
- key,
107
- libPath,
108
- exclude,
109
- shareObject,
110
- preparedSkipList,
111
- resolveGlob
112
- );
113
- if (configured) {
114
- return configured;
115
- }
116
- const secondaries = findSecondaries(io, libPath, exclude, shareObject, preparedSkipList);
117
- return secondaries;
118
- }
119
- function readConfiguredSecondaries(io, parent, libPath, exclude, shareObject, preparedSkipList, resolveGlob) {
120
- const libPackageJson = path.join(libPath, "package.json");
121
- if (!io.exists(libPackageJson)) {
122
- return null;
123
- }
124
- const packageJson = JSON.parse(io.readText(libPackageJson));
125
- const version = packageJson["version"];
126
- const esm = packageJson["type"] === "module";
127
- const exports = packageJson["exports"];
128
- if (!exports) {
129
- return null;
130
- }
131
- const keys = Object.keys(exports).filter(
132
- (key) => key !== "." && key !== "./package.json" && key.startsWith("./") && (exports[key]?.["default"] || exports[key]?.["import"] || typeof exports[key] === "string")
133
- );
134
- const result = {};
135
- const discoveredFiles = /* @__PURE__ */ new Set();
136
- for (const key of keys) {
137
- const secondaryName = path.join(parent, key).replace(/\\/g, "/");
138
- const inCustomSkipList = exclude.some(
139
- (e) => e === secondaryName || e.endsWith("*") && secondaryName.startsWith(e.slice(0, -1))
140
- );
141
- if (inCustomSkipList) continue;
142
- if (isInSkipList(secondaryName, preparedSkipList)) {
143
- continue;
144
- }
145
- const entry = getDefaultEntry(exports, key);
146
- if (typeof entry !== "string") {
147
- console.log("No entry point found for " + secondaryName);
148
- continue;
149
- }
150
- if (!key.includes("*") && !isJsFile(entry)) {
151
- continue;
152
- }
153
- const items = resolveGlobSecondaries(
154
- io,
155
- key,
156
- libPath,
157
- parent,
158
- secondaryName,
159
- entry,
160
- { discovered: discoveredFiles, skip: exclude },
161
- resolveGlob
162
- );
163
- items.forEach((e) => discoveredFiles.add(typeof e === "string" ? e : e.value));
164
- for (const item of items) {
165
- if (typeof item === "object") {
166
- result[item.key] = {
167
- ...shareObject,
168
- packageInfo: {
169
- entryPoint: item.value,
170
- version: shareObject.version ?? version,
171
- esm
172
- }
10
+ import { findPackageJson, inferProjectPath } from "./project-paths.js";
11
+ import { isInferVersion, lookupVersion } from "./version-lookup.js";
12
+ import { addSecondaries, getSecondaries } from "./secondaries.js";
13
+ const fromPackageJson = (baseCfg, projectPath) => {
14
+ const skipList = [...DEFAULT_SKIP_LIST];
15
+ let overrides = {};
16
+ const patchList = {};
17
+ const builder = {
18
+ skip(externals) {
19
+ skipList.push(...externals);
20
+ return builder;
21
+ },
22
+ override(externals) {
23
+ overrides = { ...overrides, ...externals };
24
+ return builder;
25
+ },
26
+ patch(externals, cfg) {
27
+ externals.forEach((external) => {
28
+ patchList[external] = {
29
+ ...patchList[external] ?? {},
30
+ ...cfg
173
31
  };
174
- } else {
175
- result[item] = {
176
- ...shareObject
177
- };
178
- }
179
- }
180
- }
181
- return result;
182
- }
183
- function resolveGlobSecondaries(io, key, libPath, parent, secondaryName, entry, excludes, resolveGlob) {
184
- let items = [];
185
- if (key.includes("*")) {
186
- if (!resolveGlob) return items;
187
- const expanded = resolvePackageJsonExportsWildcardCore(io, key, entry, libPath);
188
- items = expanded.map((e) => ({
189
- key: path.join(parent, e.key),
190
- value: path.join(libPath, e.value)
191
- })).filter((i) => {
192
- if (!isJsFile(i.value)) {
193
- return false;
194
- }
195
- if (excludes.skip.some(
196
- (e) => e.endsWith("*") ? i.key.startsWith(e.slice(0, -1)) : e === i.key
197
- )) {
198
- return false;
199
- }
200
- if (excludes.discovered.has(i.value)) {
201
- return false;
202
- }
203
- return true;
204
- });
205
- } else {
206
- items = [secondaryName];
207
- }
208
- return items;
209
- }
210
- function isJsFile(file) {
211
- return file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs");
212
- }
213
- function getDefaultEntry(exports, key) {
214
- let entry = "";
215
- if (typeof exports[key] === "string") {
216
- entry = exports[key];
217
- }
218
- if (!entry) {
219
- entry = exports[key]?.["default"];
220
- if (typeof entry === "object") {
221
- entry = entry["default"];
222
- }
223
- }
224
- if (!entry) {
225
- entry = exports[key]?.["import"];
226
- if (typeof entry === "object") {
227
- entry = entry["import"] ?? entry["default"];
228
- }
229
- }
230
- if (!entry) {
231
- entry = exports[key]?.["require"];
232
- if (typeof entry === "object") {
233
- entry = entry["require"] ?? entry["default"];
32
+ });
33
+ return builder;
34
+ },
35
+ get() {
36
+ return shareAllCore(nodeIo, baseCfg, {
37
+ skipList,
38
+ projectPath,
39
+ overrides,
40
+ patchList
41
+ });
234
42
  }
235
- }
236
- return entry;
237
- }
43
+ };
44
+ return builder;
45
+ };
238
46
  function shareAll(config, opts = {}) {
239
47
  return shareAllCore(nodeIo, config, opts);
240
48
  }
@@ -251,32 +59,35 @@ function shareAllCore(io, config, opts = {}, repo = sharedPackageJsonRepository)
251
59
  if (!!opts.overrides && Object.keys(opts.overrides).some((o) => key.startsWith(o))) {
252
60
  continue;
253
61
  }
254
- const inferVersion2 = !config.requiredVersion || config.requiredVersion === "auto";
255
- const requiredVersion = inferVersion2 ? versions[key] : config.requiredVersion;
62
+ const inferVersion = !config.requiredVersion || config.requiredVersion === "auto";
63
+ const requiredVersion = inferVersion ? versions[key] : config.requiredVersion;
256
64
  if (!sharedExternals[key]) {
257
65
  sharedExternals[key] = { ...config, requiredVersion };
258
66
  }
259
67
  }
260
68
  }
69
+ const finalExternalList = applyPatchList(sharedExternals, opts.patchList, opts.overrides);
261
70
  return {
262
- ...shareCore(io, sharedExternals, opts.projectPath, skipList, repo),
71
+ ...shareCore(io, finalExternalList, opts.projectPath, skipList, repo),
263
72
  ...!opts.overrides ? {} : shareCore(io, opts.overrides, opts.projectPath, skipList, repo)
264
73
  };
265
74
  }
266
- function inferProjectPath(projectPath) {
267
- if (!projectPath && getConfigContext().packageJson) {
268
- projectPath = path.dirname(getConfigContext().packageJson || "");
269
- }
270
- if (!projectPath && getConfigContext().workspaceRoot) {
271
- projectPath = getConfigContext().workspaceRoot || "";
75
+ function applyPatchList(sharedExternals, patchList, overrides) {
76
+ if (!patchList) {
77
+ return sharedExternals;
272
78
  }
273
- if (!projectPath) {
274
- projectPath = cwd();
79
+ const result = { ...sharedExternals };
80
+ for (const [external, cfg] of Object.entries(patchList)) {
81
+ if (!result[external]) {
82
+ const shadowedByOverride = !!overrides && Object.keys(overrides).some((o) => external.startsWith(o));
83
+ logger.warn(
84
+ shadowedByOverride ? `Ignoring patch for '${external}': it is already configured via 'overrides' ('patch' and 'overrides' are mutually exclusive per external).` : `Ignoring patch for '${external}': it is not a shared external (unknown dependency or skipped).`
85
+ );
86
+ continue;
87
+ }
88
+ result[external] = { ...result[external], ...cfg };
275
89
  }
276
- return projectPath;
277
- }
278
- function setInferVersion(infer) {
279
- inferVersion = infer;
90
+ return result;
280
91
  }
281
92
  function share(configuredShareObjects, projectPath = "", skipList = DEFAULT_SKIP_LIST) {
282
93
  return shareCore(nodeIo, configuredShareObjects, projectPath, skipList);
@@ -287,11 +98,10 @@ function shareCore(io, configuredShareObjects, projectPath = "", skipList = DEFA
287
98
  const preparedSkipList = prepareSkipList(skipList);
288
99
  const shareObjects = { ...configuredShareObjects };
289
100
  const result = {};
290
- let includeSecondaries;
291
101
  for (const key in shareObjects) {
292
- includeSecondaries = false;
102
+ let includeSecondaries = false;
293
103
  const shareObject = shareObjects[key];
294
- if (shareObject.requiredVersion === "auto" || inferVersion && typeof shareObject.requiredVersion === "undefined" || shareObject.requiredVersion?.length < 1) {
104
+ if (shareObject.requiredVersion === "auto" || isInferVersion() && typeof shareObject.requiredVersion === "undefined" || (shareObject.requiredVersion?.length ?? 1) < 1) {
295
105
  const version = lookupVersion(key, projectPath, repo);
296
106
  shareObject.requiredVersion = version;
297
107
  shareObject.version = version.replace(/^\D*/, "");
@@ -302,7 +112,9 @@ function shareCore(io, configuredShareObjects, projectPath = "", skipList = DEFA
302
112
  if (shareObject.includeSecondaries) {
303
113
  includeSecondaries = shareObject.includeSecondaries;
304
114
  delete shareObject.includeSecondaries;
305
- if (includeSecondaries?.keepAll) shareObject.includeSecondaries = true;
115
+ if (typeof includeSecondaries === "object" && includeSecondaries.keepAll) {
116
+ shareObject.includeSecondaries = true;
117
+ }
306
118
  }
307
119
  result[key] = shareObject;
308
120
  if (includeSecondaries) {
@@ -327,16 +139,8 @@ function shareCore(io, configuredShareObjects, projectPath = "", skipList = DEFA
327
139
  }
328
140
  return result;
329
141
  }
330
- function addSecondaries(secondaries, result) {
331
- for (const key in secondaries) {
332
- result[key] = secondaries[key];
333
- }
334
- }
335
142
  export {
336
- findRootTsConfigJson,
337
- findRootTsConfigJsonCore,
338
- getSecondaries,
339
- setInferVersion,
143
+ fromPackageJson,
340
144
  share,
341
145
  shareAll,
342
146
  shareAllCore,
@@ -0,0 +1,4 @@
1
+ import type { PackageJsonRepository } from '../domain/utils/package-json.contract.js';
2
+ export declare function setInferVersion(infer: boolean): void;
3
+ export declare function isInferVersion(): boolean;
4
+ export declare function lookupVersion(key: string, workspaceRoot: string, repo: PackageJsonRepository): string;
@@ -0,0 +1,37 @@
1
+ import { getVersionMaps } from "../utils/package/package-info.js";
2
+ let inferVersion = false;
3
+ function setInferVersion(infer) {
4
+ inferVersion = infer;
5
+ }
6
+ function isInferVersion() {
7
+ return inferVersion;
8
+ }
9
+ function lookupVersion(key, workspaceRoot, repo) {
10
+ const versionMaps = getVersionMaps(workspaceRoot, workspaceRoot, repo);
11
+ for (const versionMap of versionMaps) {
12
+ const version = lookupVersionInMap(key, versionMap);
13
+ if (version) {
14
+ return version;
15
+ }
16
+ }
17
+ throw new Error(
18
+ `Shared Dependency ${key} has requiredVersion:'auto'. However, this dependency is not found in your package.json`
19
+ );
20
+ }
21
+ function lookupVersionInMap(key, versions) {
22
+ const parts = key.split("/");
23
+ if (parts.length >= 2 && parts[0].startsWith("@")) {
24
+ key = parts[0] + "/" + parts[1];
25
+ } else {
26
+ key = parts[0];
27
+ }
28
+ if (!versions[key]) {
29
+ return null;
30
+ }
31
+ return versions[key];
32
+ }
33
+ export {
34
+ isInferVersion,
35
+ lookupVersion,
36
+ setInferVersion
37
+ };
@@ -1,5 +1,6 @@
1
1
  import { getRawMappedPaths } from "./mapped-paths.js";
2
- import { shareAll, findRootTsConfigJson } from "./share-utils.js";
2
+ import { fromPackageJson } from "./share-utils.js";
3
+ import { findRootTsConfigJson } from "./project-paths.js";
3
4
  import { isInSkipList, prepareSkipList } from "./default-skip-list.js";
4
5
  import { logger } from "../utils/logger.js";
5
6
  function withNativeFederation(config) {
@@ -18,6 +19,7 @@ function withNativeFederation(config) {
18
19
  mappingVersion: config.features?.mappingVersion ?? true,
19
20
  ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? true,
20
21
  denseChunking: config.features?.denseChunking ?? false,
22
+ denseExternals: config.features?.denseExternals ?? false,
21
23
  integrityHashes: config.features?.integrityHashes ?? false
22
24
  },
23
25
  ...config.shareScope && { shareScope: config.shareScope }
@@ -35,12 +37,12 @@ function normalizeExposes(exposes) {
35
37
  }
36
38
  function normalizeShared(config, skip, chunks) {
37
39
  let result = {};
38
- const shared = config.shared ?? shareAll({
40
+ const shared = config.shared ?? fromPackageJson({
39
41
  singleton: true,
40
42
  strictVersion: true,
41
43
  requiredVersion: "auto",
42
44
  platform: "browser"
43
- });
45
+ }).get();
44
46
  result = Object.keys(shared).reduce((acc, cur) => {
45
47
  const key = cur.replace(/\\/g, "/");
46
48
  const sharedConfig = shared[cur];
@@ -60,7 +62,8 @@ function normalizeShared(config, skip, chunks) {
60
62
  packageInfo: sharedConfig.packageInfo,
61
63
  platform: sharedConfig.platform ?? config.platform ?? "browser",
62
64
  build: sharedConfig.build ?? "default",
63
- ...sharedConfig.shareScope && { shareScope: sharedConfig.shareScope }
65
+ ...sharedConfig.shareScope && { shareScope: sharedConfig.shareScope },
66
+ ...sharedConfig.pool && { pool: sharedConfig.pool }
64
67
  };
65
68
  return {
66
69
  ...acc,
@@ -4,10 +4,11 @@ import {
4
4
  describeSharedMappings
5
5
  } from "./bundle-exposed-and-mappings.js";
6
6
  import { bundleShared } from "./bundle-shared.js";
7
+ import { densifyExternals } from "../output/densify-externals.js";
7
8
  import { writeFederationInfo } from "../output/write-federation-info.js";
8
9
  import { writeImportMap } from "../output/write-import-map.js";
9
10
  import { logger } from "../../utils/logger.js";
10
- import { normalizePackageName } from "../../utils/normalize.js";
11
+ import { inferPackageFromSecondary, normalizePackageName } from "../../utils/normalize.js";
11
12
  import { AbortedError } from "../../utils/errors.js";
12
13
  import { addExternalsToCache } from "../cache/federation-cache.js";
13
14
  import path from "path";
@@ -97,10 +98,11 @@ async function buildForFederation(config, fedOptions, externals, signal) {
97
98
  if (!external.shareScope) external.shareScope = config.shareScope;
98
99
  });
99
100
  }
101
+ const shared = config.features.denseExternals ? densifyExternals(sharedExternals) : sharedExternals;
100
102
  const buildNotificationsEndpoint = fedOptions.buildNotifications?.enable && fedOptions.dev ? fedOptions.buildNotifications?.endpoint : void 0;
101
103
  const federationInfo = {
102
104
  name: config.name,
103
- shared: sharedExternals,
105
+ shared,
104
106
  exposes: exposedInfo,
105
107
  buildNotificationsEndpoint
106
108
  };
@@ -120,13 +122,6 @@ async function buildForFederation(config, fedOptions, externals, signal) {
120
122
  writeImportMap(fedOptions.federationCache, fedOptions, federationInfo.integrity);
121
123
  return federationInfo;
122
124
  }
123
- function inferPackageFromSecondary(secondary) {
124
- const parts = secondary.split("/");
125
- if (secondary.startsWith("@") && parts.length >= 2) {
126
- return parts[0] + "/" + parts[1];
127
- }
128
- return parts[0];
129
- }
130
125
  async function bundleSeparatePackages(separateBrowser, externals, config, fedOptions, buildOptions) {
131
126
  const groupedByPackage = {};
132
127
  for (const [key, shared] of Object.entries(separateBrowser)) {
@@ -193,13 +193,8 @@ function buildResult(packageInfos, sharedBundles, outFileNames) {
193
193
  singleton: shared?.singleton,
194
194
  strictVersion: shared?.strictVersion,
195
195
  version: pi.version,
196
- ...shared?.shareScope && { shareScope: shared.shareScope }
197
- // TODO: Decide whether/when we need debug infos
198
- // dev: !fedOptions.dev
199
- // ? undefined
200
- // : {
201
- // entryPoint: normalize(pi.entryPoint),
202
- // },
196
+ ...shared?.shareScope && { shareScope: shared.shareScope },
197
+ ...shared?.pool && { pool: shared.pool }
203
198
  };
204
199
  });
205
200
  }
@@ -212,14 +207,12 @@ function addChunksToResult(chunks, result) {
212
207
  result.push({
213
208
  singleton: false,
214
209
  strictVersion: false,
215
- // Here, the version does not matter because
210
+ // Here, the version, singleton and strictversion
211
+ // do not matter because
216
212
  // a) a chunk split off by the bundler does
217
213
  // not have a version and b) it gets a hash
218
214
  // code as part of the file name to be unique
219
215
  // when requested via a _versioned_ package.
220
- //
221
- // For the same reason, we don't need to
222
- // take care of singleton and strictVersion.
223
216
  version: "0.0.0",
224
217
  requiredVersion: "0.0.0",
225
218
  packageName: toChunkImport(fileName),
@@ -0,0 +1,9 @@
1
+ import type { SharedInfo, DenseSharedInfo } from '../../domain/core/federation-info.contract.js';
2
+ /**
3
+ * Groups a flat `shared` array into {@link DenseSharedInfo} objects: one per shared external,
4
+ * with an `entries` map from each import name to its output file. Entries sharing a parent
5
+ * package but with differing metadata split into separate groups. Bundler chunks and
6
+ * already-dense entries pass through unchanged.
7
+ */
8
+ export declare function densifyExternals(shared: Array<SharedInfo | DenseSharedInfo>): Array<SharedInfo | DenseSharedInfo>;
9
+ export declare function toDenseSharedInfoFormat(shared: Array<SharedInfo | DenseSharedInfo>): DenseSharedInfo[];
@@ -0,0 +1,65 @@
1
+ import { CHUNK_PREFIX } from "../../domain/core/chunk.js";
2
+ import { inferPackageFromSecondary } from "../../utils/normalize.js";
3
+ function isDense(entry) {
4
+ return "entries" in entry;
5
+ }
6
+ function isFlatChunk(entry) {
7
+ return entry.packageName.startsWith(CHUNK_PREFIX);
8
+ }
9
+ function densifyExternals(shared) {
10
+ const result = [];
11
+ const groupIndex = /* @__PURE__ */ new Map();
12
+ for (const entry of shared) {
13
+ if (isDense(entry)) {
14
+ result.push(entry);
15
+ continue;
16
+ }
17
+ if (isFlatChunk(entry)) {
18
+ const { outFileName, ...rest } = entry;
19
+ result.push({ ...rest, entries: { [entry.packageName]: outFileName } });
20
+ continue;
21
+ }
22
+ const parent = inferPackageFromSecondary(entry.packageName);
23
+ const sig = JSON.stringify({
24
+ singleton: entry.singleton,
25
+ strictVersion: entry.strictVersion,
26
+ requiredVersion: entry.requiredVersion,
27
+ version: entry.version,
28
+ shareScope: entry.shareScope
29
+ });
30
+ const key = parent + " " + sig;
31
+ const existing = groupIndex.get(key);
32
+ if (existing === void 0) {
33
+ const dense = {
34
+ singleton: entry.singleton,
35
+ strictVersion: entry.strictVersion,
36
+ requiredVersion: entry.requiredVersion,
37
+ packageName: parent,
38
+ entries: { [entry.packageName]: entry.outFileName }
39
+ };
40
+ if (entry.version !== void 0) dense.version = entry.version;
41
+ if (entry.shareScope !== void 0) dense.shareScope = entry.shareScope;
42
+ if (entry.bundle !== void 0) dense.bundle = entry.bundle;
43
+ if (entry.dev !== void 0) dense.dev = entry.dev;
44
+ groupIndex.set(key, result.length);
45
+ result.push(dense);
46
+ } else {
47
+ result[existing].entries[entry.packageName] = entry.outFileName;
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ function toDenseSharedInfoFormat(shared) {
53
+ return shared.map((external) => {
54
+ if ("entries" in external) return external;
55
+ const { outFileName, ...baseSharedInfoProps } = external;
56
+ return {
57
+ ...baseSharedInfoProps,
58
+ entries: { [external.packageName]: outFileName }
59
+ };
60
+ });
61
+ }
62
+ export {
63
+ densifyExternals,
64
+ toDenseSharedInfoFormat
65
+ };
@@ -6,7 +6,7 @@ function writeFederationInfoCore(io, federationInfo, fedOptions) {
6
6
  fedOptions.outputPath,
7
7
  "remoteEntry.json"
8
8
  );
9
- io.writeText(metaDataPath, JSON.stringify(federationInfo, null, 2));
9
+ io.writeText(metaDataPath, JSON.stringify({ $version: "v4", ...federationInfo }, null, 2));
10
10
  }
11
11
  function writeFederationInfo(federationInfo, fedOptions) {
12
12
  writeFederationInfoCore(nodeIo, federationInfo, fedOptions);
@@ -11,6 +11,7 @@ export interface ExternalConfig {
11
11
  includeSecondaries?: IncludeSecondariesOptions;
12
12
  platform?: 'browser' | 'node';
13
13
  build?: 'separate' | 'package';
14
+ pool?: string;
14
15
  chunks?: boolean;
15
16
  shareScope?: string;
16
17
  packageInfo?: {
@@ -26,6 +27,7 @@ export interface NormalizedExternalConfig {
26
27
  version?: string;
27
28
  includeSecondaries?: boolean;
28
29
  shareScope?: string;
30
+ pool?: string;
29
31
  chunks: boolean;
30
32
  platform: 'browser' | 'node';
31
33
  build: 'default' | 'separate' | 'package';
@@ -19,6 +19,7 @@ export interface FederationConfig {
19
19
  mappingVersion?: boolean;
20
20
  ignoreUnusedDeps?: boolean;
21
21
  denseChunking?: boolean;
22
+ denseExternals?: boolean;
22
23
  integrityHashes?: boolean;
23
24
  };
24
25
  }
@@ -36,6 +37,7 @@ export interface NormalizedFederationConfig {
36
37
  mappingVersion: boolean;
37
38
  ignoreUnusedDeps: boolean;
38
39
  denseChunking: boolean;
40
+ denseExternals: boolean;
39
41
  integrityHashes: boolean;
40
42
  };
41
43
  }
@@ -1,11 +1,15 @@
1
1
  export interface FederationInfo {
2
+ $version?: string;
2
3
  name: string;
3
4
  exposes: ExposesInfo[];
4
- shared: SharedInfo[];
5
+ shared: Array<SharedInfo | DenseSharedInfo>;
5
6
  chunks?: Record<string, string[]>;
6
7
  integrity?: IntegrityMap;
7
8
  buildNotificationsEndpoint?: string;
8
9
  }
10
+ export type DenseSharedInfo = Omit<SharedInfo, 'outFileName'> & {
11
+ entries: Record<string, string>;
12
+ };
9
13
  export type SharedInfo = {
10
14
  singleton: boolean;
11
15
  strictVersion: boolean;
@@ -13,6 +17,7 @@ export type SharedInfo = {
13
17
  version?: string;
14
18
  packageName: string;
15
19
  shareScope?: string;
20
+ pool?: string;
16
21
  bundle?: string;
17
22
  outFileName: string;
18
23
  dev?: {
@@ -1,4 +1,4 @@
1
- export type { SharedInfo, FederationInfo, ExposesInfo, ArtifactInfo, ChunkInfo, IntegrityMap, } from './federation-info.contract.js';
1
+ export type { SharedInfo, DenseSharedInfo, FederationInfo, ExposesInfo, ArtifactInfo, ChunkInfo, IntegrityMap, } from './federation-info.contract.js';
2
2
  export { type BuildNotificationOptions, BuildNotificationType, } from './build-notification-options.contract.js';
3
3
  export type { FederationOptions, NormalizedFederationOptions, } from './federation-options.contract.js';
4
4
  export type { EntryPoint, NFBuildAdapterOptions, NFBuildAdapter, NFBuildAdapterResult, NFBuildAdapterContext, } from './build-adapter.contract.js';
@@ -1,2 +1,3 @@
1
1
  export declare function normalize(path: string, trailingSlash?: boolean): string;
2
2
  export declare function normalizePackageName(fileName: string): string;
3
+ export declare function inferPackageFromSecondary(secondary: string): string;
@@ -16,7 +16,15 @@ function normalizePackageName(fileName) {
16
16
  const sanitized = fileName.replace(/[^A-Za-z0-9]/g, "_");
17
17
  return sanitized.startsWith("_") ? sanitized.slice(1) : sanitized;
18
18
  }
19
+ function inferPackageFromSecondary(secondary) {
20
+ const parts = secondary.split("/");
21
+ if (secondary.startsWith("@") && parts.length >= 2) {
22
+ return parts[0] + "/" + parts[1];
23
+ }
24
+ return parts[0];
25
+ }
19
26
  export {
27
+ inferPackageFromSecondary,
20
28
  normalize,
21
29
  normalizePackageName
22
30
  };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@softarc/native-federation",
3
- "version": "4.2.1",
3
+ "version": "4.3.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
+ "packageManager": "pnpm@11.11.0",
6
7
  "scripts": {
7
8
  "build": "node esbuild.config.mjs && tsc -p tsconfig.build.json",
8
9
  "lint": "eslint src",
@@ -55,6 +56,11 @@
55
56
  "types": "./dist/internal.d.ts",
56
57
  "import": "./dist/internal.js",
57
58
  "default": "./dist/internal.js"
59
+ },
60
+ "./internal/browser": {
61
+ "types": "./dist/internal/browser.d.ts",
62
+ "import": "./dist/internal/browser.js",
63
+ "default": "./dist/internal/browser.js"
58
64
  }
59
65
  },
60
66
  "main": "./dist/index.js",