@softarc/native-federation 4.3.2 → 4.4.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 +71 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -0
- package/dist/internal.d.ts +3 -2
- package/dist/internal.js +5 -1
- package/dist/lib/config/expand-mappings.d.ts +24 -0
- package/dist/lib/config/expand-mappings.js +88 -0
- package/dist/lib/config/get-used-dependencies.d.ts +0 -2
- package/dist/lib/config/get-used-dependencies.js +4 -40
- package/dist/lib/config/mapped-paths.d.ts +7 -2
- package/dist/lib/config/mapped-paths.js +21 -4
- package/dist/lib/config/mapping-utils.d.ts +32 -0
- package/dist/lib/config/mapping-utils.js +68 -0
- package/dist/lib/config/match-mapping.d.ts +9 -0
- package/dist/lib/config/match-mapping.js +46 -0
- package/dist/lib/config/remove-unused-deps.d.ts +2 -1
- package/dist/lib/config/remove-unused-deps.js +35 -2
- package/dist/lib/config/validate-mappings.d.ts +15 -0
- package/dist/lib/config/validate-mappings.js +28 -0
- package/dist/lib/config/with-native-federation.js +18 -6
- package/dist/lib/core/build/assemble-federation-info.d.ts +8 -0
- package/dist/lib/core/build/assemble-federation-info.js +34 -0
- package/dist/lib/core/build/build-for-federation.js +7 -45
- package/dist/lib/core/build/bundle-exposed-and-mappings.d.ts +1 -3
- package/dist/lib/core/build/bundle-exposed-and-mappings.js +20 -32
- package/dist/lib/core/build/bundle-shared.js +9 -3
- package/dist/lib/core/build/rebuild-for-federation.js +6 -33
- package/dist/lib/core/build/resolve-shared-dirs.d.ts +16 -0
- package/dist/lib/core/build/resolve-shared-dirs.js +8 -4
- package/dist/lib/core/cache/cache-persistence.d.ts +4 -2
- package/dist/lib/core/cache/cache-persistence.js +27 -8
- package/dist/lib/core/normalize-options.d.ts +2 -2
- package/dist/lib/core/normalize-options.js +23 -11
- package/dist/lib/core/output/write-federation-outputs.d.ts +7 -0
- package/dist/lib/core/output/write-federation-outputs.js +9 -0
- package/dist/lib/domain/config/federation-config.contract.d.ts +27 -2
- package/dist/lib/domain/utils/file-watcher.contract.d.ts +12 -0
- package/dist/lib/domain/utils/io-port.contract.d.ts +6 -3
- package/dist/lib/utils/file-watcher.d.ts +15 -4
- package/dist/lib/utils/file-watcher.js +112 -19
- package/dist/lib/utils/io/node-io-adapter.js +10 -5
- package/dist/lib/utils/package/entry-point-resolver.js +2 -5
- package/dist/lib/utils/package/package-info.d.ts +13 -0
- package/dist/lib/utils/package/package-info.js +30 -4
- package/dist/lib/utils/package/resolve-wildcard-keys.js +8 -2
- package/dist/lib/utils/path-patterns.d.ts +14 -0
- package/dist/lib/utils/path-patterns.js +12 -0
- package/package.json +5 -5
|
@@ -2,21 +2,25 @@ import { getRawMappedPaths } from "./mapped-paths.js";
|
|
|
2
2
|
import { fromPackageJson } from "./share-utils.js";
|
|
3
3
|
import { findRootTsConfigJson } from "./project-paths.js";
|
|
4
4
|
import { isInSkipList, prepareSkipList } from "./default-skip-list.js";
|
|
5
|
+
import { normalizeMappingConfig, withoutSkippedMappings } from "./mapping-utils.js";
|
|
5
6
|
import { logger } from "../utils/logger.js";
|
|
6
7
|
function withNativeFederation(config) {
|
|
7
8
|
const skip = prepareSkipList(config.skip ?? []);
|
|
8
9
|
const chunks = config.chunks ?? true;
|
|
10
|
+
const mappingVersion = config.features?.mappingVersion ?? true;
|
|
11
|
+
const { paths, configs } = getRawMappedPaths(findRootTsConfigJson(), config.sharedMappings);
|
|
9
12
|
const normalized = {
|
|
10
13
|
$type: "classic",
|
|
11
14
|
name: config.name ?? "",
|
|
12
15
|
exposes: normalizeExposes(config.exposes),
|
|
13
16
|
shared: normalizeShared(config, skip, chunks),
|
|
14
|
-
sharedMappings:
|
|
17
|
+
sharedMappings: withoutSkippedMappings(paths, skip),
|
|
18
|
+
sharedMappingsConfig: normalizeMappingConfigs(configs, mappingVersion),
|
|
15
19
|
chunks,
|
|
16
20
|
skip,
|
|
17
21
|
externals: config.externals ?? [],
|
|
18
22
|
features: {
|
|
19
|
-
mappingVersion
|
|
23
|
+
mappingVersion,
|
|
20
24
|
ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? true,
|
|
21
25
|
denseChunking: config.features?.denseChunking ?? false,
|
|
22
26
|
denseExternals: config.features?.denseExternals ?? false,
|
|
@@ -74,10 +78,18 @@ function normalizeShared(config, skip, chunks) {
|
|
|
74
78
|
result = Object.keys(result).filter((key) => !isInSkipList(key, skip)).reduce((acc, cur) => ({ ...acc, [cur]: result[cur] }), {});
|
|
75
79
|
return result;
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const IGNORED_MAPPING_PROPS = ["build", "platform", "chunks", "packageInfo"];
|
|
82
|
+
function normalizeMappingConfigs(configs, mappingVersion) {
|
|
83
|
+
return Object.entries(configs).reduce((acc, [pattern, cfg]) => {
|
|
84
|
+
const ignored = IGNORED_MAPPING_PROPS.filter((prop) => cfg[prop] !== void 0);
|
|
85
|
+
if (ignored.length > 0) {
|
|
86
|
+
logger.warn(
|
|
87
|
+
`Mapping '${pattern}' sets ${ignored.join(", ")}, which mapped paths do not honour (they all share one bundle). Ignored.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
acc[pattern] = normalizeMappingConfig(cfg, mappingVersion);
|
|
91
|
+
return acc;
|
|
92
|
+
}, {});
|
|
81
93
|
}
|
|
82
94
|
export {
|
|
83
95
|
withNativeFederation
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArtifactInfo, FederationInfo } from '../../domain/core/federation-info.contract.js';
|
|
2
|
+
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
3
|
+
import type { NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
4
|
+
/**
|
|
5
|
+
* Derives the `remoteEntry.json` payload from the populated federation cache. Shared by the initial
|
|
6
|
+
* build and the watch rebuild.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assembleFederationInfo(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, artifactInfo: ArtifactInfo): FederationInfo;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { densifyExternals } from "../output/densify-externals.js";
|
|
2
|
+
function assembleFederationInfo(config, fedOptions, artifactInfo) {
|
|
3
|
+
const federationCache = fedOptions.federationCache;
|
|
4
|
+
const sharedExternals = applyShareScope(
|
|
5
|
+
[...federationCache.externals, ...artifactInfo.mappings],
|
|
6
|
+
config.shareScope
|
|
7
|
+
);
|
|
8
|
+
const federationInfo = {
|
|
9
|
+
name: config.name,
|
|
10
|
+
shared: config.features.denseExternals ? densifyExternals(sharedExternals) : sharedExternals,
|
|
11
|
+
exposes: artifactInfo.exposes,
|
|
12
|
+
buildNotificationsEndpoint: fedOptions.buildNotifications?.enable && fedOptions.dev ? fedOptions.buildNotifications?.endpoint : void 0
|
|
13
|
+
};
|
|
14
|
+
if (federationCache.chunks) {
|
|
15
|
+
federationInfo.chunks = federationCache.chunks;
|
|
16
|
+
}
|
|
17
|
+
if (artifactInfo.chunks) {
|
|
18
|
+
federationInfo.chunks = { ...federationInfo.chunks ?? {}, ...artifactInfo.chunks };
|
|
19
|
+
}
|
|
20
|
+
if (config.features.integrityHashes) {
|
|
21
|
+
federationInfo.integrity = {
|
|
22
|
+
...federationCache.integrity ?? {},
|
|
23
|
+
...artifactInfo.integrity ?? {}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return federationInfo;
|
|
27
|
+
}
|
|
28
|
+
function applyShareScope(externals, shareScope) {
|
|
29
|
+
if (!shareScope) return externals;
|
|
30
|
+
return externals.map((external) => external.shareScope ? external : { ...external, shareScope });
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
assembleFederationInfo
|
|
34
|
+
};
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
bundleExposedAndMappings,
|
|
3
|
-
describeExposed,
|
|
4
|
-
describeSharedMappings
|
|
5
|
-
} from "./bundle-exposed-and-mappings.js";
|
|
1
|
+
import { bundleExposedAndMappings } from "./bundle-exposed-and-mappings.js";
|
|
6
2
|
import { bundleShared } from "./bundle-shared.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { writeImportMap } from "../output/write-import-map.js";
|
|
3
|
+
import { assembleFederationInfo } from "./assemble-federation-info.js";
|
|
4
|
+
import { writeFederationOutputs } from "../output/write-federation-outputs.js";
|
|
10
5
|
import { logger } from "../../utils/logger.js";
|
|
11
6
|
import { AbortedError } from "../../utils/errors.js";
|
|
12
7
|
import { addExternalsToCache } from "../cache/federation-cache.js";
|
|
13
8
|
import { planSharedBundles } from "./shared-bundle-plan.js";
|
|
14
|
-
import path from "path";
|
|
15
9
|
async function buildForFederation(config, fedOptions, externals, signal) {
|
|
16
|
-
fedOptions.federationCache.cachePath = path.join(
|
|
17
|
-
fedOptions.federationCache.cachePath,
|
|
18
|
-
fedOptions.projectName
|
|
19
|
-
);
|
|
20
10
|
logger.info("Building federation artifacts");
|
|
21
11
|
logger.notice("Skip packages you don't want to share in your federation config");
|
|
22
12
|
await executeSharedBundlePlans(planSharedBundles(config, externals), config, fedOptions, signal);
|
|
@@ -31,36 +21,8 @@ async function buildForFederation(config, fedOptions, externals, signal) {
|
|
|
31
21
|
logger.measure(start, "Step 3) Bundling all internal libraries and exposed modules.");
|
|
32
22
|
if (signal?.aborted)
|
|
33
23
|
throw new AbortedError("[buildForFederation] After exposed-and-mappings bundle");
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const sharedExternals = [...fedOptions.federationCache.externals, ...sharedMappingInfo];
|
|
37
|
-
if (config?.shareScope) {
|
|
38
|
-
Object.values(sharedExternals).forEach((external) => {
|
|
39
|
-
if (!external.shareScope) external.shareScope = config.shareScope;
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
const shared = config.features.denseExternals ? densifyExternals(sharedExternals) : sharedExternals;
|
|
43
|
-
const buildNotificationsEndpoint = fedOptions.buildNotifications?.enable && fedOptions.dev ? fedOptions.buildNotifications?.endpoint : void 0;
|
|
44
|
-
const federationInfo = {
|
|
45
|
-
name: config.name,
|
|
46
|
-
shared,
|
|
47
|
-
exposes: exposedInfo,
|
|
48
|
-
buildNotificationsEndpoint
|
|
49
|
-
};
|
|
50
|
-
if (fedOptions.federationCache.chunks) {
|
|
51
|
-
federationInfo.chunks = fedOptions.federationCache.chunks;
|
|
52
|
-
}
|
|
53
|
-
if (artifactInfo?.chunks) {
|
|
54
|
-
federationInfo.chunks = { ...federationInfo.chunks ?? {}, ...artifactInfo?.chunks };
|
|
55
|
-
}
|
|
56
|
-
if (config.features.integrityHashes) {
|
|
57
|
-
federationInfo.integrity = {
|
|
58
|
-
...fedOptions.federationCache.integrity ?? {},
|
|
59
|
-
...artifactInfo?.integrity ?? {}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
writeFederationInfo(federationInfo, fedOptions);
|
|
63
|
-
writeImportMap(fedOptions.federationCache, fedOptions, federationInfo.integrity);
|
|
24
|
+
const federationInfo = assembleFederationInfo(config, fedOptions, artifactInfo);
|
|
25
|
+
writeFederationOutputs(federationInfo, fedOptions);
|
|
64
26
|
return federationInfo;
|
|
65
27
|
}
|
|
66
28
|
async function executeSharedBundlePlans(plans, config, fedOptions, signal) {
|
|
@@ -75,7 +37,7 @@ async function executeSharedBundlePlans(plans, config, fedOptions, signal) {
|
|
|
75
37
|
logger.measure(start, `Step 2.1) Bundling '${plan.bundleName}' externals`);
|
|
76
38
|
addExternalsToCache(fedOptions.federationCache, info);
|
|
77
39
|
if (signal?.aborted)
|
|
78
|
-
throw new AbortedError(`[
|
|
40
|
+
throw new AbortedError(`[executeSharedBundlePlans] After ${plan.bundleName} bundle`);
|
|
79
41
|
}
|
|
80
42
|
const separatePlans = plans.filter((p) => p.kind === "separate");
|
|
81
43
|
if (separatePlans.length > 0) {
|
|
@@ -91,7 +53,7 @@ async function executeSharedBundlePlans(plans, config, fedOptions, signal) {
|
|
|
91
53
|
);
|
|
92
54
|
logger.measure(start, "Step 2.2) Bundling all separate external packages");
|
|
93
55
|
for (const info of results) addExternalsToCache(fedOptions.federationCache, info);
|
|
94
|
-
if (signal?.aborted) throw new AbortedError("[
|
|
56
|
+
if (signal?.aborted) throw new AbortedError("[executeSharedBundlePlans] After separate bundle");
|
|
95
57
|
}
|
|
96
58
|
}
|
|
97
59
|
export {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArtifactInfo
|
|
1
|
+
import type { ArtifactInfo } from '../../domain/core/federation-info.contract.js';
|
|
2
2
|
import type { FileReaderPort } from '../../domain/utils/io-port.contract.js';
|
|
3
3
|
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
4
4
|
import { type NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
@@ -7,7 +7,5 @@ export declare function bundleExposedAndMappings(config: NormalizedFederationCon
|
|
|
7
7
|
export declare function bundleExposedAndMappingsCore(deps: {
|
|
8
8
|
adapter: NFBuildAdapter;
|
|
9
9
|
}, config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], modifiedFiles?: string[], signal?: AbortSignal): Promise<ArtifactInfo>;
|
|
10
|
-
export declare function describeExposed(config: NormalizedFederationConfig, options: NormalizedFederationOptions): Array<ExposesInfo>;
|
|
11
|
-
export declare function describeSharedMappings(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions): Array<SharedInfo>;
|
|
12
10
|
export declare function getMappingVersionCore(io: FileReaderPort, fileName: string, workspaceRoot: string): string;
|
|
13
11
|
export declare function getMappingVersion(fileName: string, workspaceRoot: string): string;
|
|
@@ -7,6 +7,7 @@ import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
|
7
7
|
import { AbortedError } from "../../utils/errors.js";
|
|
8
8
|
import { rewriteChunkImports } from "./rewrite-chunk-imports.js";
|
|
9
9
|
import { getBuildAdapter } from "./build-adapter.js";
|
|
10
|
+
import { resolveMappingConfig } from "../../config/mapping-utils.js";
|
|
10
11
|
async function bundleExposedAndMappings(config, fedOptions, externals, modifiedFiles, signal) {
|
|
11
12
|
return bundleExposedAndMappingsCore(
|
|
12
13
|
{ adapter: getBuildAdapter() },
|
|
@@ -78,7 +79,13 @@ async function bundleExposedAndMappingsCore(deps, config, fedOptions, externals,
|
|
|
78
79
|
for (const item of shared) {
|
|
79
80
|
const distEntryFile = popFromResultMap(resultMap, item.outName);
|
|
80
81
|
sharedResult.push(
|
|
81
|
-
toSharedMappingInfo(
|
|
82
|
+
toSharedMappingInfo(
|
|
83
|
+
item.fileName,
|
|
84
|
+
item.key,
|
|
85
|
+
path.basename(distEntryFile),
|
|
86
|
+
config,
|
|
87
|
+
fedOptions
|
|
88
|
+
)
|
|
82
89
|
);
|
|
83
90
|
entryFiles.push(distEntryFile);
|
|
84
91
|
}
|
|
@@ -108,40 +115,23 @@ async function bundleExposedAndMappingsCore(deps, config, fedOptions, externals,
|
|
|
108
115
|
const integrity = config.features.integrityHashes ? computeIntegrityMap([...entryFiles, ...chunkPaths], "") : void 0;
|
|
109
116
|
return { mappings: sharedResult, exposes: exposedResult, chunks: exportedChunks, integrity };
|
|
110
117
|
}
|
|
111
|
-
function describeExposed(config, options) {
|
|
112
|
-
const result = [];
|
|
113
|
-
for (const key in config.exposes) {
|
|
114
|
-
const expose = config.exposes[key];
|
|
115
|
-
const localPath = normalize(
|
|
116
|
-
path.normalize(path.join(options.workspaceRoot, expose.file))
|
|
117
|
-
);
|
|
118
|
-
result.push({
|
|
119
|
-
key,
|
|
120
|
-
outFileName: "",
|
|
121
|
-
...expose.element && { element: expose.element },
|
|
122
|
-
dev: !options.dev ? void 0 : {
|
|
123
|
-
entryPoint: localPath
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
function describeSharedMappings(config, fedOptions) {
|
|
130
|
-
const result = [];
|
|
131
|
-
for (const [mappedPath, mappedImport] of Object.entries(config.sharedMappings)) {
|
|
132
|
-
result.push(toSharedMappingInfo(mappedPath, mappedImport, "", config, fedOptions));
|
|
133
|
-
}
|
|
134
|
-
return result;
|
|
135
|
-
}
|
|
136
118
|
function toSharedMappingInfo(mappedPath, mappedImport, outFileName, config, fedOptions) {
|
|
137
119
|
const mappingVersion = config.features.mappingVersion ? getMappingVersion(mappedPath, fedOptions.workspaceRoot) : "";
|
|
120
|
+
const mappingConfig = resolveMappingConfig(
|
|
121
|
+
mappedImport,
|
|
122
|
+
config.sharedMappingsConfig,
|
|
123
|
+
config.features.mappingVersion
|
|
124
|
+
);
|
|
125
|
+
const version = mappingConfig.version ?? mappingVersion;
|
|
138
126
|
return {
|
|
139
127
|
packageName: mappedImport,
|
|
140
128
|
outFileName,
|
|
141
|
-
requiredVersion:
|
|
142
|
-
singleton:
|
|
143
|
-
strictVersion:
|
|
144
|
-
version
|
|
129
|
+
requiredVersion: mappingConfig.requiredVersion ?? (version.length > 0 ? "~" + version : ""),
|
|
130
|
+
singleton: mappingConfig.singleton,
|
|
131
|
+
strictVersion: mappingConfig.strictVersion,
|
|
132
|
+
version,
|
|
133
|
+
...mappingConfig.shareScope && { shareScope: mappingConfig.shareScope },
|
|
134
|
+
...mappingConfig.pool && { pool: mappingConfig.pool },
|
|
145
135
|
dev: !fedOptions.dev ? void 0 : {
|
|
146
136
|
entryPoint: normalize(path.normalize(mappedPath))
|
|
147
137
|
}
|
|
@@ -171,8 +161,6 @@ function getMappingVersion(fileName, workspaceRoot) {
|
|
|
171
161
|
export {
|
|
172
162
|
bundleExposedAndMappings,
|
|
173
163
|
bundleExposedAndMappingsCore,
|
|
174
|
-
describeExposed,
|
|
175
|
-
describeSharedMappings,
|
|
176
164
|
getMappingVersion,
|
|
177
165
|
getMappingVersionCore
|
|
178
166
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import {
|
|
3
3
|
sharedPackageJsonRepository,
|
|
4
|
-
getPackageInfo
|
|
4
|
+
getPackageInfo,
|
|
5
|
+
installedVersions
|
|
5
6
|
} from "../../utils/package/package-info.js";
|
|
6
7
|
import { logger } from "../../utils/logger.js";
|
|
7
8
|
import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
@@ -41,13 +42,18 @@ async function bundleSharedCore(deps, sharedBundles, config, fedOptions, externa
|
|
|
41
42
|
deps.io,
|
|
42
43
|
deps.repo
|
|
43
44
|
);
|
|
45
|
+
const resolvedVersions = installedVersions(Object.keys(sharedBundles), folder, deps.repo);
|
|
46
|
+
for (const [key, cfg] of Object.entries(sharedBundles)) {
|
|
47
|
+
if (cfg.packageInfo) resolvedVersions[key] = cfg.packageInfo.version ?? "";
|
|
48
|
+
}
|
|
44
49
|
const checksum = getChecksumCore(
|
|
45
50
|
deps.io,
|
|
46
51
|
sharedBundles,
|
|
47
52
|
fedOptions.dev ? "1" : "0",
|
|
48
53
|
builderVersion,
|
|
49
|
-
config.features
|
|
50
|
-
contentSignals
|
|
54
|
+
config.features,
|
|
55
|
+
contentSignals,
|
|
56
|
+
resolvedVersions
|
|
51
57
|
);
|
|
52
58
|
const bundleCache = cacheEntryCore(
|
|
53
59
|
deps.io,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
describeSharedMappings
|
|
5
|
-
} from "./bundle-exposed-and-mappings.js";
|
|
6
|
-
import { writeFederationInfo } from "../output/write-federation-info.js";
|
|
7
|
-
import { writeImportMap } from "../output/write-import-map.js";
|
|
1
|
+
import { bundleExposedAndMappings } from "./bundle-exposed-and-mappings.js";
|
|
2
|
+
import { assembleFederationInfo } from "./assemble-federation-info.js";
|
|
3
|
+
import { writeFederationOutputs } from "../output/write-federation-outputs.js";
|
|
8
4
|
import { logger } from "../../utils/logger.js";
|
|
9
5
|
import { AbortedError } from "../../utils/errors.js";
|
|
10
6
|
import { planSharedBundles } from "./shared-bundle-plan.js";
|
|
@@ -14,7 +10,6 @@ import { cacheEntryCore, getFilename } from "../cache/cache-persistence.js";
|
|
|
14
10
|
import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
15
11
|
import { sharedPackageJsonRepository } from "../../utils/package/package-info.js";
|
|
16
12
|
async function rebuildForFederation(config, fedOptions, externals, modifiedFiles, signal) {
|
|
17
|
-
const federationCache = fedOptions.federationCache;
|
|
18
13
|
await rebuildAffectedExternals(config, fedOptions, externals, modifiedFiles, signal);
|
|
19
14
|
logger.info(`Re-bundling all internal libraries and exposed modules..'`);
|
|
20
15
|
const start = process.hrtime();
|
|
@@ -27,31 +22,9 @@ async function rebuildForFederation(config, fedOptions, externals, modifiedFiles
|
|
|
27
22
|
);
|
|
28
23
|
logger.measure(start, "To re-bundle all internal libraries and exposed modules.");
|
|
29
24
|
if (signal?.aborted)
|
|
30
|
-
throw new AbortedError("[
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const sharedExternals = [...federationCache.externals, ...sharedMappingInfo];
|
|
34
|
-
const buildNotificationsEndpoint = fedOptions.buildNotifications?.enable && fedOptions.dev ? fedOptions.buildNotifications?.endpoint : void 0;
|
|
35
|
-
const federationInfo = {
|
|
36
|
-
name: config.name,
|
|
37
|
-
shared: sharedExternals,
|
|
38
|
-
exposes: exposedInfo,
|
|
39
|
-
buildNotificationsEndpoint
|
|
40
|
-
};
|
|
41
|
-
if (federationCache.chunks) {
|
|
42
|
-
federationInfo.chunks = federationCache.chunks;
|
|
43
|
-
}
|
|
44
|
-
if (artifactInfo?.chunks) {
|
|
45
|
-
federationInfo.chunks = { ...federationInfo.chunks ?? {}, ...artifactInfo?.chunks };
|
|
46
|
-
}
|
|
47
|
-
if (config.features.integrityHashes) {
|
|
48
|
-
federationInfo.integrity = {
|
|
49
|
-
...federationCache.integrity ?? {},
|
|
50
|
-
...artifactInfo?.integrity ?? {}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
writeFederationInfo(federationInfo, fedOptions);
|
|
54
|
-
writeImportMap(federationCache, fedOptions, federationInfo.integrity);
|
|
25
|
+
throw new AbortedError("[rebuildForFederation] After exposed-and-mappings bundle");
|
|
26
|
+
const federationInfo = assembleFederationInfo(config, fedOptions, artifactInfo);
|
|
27
|
+
writeFederationOutputs(federationInfo, fedOptions);
|
|
55
28
|
return federationInfo;
|
|
56
29
|
}
|
|
57
30
|
async function rebuildAffectedExternals(config, fedOptions, externals, modifiedFiles, signal, io = nodeIo, repo = sharedPackageJsonRepository) {
|
|
@@ -8,6 +8,22 @@ export declare function resolveSharedPackageDirs(config: NormalizedFederationCon
|
|
|
8
8
|
/** Realpath'd dirs of symlinked shared packages — the bounded watch set.
|
|
9
9
|
* Deduped, since secondaries share a package dir. */
|
|
10
10
|
export declare function linkedSharedDirs(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, io?: FileReaderPort, repo?: PackageJsonRepository): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Source directories of the workspace libs in `config.sharedMappings` — the watch set
|
|
13
|
+
* for mappings, derived from config alone rather than from a bundler cache.
|
|
14
|
+
*
|
|
15
|
+
* Coarser than the inputs a build actually compiled, and deliberately so: it covers
|
|
16
|
+
* files added to a lib after the last build, which a compiled-inputs watch set cannot
|
|
17
|
+
* know about yet. It does not follow imports out of the lib, so an adapter that can
|
|
18
|
+
* enumerate its build inputs should watch both. See angular-adapter#94.
|
|
19
|
+
*
|
|
20
|
+
* How coarse is the caller's to bound: an entry point that is not a lib barrel widens the
|
|
21
|
+
* watch to whatever directory it sits in (`'@app/env': ['src/environments/environment.ts']`
|
|
22
|
+
* to that folder, `'@shared': ['src/index.ts']` to all of `src`), and with `sharedMappings`
|
|
23
|
+
* unset every tsconfig path becomes a mapping. Watch these natively rather than polled —
|
|
24
|
+
* they are source trees, not the dist output `linkedSharedDirs` exists for.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sharedMappingDirs(config: NormalizedFederationConfig): string[];
|
|
11
27
|
/** Per-key content signal (max mtime of the resolved dir) for symlinked deps only.
|
|
12
28
|
* Registry deps get no signal, keeping their checksum version-only. (Every key is
|
|
13
29
|
* still resolved: detecting the symlink requires the realpath + lstat.) */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
3
3
|
import { sharedPackageJsonRepository } from "../../utils/package/package-info.js";
|
|
4
|
-
import { toPosix } from "../../utils/path-patterns.js";
|
|
4
|
+
import { isUnderDir, toPosix } from "../../utils/path-patterns.js";
|
|
5
5
|
const folderOf = (fedOptions) => fedOptions.packageJson ? path.dirname(fedOptions.packageJson) : fedOptions.workspaceRoot;
|
|
6
6
|
function resolveEntries(keys, folder, io, repo) {
|
|
7
7
|
const out = [];
|
|
@@ -25,6 +25,10 @@ function linkedSharedDirs(config, fedOptions, io = nodeIo, repo = sharedPackageJ
|
|
|
25
25
|
const entries = resolveEntries(Object.keys(config.shared), folderOf(fedOptions), io, repo);
|
|
26
26
|
return [...new Set(entries.filter((e) => e.isSymlink).map((e) => e.realDir))];
|
|
27
27
|
}
|
|
28
|
+
function sharedMappingDirs(config) {
|
|
29
|
+
const dirs = Object.keys(config.sharedMappings).map((entryPoint) => toPosix(path.dirname(entryPoint))).filter((dir) => !dir.includes("node_modules"));
|
|
30
|
+
return [...new Set(dirs)];
|
|
31
|
+
}
|
|
28
32
|
function maxMtime(io, dir) {
|
|
29
33
|
let max = 0;
|
|
30
34
|
const walk = (d) => {
|
|
@@ -53,8 +57,7 @@ function affectedSharedKeys(modifiedFiles, dirs, io = nodeIo) {
|
|
|
53
57
|
if (modifiedFiles.length === 0 || dirs.size === 0) return affected;
|
|
54
58
|
const realFiles = modifiedFiles.map((f) => toPosix(io.realpath(f)));
|
|
55
59
|
for (const [key, dir] of dirs) {
|
|
56
|
-
|
|
57
|
-
if (realFiles.some((f) => f === dir || f.startsWith(prefix))) affected.add(key);
|
|
60
|
+
if (realFiles.some((f) => isUnderDir(f, dir))) affected.add(key);
|
|
58
61
|
}
|
|
59
62
|
return affected;
|
|
60
63
|
}
|
|
@@ -62,5 +65,6 @@ export {
|
|
|
62
65
|
affectedSharedKeys,
|
|
63
66
|
linkedContentSignals,
|
|
64
67
|
linkedSharedDirs,
|
|
65
|
-
resolveSharedPackageDirs
|
|
68
|
+
resolveSharedPackageDirs,
|
|
69
|
+
sharedMappingDirs
|
|
66
70
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { NormalizedExternalConfig } from '../../domain/config/external-config.contract.js';
|
|
2
|
+
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
2
3
|
import type { ChunkInfo, IntegrityMap, SharedInfo } from '../../domain/core/federation-info.contract.js';
|
|
3
4
|
import type { FileReaderPort, FileWriterPort, HashPort } from '../../domain/utils/io-port.contract.js';
|
|
4
5
|
export declare const getDefaultCachePath: (workspaceRoot: string) => string;
|
|
5
6
|
export declare const getFilename: (title: string, dev?: boolean) => string;
|
|
6
|
-
export declare const getChecksum: (shared: Record<string, NormalizedExternalConfig>, dev: "1" | "0", builderVersion?: string,
|
|
7
|
-
export
|
|
7
|
+
export declare const getChecksum: (shared: Record<string, NormalizedExternalConfig>, dev: "1" | "0", builderVersion?: string, features?: FeatureFlags, contentSignals?: Record<string, string>, resolvedVersions?: Record<string, string>) => string;
|
|
8
|
+
export type FeatureFlags = Partial<NormalizedFederationConfig['features']>;
|
|
9
|
+
export declare const getChecksumCore: (hash: HashPort, shared: Record<string, NormalizedExternalConfig>, dev: "1" | "0", builderVersion?: string, features?: FeatureFlags, contentSignals?: Record<string, string>, resolvedVersions?: Record<string, string>) => string;
|
|
8
10
|
export type CacheMetadata = {
|
|
9
11
|
checksum: string;
|
|
10
12
|
externals: SharedInfo[];
|
|
@@ -6,15 +6,31 @@ const getFilename = (title, dev) => {
|
|
|
6
6
|
const devSuffix = dev ? "-dev" : "";
|
|
7
7
|
return `${title}${devSuffix}.meta.json`;
|
|
8
8
|
};
|
|
9
|
-
const getChecksum = (shared, dev, builderVersion = "",
|
|
10
|
-
const
|
|
9
|
+
const getChecksum = (shared, dev, builderVersion = "", features = {}, contentSignals = {}, resolvedVersions = {}) => getChecksumCore(nodeIo, shared, dev, builderVersion, features, contentSignals, resolvedVersions);
|
|
10
|
+
const featureState = (features) => Object.entries(features).sort(([a], [b]) => a < b ? -1 : 1).map(([flag, on]) => `${flag}=${on ? "1" : "0"}`).join(",");
|
|
11
|
+
const SHARED_INFO_FIELDS = [
|
|
12
|
+
"requiredVersion",
|
|
13
|
+
"singleton",
|
|
14
|
+
"strictVersion",
|
|
15
|
+
"shareScope",
|
|
16
|
+
"pool"
|
|
17
|
+
];
|
|
18
|
+
const sharedInfoState = (config) => {
|
|
19
|
+
const values = SHARED_INFO_FIELDS.map((field) => config[field] ?? null);
|
|
20
|
+
return values.every((value) => value === null) ? "" : `!${JSON.stringify(values)}`;
|
|
21
|
+
};
|
|
22
|
+
const getChecksumCore = (hash, shared, dev, builderVersion = "", features = {}, contentSignals = {}, resolvedVersions = {}) => {
|
|
11
23
|
const denseExternals = Object.keys(shared).sort().reduce((clean, external) => {
|
|
12
|
-
const
|
|
24
|
+
const installed = resolvedVersions[external];
|
|
25
|
+
const declared = shared[external].version;
|
|
26
|
+
const version = installed ? `~${installed}` : declared ? `@${declared}` : "";
|
|
13
27
|
const signal = contentSignals[external] ? `#${contentSignals[external]}` : "";
|
|
14
|
-
return clean + ":" + external + version + signal;
|
|
28
|
+
return clean + ":" + external + version + sharedInfoState(shared[external]) + signal;
|
|
15
29
|
}, "deps");
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
return hash.hash(
|
|
31
|
+
"sha256",
|
|
32
|
+
denseExternals + `:dev=${dev}:builder=${builderVersion}:features=${featureState(features)}`
|
|
33
|
+
).hex();
|
|
18
34
|
};
|
|
19
35
|
const cacheEntryCore = (io, pathToCache, fileName) => {
|
|
20
36
|
const metadataFile = path.join(pathToCache, fileName);
|
|
@@ -36,8 +52,11 @@ const cacheEntryCore = (io, pathToCache, fileName) => {
|
|
|
36
52
|
io.mkdirp(fullOutputPath);
|
|
37
53
|
cachedResult.files.forEach((file) => {
|
|
38
54
|
const cachedFile = path.join(pathToCache, file);
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
if (!io.exists(cachedFile))
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Cached artifact '${file}' recorded in '${metadataFile}' is missing. Delete '${pathToCache}' and rebuild.`
|
|
58
|
+
);
|
|
59
|
+
io.copyFile(cachedFile, path.join(fullOutputPath, file));
|
|
41
60
|
});
|
|
42
61
|
},
|
|
43
62
|
clear: () => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { NormalizedFederationConfig } from '../domain/config/federation-config.contract.js';
|
|
2
2
|
import type { FederationOptions, NormalizedFederationOptions } from '../domain/core/federation-options.contract.js';
|
|
3
|
-
import type { FileReaderPort } from '../domain/utils/io-port.contract.js';
|
|
3
|
+
import type { FileReaderPort, GlobPort } from '../domain/utils/io-port.contract.js';
|
|
4
4
|
import { type FederationCache } from '../../domain.js';
|
|
5
5
|
import { getUsedDependenciesFactory } from '../config/get-used-dependencies.js';
|
|
6
6
|
type ConfigLoader = (fullConfigPath: string) => Promise<NormalizedFederationConfig>;
|
|
7
7
|
interface NormalizeFederationDeps {
|
|
8
|
-
io: FileReaderPort;
|
|
8
|
+
io: FileReaderPort & GlobPort;
|
|
9
9
|
loadConfig: ConfigLoader;
|
|
10
10
|
usedDependenciesFactory?: typeof getUsedDependenciesFactory;
|
|
11
11
|
}
|
|
@@ -2,6 +2,8 @@ import * as path from "path";
|
|
|
2
2
|
import { pathToFileURL } from "url";
|
|
3
3
|
import { nodeIo } from "../utils/io/node-io-adapter.js";
|
|
4
4
|
import { removeUnusedDeps } from "../config/remove-unused-deps.js";
|
|
5
|
+
import { expandOrDropWildcards } from "../config/expand-mappings.js";
|
|
6
|
+
import { assertBarrelMappings } from "../config/validate-mappings.js";
|
|
5
7
|
import { createFederationCache } from "./cache/federation-cache.js";
|
|
6
8
|
import { getDefaultCachePath } from "./cache/cache-persistence.js";
|
|
7
9
|
import { getUsedDependenciesFactory } from "../config/get-used-dependencies.js";
|
|
@@ -21,35 +23,45 @@ async function normalizeFederationOptionsCore(deps, options, cache) {
|
|
|
21
23
|
throw new Error("Expected " + fullConfigPath);
|
|
22
24
|
}
|
|
23
25
|
let config = await deps.loadConfig(fullConfigPath);
|
|
24
|
-
const
|
|
26
|
+
const projectName = resolveProjectName(options.projectName ?? config.name);
|
|
27
|
+
const suppliedCache = cache ?? createFederationCache(
|
|
25
28
|
getDefaultCachePath(options.workspaceRoot)
|
|
26
29
|
);
|
|
30
|
+
const federationCache = {
|
|
31
|
+
...suppliedCache,
|
|
32
|
+
cachePath: path.join(suppliedCache.cachePath, projectName),
|
|
33
|
+
externals: [...suppliedCache.externals]
|
|
34
|
+
};
|
|
27
35
|
const normalizedOptions = {
|
|
28
36
|
...options,
|
|
29
37
|
entryPoints: options.entryPoints ?? Object.values(config.exposes ?? {}).map((e) => e.file),
|
|
30
|
-
projectName
|
|
38
|
+
projectName,
|
|
31
39
|
cacheExternalArtifacts: options.cacheExternalArtifacts ?? true,
|
|
32
40
|
federationCache
|
|
33
41
|
};
|
|
34
|
-
|
|
42
|
+
const nothingShared = Object.keys(config.shared).length === 0 && Object.keys(config.sharedMappings).length === 0;
|
|
43
|
+
if (nothingShared) {
|
|
44
|
+
logger.debug("Nothing is shared, skipping the used dependency scan.");
|
|
45
|
+
} else if (config.features.ignoreUnusedDeps) {
|
|
35
46
|
const getUsedDeps = (deps.usedDependenciesFactory ?? getUsedDependenciesFactory)(
|
|
36
47
|
options.workspaceRoot,
|
|
37
48
|
options.entryPoints
|
|
38
49
|
);
|
|
39
|
-
config = removeUnusedDeps(getUsedDeps(config), config
|
|
50
|
+
config = removeUnusedDeps(getUsedDeps(config), config, {
|
|
51
|
+
io: deps.io,
|
|
52
|
+
workspaceRoot: options.workspaceRoot
|
|
53
|
+
});
|
|
40
54
|
logger.info("Removed unused dependencies.");
|
|
41
55
|
logger.debug(
|
|
42
56
|
'This can be disabled per dependency/external using the "includeSecondaries: {keepAll: true}" property. Or in general by disabling the "ignoreUnusedDeps" feature. '
|
|
43
57
|
);
|
|
44
58
|
} else {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
config.sharedMappings = Object.entries(config.sharedMappings).filter(([_path]) => !_path.includes("*")).reduce((acc, [_path, _import]) => ({ ...acc, [_path]: _import }), {});
|
|
51
|
-
}
|
|
59
|
+
config.sharedMappings = expandOrDropWildcards(config, {
|
|
60
|
+
io: deps.io,
|
|
61
|
+
workspaceRoot: options.workspaceRoot
|
|
62
|
+
});
|
|
52
63
|
}
|
|
64
|
+
assertBarrelMappings(config.sharedMappings);
|
|
53
65
|
return { config, options: normalizedOptions };
|
|
54
66
|
}
|
|
55
67
|
function resolveProjectName(name) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FederationInfo } from '../../domain/core/federation-info.contract.js';
|
|
2
|
+
import type { NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
3
|
+
/**
|
|
4
|
+
* The import map is built from the flat federation cache rather than `federationInfo.shared`: a
|
|
5
|
+
* dense group has no import-map representation, every specifier needs its own entry.
|
|
6
|
+
*/
|
|
7
|
+
export declare function writeFederationOutputs(federationInfo: FederationInfo, fedOptions: NormalizedFederationOptions): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { writeFederationInfo } from "./write-federation-info.js";
|
|
2
|
+
import { writeImportMap } from "./write-import-map.js";
|
|
3
|
+
function writeFederationOutputs(federationInfo, fedOptions) {
|
|
4
|
+
writeFederationInfo(federationInfo, fedOptions);
|
|
5
|
+
writeImportMap(fedOptions.federationCache, fedOptions, federationInfo.integrity);
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
writeFederationOutputs
|
|
9
|
+
};
|