@softarc/native-federation 4.3.0 → 4.3.2
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 +2 -2
- package/dist/internal/browser.d.ts +6 -0
- package/dist/internal/browser.js +9 -0
- package/dist/internal.d.ts +9 -6
- package/dist/internal.js +21 -6
- package/dist/lib/config/with-native-federation.js +2 -1
- package/dist/lib/core/build/build-for-federation.d.ts +7 -0
- package/dist/lib/core/build/build-for-federation.js +32 -134
- package/dist/lib/core/build/bundle-shared.d.ts +7 -0
- package/dist/lib/core/build/bundle-shared.js +38 -7
- package/dist/lib/core/build/rebuild-for-federation.d.ts +8 -0
- package/dist/lib/core/build/rebuild-for-federation.js +26 -0
- package/dist/lib/core/build/resolve-shared-dirs.d.ts +16 -0
- package/dist/lib/core/build/resolve-shared-dirs.js +66 -0
- package/dist/lib/core/build/shared-bundle-plan.d.ts +22 -0
- package/dist/lib/core/build/shared-bundle-plan.js +70 -0
- package/dist/lib/core/build/synthesize-cjs-exports.d.ts +12 -0
- package/dist/lib/core/build/synthesize-cjs-exports.js +58 -0
- package/dist/lib/core/cache/cache-persistence.d.ts +2 -2
- package/dist/lib/core/cache/cache-persistence.js +7 -4
- package/dist/lib/core/federation-builder.d.ts +2 -1
- package/dist/lib/core/federation-builder.js +3 -0
- package/dist/lib/core/output/densify-externals.d.ts +1 -0
- package/dist/lib/core/output/densify-externals.js +21 -4
- package/dist/lib/core/output/write-federation-info.js +1 -1
- package/dist/lib/domain/config/federation-config.contract.d.ts +2 -0
- package/dist/lib/domain/core/federation-info.contract.d.ts +1 -0
- package/dist/lib/domain/utils/file-watcher.contract.d.ts +7 -1
- package/dist/lib/domain/utils/io-port.contract.d.ts +14 -3
- package/dist/lib/utils/file-watcher.d.ts +1 -1
- package/dist/lib/utils/file-watcher.js +23 -6
- package/dist/lib/utils/io/node-io-adapter.js +77 -21
- package/dist/lib/utils/package/cjs-named-exports.d.ts +21 -0
- package/dist/lib/utils/package/cjs-named-exports.js +36 -0
- package/dist/lib/utils/package/esm-detection.d.ts +13 -0
- package/dist/lib/utils/package/esm-detection.js +29 -0
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -614,7 +614,7 @@ For a zero-build integration, declare your remotes in a manifest and include the
|
|
|
614
614
|
<script>
|
|
615
615
|
window.addEventListener(
|
|
616
616
|
'mfe-loader-available',
|
|
617
|
-
|
|
617
|
+
e => {
|
|
618
618
|
e.detail.loadRemoteModule('team/mfe1', './Button');
|
|
619
619
|
e.detail.loadRemoteModule('team/mfe2', './Header');
|
|
620
620
|
},
|
|
@@ -623,7 +623,7 @@ For a zero-build integration, declare your remotes in a manifest and include the
|
|
|
623
623
|
</script>
|
|
624
624
|
|
|
625
625
|
<!-- Include the orchestrator -->
|
|
626
|
-
<script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.
|
|
626
|
+
<script src="https://unpkg.com/@softarc/native-federation-orchestrator@4.5.1/quickstart.mjs"></script>
|
|
627
627
|
```
|
|
628
628
|
|
|
629
629
|
The `mfe-loader-available` event signals that the orchestrator has fetched the
|
|
@@ -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
|
+
};
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
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 {
|
|
13
|
-
export {
|
|
14
|
+
export { isCjsCandidate, classifyByExtension, hasEsmSyntax, type ModuleFormat, } from './lib/utils/package/esm-detection.js';
|
|
15
|
+
export { isIdentifierName, planCjsWrap, buildSyntheticCjsEntry, isEsmInteropError, } from './lib/utils/package/cjs-named-exports.js';
|
|
14
16
|
export type { NfFileWatcher, NfFileWatcherOptions, } from './lib/domain/utils/file-watcher.contract.js';
|
|
15
17
|
export { syncNfFileWatcher, createNfWatcher } from './lib/utils/file-watcher.js';
|
|
18
|
+
export { linkedSharedDirs } from './lib/core/build/resolve-shared-dirs.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,20 +8,35 @@ 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 {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
isCjsCandidate,
|
|
13
|
+
classifyByExtension,
|
|
14
|
+
hasEsmSyntax
|
|
15
|
+
} from "./lib/utils/package/esm-detection.js";
|
|
16
|
+
import {
|
|
17
|
+
isIdentifierName,
|
|
18
|
+
planCjsWrap,
|
|
19
|
+
buildSyntheticCjsEntry,
|
|
20
|
+
isEsmInteropError
|
|
21
|
+
} from "./lib/utils/package/cjs-named-exports.js";
|
|
13
22
|
import { syncNfFileWatcher, createNfWatcher } from "./lib/utils/file-watcher.js";
|
|
23
|
+
import { linkedSharedDirs } from "./lib/core/build/resolve-shared-dirs.js";
|
|
14
24
|
export {
|
|
15
25
|
RebuildQueue,
|
|
26
|
+
buildSyntheticCjsEntry,
|
|
27
|
+
classifyByExtension,
|
|
16
28
|
createNfWatcher,
|
|
17
|
-
densifyExternals,
|
|
18
29
|
getChecksum,
|
|
19
30
|
getDefaultCachePath,
|
|
31
|
+
hasEsmSyntax,
|
|
20
32
|
hashFile,
|
|
33
|
+
isCjsCandidate,
|
|
21
34
|
isESMExport,
|
|
22
|
-
|
|
35
|
+
isEsmInteropError,
|
|
36
|
+
isIdentifierName,
|
|
37
|
+
linkedSharedDirs,
|
|
23
38
|
logger,
|
|
24
|
-
|
|
39
|
+
planCjsWrap,
|
|
25
40
|
setLogLevel,
|
|
26
41
|
syncNfFileWatcher,
|
|
27
42
|
writeImportMap
|
|
@@ -20,7 +20,8 @@ function withNativeFederation(config) {
|
|
|
20
20
|
ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? true,
|
|
21
21
|
denseChunking: config.features?.denseChunking ?? false,
|
|
22
22
|
denseExternals: config.features?.denseExternals ?? false,
|
|
23
|
-
integrityHashes: config.features?.integrityHashes ?? false
|
|
23
|
+
integrityHashes: config.features?.integrityHashes ?? false,
|
|
24
|
+
synthesizeCjsExports: config.features?.synthesizeCjsExports ?? true
|
|
24
25
|
},
|
|
25
26
|
...config.shareScope && { shareScope: config.shareScope }
|
|
26
27
|
};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { FederationInfo } from '../../domain/core/federation-info.contract.js';
|
|
2
2
|
import type { NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
3
3
|
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
4
|
+
import { type SharedBundlePlan } from './shared-bundle-plan.js';
|
|
4
5
|
export declare function buildForFederation(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], signal?: AbortSignal): Promise<FederationInfo>;
|
|
6
|
+
/**
|
|
7
|
+
* Bundles shared/separate externals per plan and populates the federation cache.
|
|
8
|
+
* Shared bundles run sequentially (with signal checks); separate bundles run in
|
|
9
|
+
* parallel. Shared by the initial build and the watch rebuild.
|
|
10
|
+
*/
|
|
11
|
+
export declare function executeSharedBundlePlans(plans: SharedBundlePlan[], config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, signal?: AbortSignal): Promise<void>;
|
|
@@ -8,9 +8,9 @@ import { densifyExternals } from "../output/densify-externals.js";
|
|
|
8
8
|
import { writeFederationInfo } from "../output/write-federation-info.js";
|
|
9
9
|
import { writeImportMap } from "../output/write-import-map.js";
|
|
10
10
|
import { logger } from "../../utils/logger.js";
|
|
11
|
-
import { inferPackageFromSecondary, normalizePackageName } from "../../utils/normalize.js";
|
|
12
11
|
import { AbortedError } from "../../utils/errors.js";
|
|
13
12
|
import { addExternalsToCache } from "../cache/federation-cache.js";
|
|
13
|
+
import { planSharedBundles } from "./shared-bundle-plan.js";
|
|
14
14
|
import path from "path";
|
|
15
15
|
async function buildForFederation(config, fedOptions, externals, signal) {
|
|
16
16
|
fedOptions.federationCache.cachePath = path.join(
|
|
@@ -19,66 +19,7 @@ async function buildForFederation(config, fedOptions, externals, signal) {
|
|
|
19
19
|
);
|
|
20
20
|
logger.info("Building federation artifacts");
|
|
21
21
|
logger.notice("Skip packages you don't want to share in your federation config");
|
|
22
|
-
|
|
23
|
-
config.shared
|
|
24
|
-
);
|
|
25
|
-
if (Object.keys(sharedBrowser).length > 0) {
|
|
26
|
-
logger.info(`Bundling external npm packages with bundle type 'browser-shared'`);
|
|
27
|
-
const start2 = process.hrtime();
|
|
28
|
-
const sharedPackageInfoBrowser = await bundleShared(
|
|
29
|
-
sharedBrowser,
|
|
30
|
-
config,
|
|
31
|
-
fedOptions,
|
|
32
|
-
externals,
|
|
33
|
-
{ platform: "browser", bundleName: "browser-shared", chunks: config.chunks }
|
|
34
|
-
);
|
|
35
|
-
logger.measure(start2, "Step 2.1) Bundling all shared browser externals");
|
|
36
|
-
addExternalsToCache(fedOptions.federationCache, sharedPackageInfoBrowser);
|
|
37
|
-
if (signal?.aborted) throw new AbortedError("[buildForFederation] After shared-browser bundle");
|
|
38
|
-
}
|
|
39
|
-
if (Object.keys(sharedServer).length > 0) {
|
|
40
|
-
logger.info(`Bundling external npm packages with bundle type 'server-shared'`);
|
|
41
|
-
const start2 = process.hrtime();
|
|
42
|
-
const sharedPackageInfoServer = await bundleShared(
|
|
43
|
-
sharedServer,
|
|
44
|
-
config,
|
|
45
|
-
fedOptions,
|
|
46
|
-
externals,
|
|
47
|
-
{ platform: "node", bundleName: "node-shared", chunks: config.chunks }
|
|
48
|
-
);
|
|
49
|
-
logger.measure(start2, "Step 2.1) Bundling all shared node externals");
|
|
50
|
-
addExternalsToCache(fedOptions.federationCache, sharedPackageInfoServer);
|
|
51
|
-
if (signal?.aborted) throw new AbortedError("[buildForFederation] After shared-node bundle");
|
|
52
|
-
}
|
|
53
|
-
if (Object.keys(separateBrowser).length > 0) {
|
|
54
|
-
logger.info(`Bundling external npm packages with bundle type 'browser-separate'`);
|
|
55
|
-
const start2 = process.hrtime();
|
|
56
|
-
const separatePackageInfoBrowser = await bundleSeparatePackages(
|
|
57
|
-
separateBrowser,
|
|
58
|
-
externals,
|
|
59
|
-
config,
|
|
60
|
-
fedOptions,
|
|
61
|
-
{ platform: "browser" }
|
|
62
|
-
);
|
|
63
|
-
logger.measure(start2, "Step 2.2) Bundling all separate browser external packages");
|
|
64
|
-
addExternalsToCache(fedOptions.federationCache, separatePackageInfoBrowser);
|
|
65
|
-
if (signal?.aborted)
|
|
66
|
-
throw new AbortedError("[buildForFederation] After separate-browser bundle");
|
|
67
|
-
}
|
|
68
|
-
if (Object.keys(separateServer).length > 0) {
|
|
69
|
-
logger.info(`Bundling external npm packages with bundle type 'node-separate'`);
|
|
70
|
-
const start2 = process.hrtime();
|
|
71
|
-
const separatePackageInfoServer = await bundleSeparatePackages(
|
|
72
|
-
separateServer,
|
|
73
|
-
externals,
|
|
74
|
-
config,
|
|
75
|
-
fedOptions,
|
|
76
|
-
{ platform: "node" }
|
|
77
|
-
);
|
|
78
|
-
logger.measure(start2, "Step 2.2) Bundling all separate node external packages");
|
|
79
|
-
addExternalsToCache(fedOptions.federationCache, separatePackageInfoServer);
|
|
80
|
-
}
|
|
81
|
-
if (signal?.aborted) throw new AbortedError("[buildForFederation] After separate-node bundle");
|
|
22
|
+
await executeSharedBundlePlans(planSharedBundles(config, externals), config, fedOptions, signal);
|
|
82
23
|
const start = process.hrtime();
|
|
83
24
|
const artifactInfo = await bundleExposedAndMappings(
|
|
84
25
|
config,
|
|
@@ -122,81 +63,38 @@ async function buildForFederation(config, fedOptions, externals, signal) {
|
|
|
122
63
|
writeImportMap(fedOptions.federationCache, fedOptions, federationInfo.integrity);
|
|
123
64
|
return federationInfo;
|
|
124
65
|
}
|
|
125
|
-
async function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
66
|
+
async function executeSharedBundlePlans(plans, config, fedOptions, signal) {
|
|
67
|
+
for (const plan of plans.filter((p) => p.kind === "shared")) {
|
|
68
|
+
logger.info(`Bundling external npm packages with bundle type '${plan.bundleName}'`);
|
|
69
|
+
const start = process.hrtime();
|
|
70
|
+
const info = await bundleShared(plan.entries, config, fedOptions, plan.externals, {
|
|
71
|
+
platform: plan.platform,
|
|
72
|
+
bundleName: plan.bundleName,
|
|
73
|
+
chunks: plan.chunks
|
|
74
|
+
});
|
|
75
|
+
logger.measure(start, `Step 2.1) Bundling '${plan.bundleName}' externals`);
|
|
76
|
+
addExternalsToCache(fedOptions.federationCache, info);
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
throw new AbortedError(`[buildForFederation] After ${plan.bundleName} bundle`);
|
|
136
79
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
fedOptions,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const buildResults = await Promise.all(bundlePromises);
|
|
153
|
-
return buildResults.reduce(
|
|
154
|
-
(acc, r) => {
|
|
155
|
-
let chunks = acc.chunks;
|
|
156
|
-
if (r.chunks) {
|
|
157
|
-
chunks = { ...acc.chunks ?? {}, ...r.chunks };
|
|
158
|
-
}
|
|
159
|
-
let integrity = acc.integrity;
|
|
160
|
-
if (r.integrity) {
|
|
161
|
-
integrity = { ...acc.integrity ?? {}, ...r.integrity };
|
|
162
|
-
}
|
|
163
|
-
return {
|
|
164
|
-
externals: [...acc.externals, ...r.externals],
|
|
165
|
-
chunks,
|
|
166
|
-
integrity
|
|
167
|
-
};
|
|
168
|
-
},
|
|
169
|
-
{ externals: [] }
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
function splitShared(shared) {
|
|
173
|
-
const sharedServer = {};
|
|
174
|
-
const sharedBrowser = {};
|
|
175
|
-
const separateBrowser = {};
|
|
176
|
-
const separateServer = {};
|
|
177
|
-
for (const key in shared) {
|
|
178
|
-
const obj = shared[key];
|
|
179
|
-
if (obj?.platform === "node") {
|
|
180
|
-
if (obj.build === "default") {
|
|
181
|
-
sharedServer[key] = obj;
|
|
182
|
-
} else {
|
|
183
|
-
separateServer[key] = obj;
|
|
184
|
-
}
|
|
185
|
-
} else if (obj?.platform === "browser") {
|
|
186
|
-
if (obj.build === "default") {
|
|
187
|
-
sharedBrowser[key] = obj;
|
|
188
|
-
} else {
|
|
189
|
-
separateBrowser[key] = obj;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
80
|
+
const separatePlans = plans.filter((p) => p.kind === "separate");
|
|
81
|
+
if (separatePlans.length > 0) {
|
|
82
|
+
const start = process.hrtime();
|
|
83
|
+
const results = await Promise.all(
|
|
84
|
+
separatePlans.map(
|
|
85
|
+
(plan) => bundleShared(plan.entries, config, fedOptions, plan.externals, {
|
|
86
|
+
platform: plan.platform,
|
|
87
|
+
bundleName: plan.bundleName,
|
|
88
|
+
chunks: plan.chunks
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
logger.measure(start, "Step 2.2) Bundling all separate external packages");
|
|
93
|
+
for (const info of results) addExternalsToCache(fedOptions.federationCache, info);
|
|
94
|
+
if (signal?.aborted) throw new AbortedError("[buildForFederation] After separate bundle");
|
|
192
95
|
}
|
|
193
|
-
return {
|
|
194
|
-
sharedBrowser,
|
|
195
|
-
sharedServer,
|
|
196
|
-
separateBrowser,
|
|
197
|
-
separateServer
|
|
198
|
-
};
|
|
199
96
|
}
|
|
200
97
|
export {
|
|
201
|
-
buildForFederation
|
|
98
|
+
buildForFederation,
|
|
99
|
+
executeSharedBundlePlans
|
|
202
100
|
};
|
|
@@ -5,6 +5,7 @@ import type { HashPort, IoPort } from '../../domain/utils/io-port.contract.js';
|
|
|
5
5
|
import { type NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
6
6
|
import type { NormalizedExternalConfig } from '../../domain/config/external-config.contract.js';
|
|
7
7
|
import type { NFBuildAdapter } from '../../domain/core/build-adapter.contract.js';
|
|
8
|
+
import { type ModuleEvaluator } from './synthesize-cjs-exports.js';
|
|
8
9
|
export declare function bundleShared(sharedBundles: Record<string, NormalizedExternalConfig>, config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], buildOptions: {
|
|
9
10
|
platform: 'browser' | 'node';
|
|
10
11
|
bundleName: string;
|
|
@@ -18,6 +19,12 @@ interface BundleSharedDeps {
|
|
|
18
19
|
io: IoPort;
|
|
19
20
|
repo: PackageJsonRepository;
|
|
20
21
|
adapter: NFBuildAdapter;
|
|
22
|
+
/**
|
|
23
|
+
* Loads a module by absolute path at build time (Node `require`) and returns its
|
|
24
|
+
* runtime exports, from which a CJS external's named exports are then enumerated.
|
|
25
|
+
* Omitted → no named-export synthesis; externals stay default-only.
|
|
26
|
+
*/
|
|
27
|
+
evaluateModule?: ModuleEvaluator;
|
|
21
28
|
}
|
|
22
29
|
export declare function bundleSharedCore(deps: BundleSharedDeps, sharedBundles: Record<string, NormalizedExternalConfig>, config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], buildOptions: {
|
|
23
30
|
platform: 'browser' | 'node';
|
|
@@ -9,12 +9,21 @@ import { DEFAULT_EXTERNAL_LIST } from "./default-external-list.js";
|
|
|
9
9
|
import { isSourceFile, transformChunkImports } from "./rewrite-chunk-imports.js";
|
|
10
10
|
import { toChunkImport } from "../../domain/core/chunk.js";
|
|
11
11
|
import { cacheEntryCore, getChecksumCore, getFilename } from "../cache/cache-persistence.js";
|
|
12
|
+
import { linkedContentSignals } from "./resolve-shared-dirs.js";
|
|
12
13
|
import { computeIntegrityMapCore } from "./compute-integrity.js";
|
|
13
14
|
import { fileURLToPath } from "url";
|
|
14
15
|
import { getBuildAdapter } from "./build-adapter.js";
|
|
16
|
+
import { synthesizeCjsNamedExportsEntry } from "./synthesize-cjs-exports.js";
|
|
17
|
+
import { createRequire } from "module";
|
|
15
18
|
async function bundleShared(sharedBundles, config, fedOptions, externals, buildOptions) {
|
|
19
|
+
const requireFromWorkspace = createRequire(path.join(fedOptions.workspaceRoot, "index.js"));
|
|
16
20
|
return bundleSharedCore(
|
|
17
|
-
{
|
|
21
|
+
{
|
|
22
|
+
io: nodeIo,
|
|
23
|
+
repo: sharedPackageJsonRepository,
|
|
24
|
+
adapter: getBuildAdapter(),
|
|
25
|
+
evaluateModule: (absPath) => requireFromWorkspace(absPath)
|
|
26
|
+
},
|
|
18
27
|
sharedBundles,
|
|
19
28
|
config,
|
|
20
29
|
fedOptions,
|
|
@@ -25,13 +34,21 @@ async function bundleShared(sharedBundles, config, fedOptions, externals, buildO
|
|
|
25
34
|
async function bundleSharedCore(deps, sharedBundles, config, fedOptions, externals, buildOptions) {
|
|
26
35
|
const builderPackageJson = readBuilderPackageJson(deps.io, fileURLToPath(import.meta.url));
|
|
27
36
|
const builderVersion = parseBuilderVersion(builderPackageJson);
|
|
37
|
+
const folder = fedOptions.packageJson ? path.dirname(fedOptions.packageJson) : fedOptions.workspaceRoot;
|
|
38
|
+
const contentSignals = linkedContentSignals(
|
|
39
|
+
Object.keys(sharedBundles),
|
|
40
|
+
folder,
|
|
41
|
+
deps.io,
|
|
42
|
+
deps.repo
|
|
43
|
+
);
|
|
28
44
|
const checksum = getChecksumCore(
|
|
29
45
|
deps.io,
|
|
30
46
|
sharedBundles,
|
|
31
47
|
fedOptions.dev ? "1" : "0",
|
|
32
|
-
builderVersion
|
|
48
|
+
builderVersion,
|
|
49
|
+
config.features.synthesizeCjsExports,
|
|
50
|
+
contentSignals
|
|
33
51
|
);
|
|
34
|
-
const folder = fedOptions.packageJson ? path.dirname(fedOptions.packageJson) : fedOptions.workspaceRoot;
|
|
35
52
|
const bundleCache = cacheEntryCore(
|
|
36
53
|
deps.io,
|
|
37
54
|
fedOptions.federationCache.cachePath,
|
|
@@ -67,8 +84,22 @@ async function bundleSharedCore(deps, sharedBundles, config, fedOptions, externa
|
|
|
67
84
|
const configState = `BUNDLER_CHUNKS;${builderVersion};${JSON.stringify(config)}`;
|
|
68
85
|
const entryPoints = packageInfos.map((pi) => {
|
|
69
86
|
const encName = pi.packageName.replace(/[^A-Za-z0-9]/g, "_");
|
|
70
|
-
const outName = createOutName(
|
|
71
|
-
|
|
87
|
+
const outName = createOutName(
|
|
88
|
+
deps.io,
|
|
89
|
+
pi,
|
|
90
|
+
configState,
|
|
91
|
+
fedOptions,
|
|
92
|
+
encName,
|
|
93
|
+
contentSignals[pi.packageName] ?? ""
|
|
94
|
+
);
|
|
95
|
+
const synthetic = deps.evaluateModule && config.features.synthesizeCjsExports ? synthesizeCjsNamedExportsEntry(
|
|
96
|
+
deps.io,
|
|
97
|
+
deps.evaluateModule,
|
|
98
|
+
pi,
|
|
99
|
+
fedOptions.federationCache.cachePath,
|
|
100
|
+
outName
|
|
101
|
+
) : null;
|
|
102
|
+
return { fileName: synthetic ?? pi.entryPoint, outName };
|
|
72
103
|
});
|
|
73
104
|
const fullOutputPath = path.join(fedOptions.workspaceRoot, fedOptions.outputPath);
|
|
74
105
|
const useDefaultExternalList = buildOptions.platform === "browser" && !config.features.ignoreUnusedDeps;
|
|
@@ -177,8 +208,8 @@ function applyRenames(bundleResult, entryPoints, renamed) {
|
|
|
177
208
|
if (next) ep.outName = next;
|
|
178
209
|
}
|
|
179
210
|
}
|
|
180
|
-
function createOutName(io, pi, configState, fedOptions, encName) {
|
|
181
|
-
const hashBase = pi.version + "_" + pi.entryPoint + "_" + configState;
|
|
211
|
+
function createOutName(io, pi, configState, fedOptions, encName, contentSignal = "") {
|
|
212
|
+
const hashBase = pi.version + "_" + pi.entryPoint + "_" + configState + (contentSignal ? "_" + contentSignal : "");
|
|
182
213
|
const hash = calcHashCore(io, hashBase);
|
|
183
214
|
const outName = fedOptions.dev ? `${encName}.${hash}-dev.js` : `${encName}.${hash}.js`;
|
|
184
215
|
return outName;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { FederationInfo } from '../../domain/core/federation-info.contract.js';
|
|
2
2
|
import type { NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
3
3
|
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
4
|
+
import type { IoPort } from '../../domain/utils/io-port.contract.js';
|
|
5
|
+
import type { PackageJsonRepository } from '../../domain/utils/package-json.contract.js';
|
|
4
6
|
export declare function rebuildForFederation(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], modifiedFiles: string[], signal?: AbortSignal): Promise<FederationInfo>;
|
|
7
|
+
/**
|
|
8
|
+
* When a modified file belongs to a shared package (e.g. npm-linked dev dep),
|
|
9
|
+
* re-bundle the affected bundle(s); unchanged bundles hit their cache, so
|
|
10
|
+
* `federationCache.externals` regenerates cheaply. No package touched → no-op.
|
|
11
|
+
*/
|
|
12
|
+
export declare function rebuildAffectedExternals(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], modifiedFiles: string[], signal?: AbortSignal, io?: IoPort, repo?: PackageJsonRepository): Promise<void>;
|
|
@@ -7,8 +7,15 @@ import { writeFederationInfo } from "../output/write-federation-info.js";
|
|
|
7
7
|
import { writeImportMap } from "../output/write-import-map.js";
|
|
8
8
|
import { logger } from "../../utils/logger.js";
|
|
9
9
|
import { AbortedError } from "../../utils/errors.js";
|
|
10
|
+
import { planSharedBundles } from "./shared-bundle-plan.js";
|
|
11
|
+
import { executeSharedBundlePlans } from "./build-for-federation.js";
|
|
12
|
+
import { affectedSharedKeys, resolveSharedPackageDirs } from "./resolve-shared-dirs.js";
|
|
13
|
+
import { cacheEntryCore, getFilename } from "../cache/cache-persistence.js";
|
|
14
|
+
import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
15
|
+
import { sharedPackageJsonRepository } from "../../utils/package/package-info.js";
|
|
10
16
|
async function rebuildForFederation(config, fedOptions, externals, modifiedFiles, signal) {
|
|
11
17
|
const federationCache = fedOptions.federationCache;
|
|
18
|
+
await rebuildAffectedExternals(config, fedOptions, externals, modifiedFiles, signal);
|
|
12
19
|
logger.info(`Re-bundling all internal libraries and exposed modules..'`);
|
|
13
20
|
const start = process.hrtime();
|
|
14
21
|
const artifactInfo = await bundleExposedAndMappings(
|
|
@@ -47,6 +54,25 @@ async function rebuildForFederation(config, fedOptions, externals, modifiedFiles
|
|
|
47
54
|
writeImportMap(federationCache, fedOptions, federationInfo.integrity);
|
|
48
55
|
return federationInfo;
|
|
49
56
|
}
|
|
57
|
+
async function rebuildAffectedExternals(config, fedOptions, externals, modifiedFiles, signal, io = nodeIo, repo = sharedPackageJsonRepository) {
|
|
58
|
+
if (modifiedFiles.length === 0) return;
|
|
59
|
+
const plans = planSharedBundles(config, externals);
|
|
60
|
+
if (plans.length === 0) return;
|
|
61
|
+
const dirs = resolveSharedPackageDirs(config, fedOptions, io, repo);
|
|
62
|
+
const affected = affectedSharedKeys(modifiedFiles, dirs, io);
|
|
63
|
+
const affectedPlans = plans.filter((p) => p.keys.some((k) => affected.has(k)));
|
|
64
|
+
if (affectedPlans.length === 0) return;
|
|
65
|
+
const federationCache = fedOptions.federationCache;
|
|
66
|
+
for (const plan of affectedPlans) {
|
|
67
|
+
logger.info(`Detected change in linked shared package(s); re-bundling '${plan.bundleName}'.`);
|
|
68
|
+
cacheEntryCore(io, federationCache.cachePath, getFilename(plan.bundleName, fedOptions.dev)).clear();
|
|
69
|
+
}
|
|
70
|
+
federationCache.externals = [];
|
|
71
|
+
federationCache.chunks = void 0;
|
|
72
|
+
federationCache.integrity = void 0;
|
|
73
|
+
await executeSharedBundlePlans(plans, config, fedOptions, signal);
|
|
74
|
+
}
|
|
50
75
|
export {
|
|
76
|
+
rebuildAffectedExternals,
|
|
51
77
|
rebuildForFederation
|
|
52
78
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
2
|
+
import type { NormalizedFederationOptions } from '../../domain/core/federation-options.contract.js';
|
|
3
|
+
import type { FileReaderPort } from '../../domain/utils/io-port.contract.js';
|
|
4
|
+
import type { PackageJsonRepository } from '../../domain/utils/package-json.contract.js';
|
|
5
|
+
/** Each `config.shared` key → its realpath'd package dir, so watcher, checksum, and
|
|
6
|
+
* file-mapping agree on one identity for symlinked (npm-linked) deps. */
|
|
7
|
+
export declare function resolveSharedPackageDirs(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, io?: FileReaderPort, repo?: PackageJsonRepository): Map<string, string>;
|
|
8
|
+
/** Realpath'd dirs of symlinked shared packages — the bounded watch set.
|
|
9
|
+
* Deduped, since secondaries share a package dir. */
|
|
10
|
+
export declare function linkedSharedDirs(config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, io?: FileReaderPort, repo?: PackageJsonRepository): string[];
|
|
11
|
+
/** Per-key content signal (max mtime of the resolved dir) for symlinked deps only.
|
|
12
|
+
* Registry deps get no signal, keeping their checksum version-only. (Every key is
|
|
13
|
+
* still resolved: detecting the symlink requires the realpath + lstat.) */
|
|
14
|
+
export declare function linkedContentSignals(keys: string[], folder: string, io?: FileReaderPort, repo?: PackageJsonRepository): Record<string, string>;
|
|
15
|
+
/** `config.shared` keys whose package directory contains at least one modified file. */
|
|
16
|
+
export declare function affectedSharedKeys(modifiedFiles: readonly string[], dirs: Map<string, string>, io?: FileReaderPort): Set<string>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { nodeIo } from "../../utils/io/node-io-adapter.js";
|
|
3
|
+
import { sharedPackageJsonRepository } from "../../utils/package/package-info.js";
|
|
4
|
+
import { toPosix } from "../../utils/path-patterns.js";
|
|
5
|
+
const folderOf = (fedOptions) => fedOptions.packageJson ? path.dirname(fedOptions.packageJson) : fedOptions.workspaceRoot;
|
|
6
|
+
function resolveEntries(keys, folder, io, repo) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const key of keys) {
|
|
9
|
+
const pkgJsonPath = repo.findDepPackageJson(key, folder);
|
|
10
|
+
if (!pkgJsonPath) continue;
|
|
11
|
+
const pkgDir = path.dirname(pkgJsonPath);
|
|
12
|
+
out.push({
|
|
13
|
+
key,
|
|
14
|
+
realDir: toPosix(io.realpath(pkgDir)),
|
|
15
|
+
isSymlink: !!io.stat(pkgDir)?.isSymbolicLink
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function resolveSharedPackageDirs(config, fedOptions, io = nodeIo, repo = sharedPackageJsonRepository) {
|
|
21
|
+
const entries = resolveEntries(Object.keys(config.shared), folderOf(fedOptions), io, repo);
|
|
22
|
+
return new Map(entries.map((e) => [e.key, e.realDir]));
|
|
23
|
+
}
|
|
24
|
+
function linkedSharedDirs(config, fedOptions, io = nodeIo, repo = sharedPackageJsonRepository) {
|
|
25
|
+
const entries = resolveEntries(Object.keys(config.shared), folderOf(fedOptions), io, repo);
|
|
26
|
+
return [...new Set(entries.filter((e) => e.isSymlink).map((e) => e.realDir))];
|
|
27
|
+
}
|
|
28
|
+
function maxMtime(io, dir) {
|
|
29
|
+
let max = 0;
|
|
30
|
+
const walk = (d) => {
|
|
31
|
+
for (const name of io.readDir(d)) {
|
|
32
|
+
const full = path.join(d, name);
|
|
33
|
+
if (io.isDirectory(full)) walk(full);
|
|
34
|
+
else {
|
|
35
|
+
let s = io.stat(full);
|
|
36
|
+
if (s?.isSymbolicLink) s = io.stat(io.realpath(full));
|
|
37
|
+
if (s && s.mtimeMs > max) max = s.mtimeMs;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
walk(dir);
|
|
42
|
+
return max;
|
|
43
|
+
}
|
|
44
|
+
function linkedContentSignals(keys, folder, io = nodeIo, repo = sharedPackageJsonRepository) {
|
|
45
|
+
const signals = {};
|
|
46
|
+
for (const entry of resolveEntries(keys, folder, io, repo)) {
|
|
47
|
+
if (entry.isSymlink) signals[entry.key] = String(maxMtime(io, entry.realDir));
|
|
48
|
+
}
|
|
49
|
+
return signals;
|
|
50
|
+
}
|
|
51
|
+
function affectedSharedKeys(modifiedFiles, dirs, io = nodeIo) {
|
|
52
|
+
const affected = /* @__PURE__ */ new Set();
|
|
53
|
+
if (modifiedFiles.length === 0 || dirs.size === 0) return affected;
|
|
54
|
+
const realFiles = modifiedFiles.map((f) => toPosix(io.realpath(f)));
|
|
55
|
+
for (const [key, dir] of dirs) {
|
|
56
|
+
const prefix = dir.endsWith("/") ? dir : dir + "/";
|
|
57
|
+
if (realFiles.some((f) => f === dir || f.startsWith(prefix))) affected.add(key);
|
|
58
|
+
}
|
|
59
|
+
return affected;
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
affectedSharedKeys,
|
|
63
|
+
linkedContentSignals,
|
|
64
|
+
linkedSharedDirs,
|
|
65
|
+
resolveSharedPackageDirs
|
|
66
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NormalizedFederationConfig } from '../../domain/config/federation-config.contract.js';
|
|
2
|
+
import type { NormalizedExternalConfig } from '../../domain/config/external-config.contract.js';
|
|
3
|
+
export interface SharedBundlePlan {
|
|
4
|
+
bundleName: string;
|
|
5
|
+
platform: 'browser' | 'node';
|
|
6
|
+
chunks: boolean;
|
|
7
|
+
entries: Record<string, NormalizedExternalConfig>;
|
|
8
|
+
externals: string[];
|
|
9
|
+
keys: string[];
|
|
10
|
+
kind: 'shared' | 'separate';
|
|
11
|
+
}
|
|
12
|
+
type SplitSharedResult = {
|
|
13
|
+
sharedServer: Record<string, NormalizedExternalConfig>;
|
|
14
|
+
sharedBrowser: Record<string, NormalizedExternalConfig>;
|
|
15
|
+
separateBrowser: Record<string, NormalizedExternalConfig>;
|
|
16
|
+
separateServer: Record<string, NormalizedExternalConfig>;
|
|
17
|
+
};
|
|
18
|
+
export declare function splitShared(shared: Record<string, NormalizedExternalConfig>): SplitSharedResult;
|
|
19
|
+
/** Single source of truth for package → bundle(name) mapping, shared by the initial
|
|
20
|
+
* build and the watch rebuild so both target the same cache entries. */
|
|
21
|
+
export declare function planSharedBundles(config: NormalizedFederationConfig, externals: string[]): SharedBundlePlan[];
|
|
22
|
+
export {};
|