@softarc/native-federation 4.2.1 → 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 +94 -6
- package/dist/config.d.ts +3 -1
- package/dist/config.js +4 -6
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -0
- package/dist/lib/config/project-paths.d.ts +5 -0
- package/dist/lib/config/project-paths.js +49 -0
- package/dist/lib/config/secondaries.d.ts +5 -0
- package/dist/lib/config/secondaries.js +186 -0
- package/dist/lib/config/share-utils.d.ts +9 -6
- package/dist/lib/config/share-utils.js +58 -254
- package/dist/lib/config/version-lookup.d.ts +4 -0
- package/dist/lib/config/version-lookup.js +37 -0
- package/dist/lib/config/with-native-federation.js +7 -4
- package/dist/lib/core/build/build-for-federation.js +4 -9
- package/dist/lib/core/build/bundle-shared.js +4 -11
- package/dist/lib/core/output/densify-externals.d.ts +8 -0
- package/dist/lib/core/output/densify-externals.js +49 -0
- package/dist/lib/domain/config/external-config.contract.d.ts +2 -0
- package/dist/lib/domain/config/federation-config.contract.d.ts +2 -0
- package/dist/lib/domain/core/federation-info.contract.d.ts +5 -1
- package/dist/lib/domain/core/index.d.ts +1 -1
- package/dist/lib/utils/normalize.d.ts +1 -0
- package/dist/lib/utils/normalize.js +8 -0
- package/package.json +1 -1
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 `
|
|
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
|
-
|
|
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:
|
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
|
|
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
|
-
|
|
5
|
-
|
|
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,
|
package/dist/internal.d.ts
CHANGED
|
@@ -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
|
|
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,10 +1,13 @@
|
|
|
1
|
-
import { type SkipList
|
|
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,
|
|
5
|
-
export declare
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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
|
|
255
|
-
const 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,
|
|
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
|
|
267
|
-
if (!
|
|
268
|
-
|
|
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
|
-
|
|
274
|
-
|
|
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
|
|
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" ||
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 ??
|
|
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
|
|
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
|
-
|
|
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
|
|
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,8 @@
|
|
|
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>;
|
|
@@ -0,0 +1,49 @@
|
|
|
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 isChunk(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) || isChunk(entry)) {
|
|
14
|
+
result.push(entry);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const parent = inferPackageFromSecondary(entry.packageName);
|
|
18
|
+
const sig = JSON.stringify({
|
|
19
|
+
singleton: entry.singleton,
|
|
20
|
+
strictVersion: entry.strictVersion,
|
|
21
|
+
requiredVersion: entry.requiredVersion,
|
|
22
|
+
version: entry.version,
|
|
23
|
+
shareScope: entry.shareScope
|
|
24
|
+
});
|
|
25
|
+
const key = parent + " " + sig;
|
|
26
|
+
const existing = groupIndex.get(key);
|
|
27
|
+
if (existing === void 0) {
|
|
28
|
+
const dense = {
|
|
29
|
+
singleton: entry.singleton,
|
|
30
|
+
strictVersion: entry.strictVersion,
|
|
31
|
+
requiredVersion: entry.requiredVersion,
|
|
32
|
+
packageName: parent,
|
|
33
|
+
entries: { [entry.packageName]: entry.outFileName }
|
|
34
|
+
};
|
|
35
|
+
if (entry.version !== void 0) dense.version = entry.version;
|
|
36
|
+
if (entry.shareScope !== void 0) dense.shareScope = entry.shareScope;
|
|
37
|
+
if (entry.bundle !== void 0) dense.bundle = entry.bundle;
|
|
38
|
+
if (entry.dev !== void 0) dense.dev = entry.dev;
|
|
39
|
+
groupIndex.set(key, result.length);
|
|
40
|
+
result.push(dense);
|
|
41
|
+
} else {
|
|
42
|
+
result[existing].entries[entry.packageName] = entry.outFileName;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
densifyExternals
|
|
49
|
+
};
|
|
@@ -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,14 @@
|
|
|
1
1
|
export interface FederationInfo {
|
|
2
2
|
name: string;
|
|
3
3
|
exposes: ExposesInfo[];
|
|
4
|
-
shared: SharedInfo
|
|
4
|
+
shared: Array<SharedInfo | DenseSharedInfo>;
|
|
5
5
|
chunks?: Record<string, string[]>;
|
|
6
6
|
integrity?: IntegrityMap;
|
|
7
7
|
buildNotificationsEndpoint?: string;
|
|
8
8
|
}
|
|
9
|
+
export type DenseSharedInfo = Omit<SharedInfo, 'outFileName'> & {
|
|
10
|
+
entries: Record<string, string>;
|
|
11
|
+
};
|
|
9
12
|
export type SharedInfo = {
|
|
10
13
|
singleton: boolean;
|
|
11
14
|
strictVersion: boolean;
|
|
@@ -13,6 +16,7 @@ export type SharedInfo = {
|
|
|
13
16
|
version?: string;
|
|
14
17
|
packageName: string;
|
|
15
18
|
shareScope?: string;
|
|
19
|
+
pool?: string;
|
|
16
20
|
bundle?: string;
|
|
17
21
|
outFileName: string;
|
|
18
22
|
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';
|
|
@@ -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
|
};
|