@softarc/native-federation 4.0.0-RC7 → 4.0.0-RC9

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.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/src/index.d.ts +1 -2
  3. package/src/index.js +1 -2
  4. package/src/internal.d.ts +3 -2
  5. package/src/internal.js +1 -0
  6. package/src/lib/config/default-skip-list.js +3 -15
  7. package/src/lib/config/remove-unused-deps.d.ts +3 -0
  8. package/src/lib/config/remove-unused-deps.js +10 -0
  9. package/src/lib/config/share-utils.d.ts +0 -1
  10. package/src/lib/config/share-utils.js +7 -13
  11. package/src/lib/config/with-native-federation.js +40 -61
  12. package/src/lib/core/build-for-federation.js +30 -30
  13. package/src/lib/core/bundle-exposed-and-mappings.js +15 -17
  14. package/src/lib/core/bundle-shared.d.ts +1 -0
  15. package/src/lib/core/bundle-shared.js +8 -7
  16. package/src/lib/core/federation-builder.d.ts +6 -1
  17. package/src/lib/core/federation-builder.js +18 -8
  18. package/src/lib/core/get-externals.js +1 -1
  19. package/src/lib/core/normalize-options.d.ts +12 -0
  20. package/src/lib/core/normalize-options.js +90 -0
  21. package/src/lib/core/rebuild-for-federation.js +2 -2
  22. package/src/lib/domain/config/external-config.contract.d.ts +3 -1
  23. package/src/lib/domain/config/federation-config.contract.d.ts +8 -2
  24. package/src/lib/domain/config/with-native-federation.contract.d.ts +2 -0
  25. package/src/lib/domain/config/with-native-federation.contract.js +1 -0
  26. package/src/lib/domain/core/build-adapter.contract.d.ts +12 -6
  27. package/src/lib/domain/core/federation-options.contract.d.ts +4 -5
  28. package/src/lib/domain/core/index.d.ts +1 -1
  29. package/src/lib/domain/utils/index.d.ts +1 -1
  30. package/src/lib/domain/utils/mapped-path.contract.d.ts +1 -4
  31. package/src/lib/domain/utils/mapped-path.contract.js +4 -0
  32. package/src/lib/domain/utils/used-dependencies.contract.d.ts +5 -0
  33. package/src/lib/domain/utils/used-dependencies.contract.js +1 -0
  34. package/src/lib/utils/get-used-dependencies.d.ts +7 -0
  35. package/src/lib/utils/get-used-dependencies.js +123 -0
  36. package/src/lib/utils/mapped-paths.d.ts +7 -7
  37. package/src/lib/utils/mapped-paths.js +14 -12
  38. package/src/lib/utils/rebuild-queue.d.ts +14 -1
  39. package/src/lib/utils/rebuild-queue.js +32 -12
  40. package/src/lib/utils/resolve-wildcard-keys.d.ts +29 -2
  41. package/src/lib/utils/resolve-wildcard-keys.js +105 -38
  42. package/src/lib/core/default-server-deps-list.d.ts +0 -2
  43. package/src/lib/core/default-server-deps-list.js +0 -6
  44. package/src/lib/core/load-federation-config.d.ts +0 -3
  45. package/src/lib/core/load-federation-config.js +0 -18
  46. package/src/lib/core/normalize-federation-options.d.ts +0 -4
  47. package/src/lib/core/normalize-federation-options.js +0 -10
  48. package/src/lib/core/remove-unused-deps.d.ts +0 -2
  49. package/src/lib/core/remove-unused-deps.js +0 -90
  50. package/src/lib/utils/config-utils.d.ts +0 -2
  51. package/src/lib/utils/config-utils.js +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softarc/native-federation",
3
- "version": "4.0.0-RC7",
3
+ "version": "4.0.0-RC9",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "dependencies": {
package/src/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  export { setBuildAdapter } from './lib/core/build-adapter.js';
2
2
  export { buildForFederation } from './lib/core/build-for-federation.js';
3
3
  export { rebuildForFederation } from './lib/core/rebuild-for-federation.js';
4
- export { normalizeFederationOptions } from './lib/core/normalize-federation-options.js';
5
4
  export { createFederationCache } from './lib/core/federation-cache.js';
6
5
  export { bundleExposedAndMappings } from './lib/core/bundle-exposed-and-mappings.js';
7
6
  export { getExternals } from './lib/core/get-externals.js';
8
- export { loadFederationConfig } from './lib/core/load-federation-config.js';
7
+ export { normalizeFederationOptions } from './lib/core/normalize-options.js';
9
8
  export { writeFederationInfo } from './lib/core/write-federation-info.js';
10
9
  export { type BuildHelperParams, federationBuilder } from './lib/core/federation-builder.js';
11
10
  export * from './domain.js';
package/src/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  export { setBuildAdapter } from './lib/core/build-adapter.js';
2
2
  export { buildForFederation } from './lib/core/build-for-federation.js';
3
3
  export { rebuildForFederation } from './lib/core/rebuild-for-federation.js';
4
- export { normalizeFederationOptions } from './lib/core/normalize-federation-options.js';
5
4
  export { createFederationCache } from './lib/core/federation-cache.js';
6
5
  export { bundleExposedAndMappings } from './lib/core/bundle-exposed-and-mappings.js';
7
6
  export { getExternals } from './lib/core/get-externals.js';
8
- export { loadFederationConfig } from './lib/core/load-federation-config.js';
7
+ export { normalizeFederationOptions } from './lib/core/normalize-options.js';
9
8
  export { writeFederationInfo } from './lib/core/write-federation-info.js';
10
9
  export { federationBuilder } from './lib/core/federation-builder.js';
11
10
  export * from './domain.js';
package/src/internal.d.ts CHANGED
@@ -2,11 +2,12 @@ export * from './lib/utils/build-result-map.js';
2
2
  export { hashFile } from './lib/utils/hash-file.js';
3
3
  export * from './lib/utils/errors.js';
4
4
  export { logger, setLogLevel } from './lib/utils/logger.js';
5
- export type { MappedPath } from './lib/domain/utils/mapped-path.contract.js';
6
- export { RebuildQueue } from './lib/utils/rebuild-queue.js';
5
+ export type { PathToImport } from './lib/domain/utils/mapped-path.contract.js';
6
+ export { RebuildQueue, type TrackResult } from './lib/utils/rebuild-queue.js';
7
7
  export { AbortedError } from './lib/utils/errors.js';
8
8
  export { createBuildResultMap, lookupInResultMap, popFromResultMap, } from './lib/utils/build-result-map.js';
9
9
  export { writeImportMap } from './lib/core/write-import-map.js';
10
10
  export type { NormalizedExternalConfig, NormalizedSharedExternalsConfig, } from './lib/domain/config/external-config.contract.js';
11
11
  export type { NormalizedFederationConfig } from './lib/domain/config/federation-config.contract.js';
12
12
  export { getDefaultCachePath, getChecksum } from './lib/utils/cache-persistence.js';
13
+ export { isInSkipList, prepareSkipList } from './lib/config/default-skip-list.js';
package/src/internal.js CHANGED
@@ -7,3 +7,4 @@ export { AbortedError } from './lib/utils/errors.js';
7
7
  export { createBuildResultMap, lookupInResultMap, popFromResultMap, } from './lib/utils/build-result-map.js';
8
8
  export { writeImportMap } from './lib/core/write-import-map.js';
9
9
  export { getDefaultCachePath, getChecksum } from './lib/utils/cache-persistence.js';
10
+ export { isInSkipList, prepareSkipList } from './lib/config/default-skip-list.js';
@@ -1,26 +1,14 @@
1
1
  export const DEFAULT_SKIP_LIST = [
2
- '@softarc/native-federation-runtime',
3
2
  '@softarc/native-federation',
4
3
  '@softarc/native-federation-core',
5
4
  '@softarc/native-federation-node',
6
5
  '@softarc/native-federation-esbuild',
7
- '@angular-architects/native-federation',
8
- '@angular-architects/native-federation-runtime',
6
+ '@softarc/native-federation-runtime',
7
+ '@softarc/native-federation-orchestrator',
8
+ 'vanilla-native-federation',
9
9
  'es-module-shims',
10
- 'zone.js',
11
10
  'tslib/',
12
- '@angular/localize',
13
- '@angular/localize/init',
14
- '@angular/localize/tools',
15
- // '@angular/platform-server',
16
- // '@angular/platform-server/init',
17
- // '@angular/ssr',
18
- /\/schematics(\/|$)/,
19
- /^@nx\/angular/,
20
- pkg => pkg.startsWith('@angular/') && !!pkg.match(/\/testing(\/|$)/),
21
11
  pkg => pkg.startsWith('@types/'),
22
- 'express',
23
- // (pkg) => pkg.startsWith('@angular/common/locales'),
24
12
  ];
25
13
  export function prepareSkipList(skipList) {
26
14
  return {
@@ -0,0 +1,3 @@
1
+ import type { NormalizedFederationConfig } from '../domain/config/federation-config.contract.js';
2
+ import type { UsedDependencies } from '../domain/utils/used-dependencies.contract.js';
3
+ export declare function removeUnusedDeps(usedDependencies: UsedDependencies, config: NormalizedFederationConfig): NormalizedFederationConfig;
@@ -0,0 +1,10 @@
1
+ export function removeUnusedDeps(usedDependencies, config) {
2
+ const filteredDependencies = Object.entries(config.shared)
3
+ .filter(([shared, meta]) => !!meta.includeSecondaries || usedDependencies.external.has(shared))
4
+ .reduce((acc, [shared, meta]) => ({ ...acc, [shared]: meta }), {});
5
+ return {
6
+ ...config,
7
+ shared: filteredDependencies,
8
+ sharedMappings: usedDependencies.internal,
9
+ };
10
+ }
@@ -1,6 +1,5 @@
1
1
  import { type SkipList } from '../domain/config/skip-list.contract.js';
2
2
  import type { ShareAllExternalsOptions, ShareExternalsOptions } from '../domain/config/external-config.contract.js';
3
- export declare const DEFAULT_SECONDARIES_SKIP_LIST: string[];
4
3
  export declare function findRootTsConfigJson(): string;
5
4
  export declare function shareAll(config: ShareAllExternalsOptions, opts?: {
6
5
  skipList?: SkipList;
@@ -5,9 +5,8 @@ import { DEFAULT_SKIP_LIST, isInSkipList, prepareSkipList } from './default-skip
5
5
  import { findDepPackageJson, getVersionMaps } from '../utils/package-info.js';
6
6
  import { getConfigContext } from './configuration-context.js';
7
7
  import { logger } from '../utils/logger.js';
8
- import { resolveWildcardKeys } from '../utils/resolve-wildcard-keys.js';
8
+ import { resolvePackageJsonExportsWildcard } from '../utils/resolve-wildcard-keys.js';
9
9
  let inferVersion = false;
10
- export const DEFAULT_SECONDARIES_SKIP_LIST = ['@angular/router/upgrade', '@angular/common/upgrade'];
11
10
  export function findRootTsConfigJson() {
12
11
  const packageJson = findPackageJson(cwd());
13
12
  const projectRoot = path.dirname(packageJson);
@@ -49,9 +48,6 @@ function lookupVersionInMap(key, versions) {
49
48
  else {
50
49
  key = parts[0];
51
50
  }
52
- if (key.toLowerCase() === '@angular-architects/module-federation-runtime') {
53
- key = '@angular-architects/module-federation';
54
- }
55
51
  if (!versions[key]) {
56
52
  return null;
57
53
  }
@@ -82,7 +78,7 @@ function findSecondaries(libPath, excludes, shareObject, preparedSkipList) {
82
78
  return acc;
83
79
  }
84
80
  function getSecondaries(includeSecondaries, libPath, key, shareObject, preparedSkipList) {
85
- let exclude = [...DEFAULT_SECONDARIES_SKIP_LIST];
81
+ let exclude = [];
86
82
  let resolveGlob = false;
87
83
  if (typeof includeSecondaries === 'object') {
88
84
  if (Array.isArray(includeSecondaries.skip)) {
@@ -166,7 +162,7 @@ function resolveGlobSecondaries(key, libPath, parent, secondaryName, entry, excl
166
162
  if (key.includes('*')) {
167
163
  if (!resolveGlob)
168
164
  return items;
169
- const expanded = resolveWildcardKeys(key, entry, libPath);
165
+ const expanded = resolvePackageJsonExportsWildcard(key, entry, libPath);
170
166
  items = expanded
171
167
  .map(e => ({
172
168
  key: path.join(parent, e.key),
@@ -221,10 +217,10 @@ export function shareAll(config, opts = {}) {
221
217
  // }
222
218
  const versionMaps = getVersionMaps(projectPath, projectPath);
223
219
  const sharedExternals = {};
224
- const preparedSkipList = prepareSkipList(opts.skipList ?? DEFAULT_SKIP_LIST);
220
+ const skipList = opts.skipList ?? DEFAULT_SKIP_LIST;
225
221
  for (const versions of versionMaps) {
226
222
  for (const key in versions) {
227
- if (isInSkipList(key, preparedSkipList)) {
223
+ if (isInSkipList(key, prepareSkipList(skipList))) {
228
224
  continue;
229
225
  }
230
226
  if (!!opts.overrides && Object.keys(opts.overrides).some(o => key.startsWith(o))) {
@@ -238,10 +234,8 @@ export function shareAll(config, opts = {}) {
238
234
  }
239
235
  }
240
236
  return {
241
- ...share(sharedExternals, opts.projectPath, opts.skipList ?? DEFAULT_SKIP_LIST),
242
- ...(!opts.overrides
243
- ? {}
244
- : share(opts.overrides, opts.projectPath, opts.skipList ?? DEFAULT_SKIP_LIST)),
237
+ ...share(sharedExternals, opts.projectPath, skipList),
238
+ ...(!opts.overrides ? {} : share(opts.overrides, opts.projectPath, skipList)),
245
239
  };
246
240
  }
247
241
  function inferProjectPath(projectPath) {
@@ -1,91 +1,70 @@
1
- import { getMappedPaths } from '../utils/mapped-paths.js';
1
+ import { getRawMappedPaths } from '../utils/mapped-paths.js';
2
2
  import { shareAll, findRootTsConfigJson } from './share-utils.js';
3
3
  import { isInSkipList, prepareSkipList } from './default-skip-list.js';
4
4
  import { logger } from '../utils/logger.js';
5
- import { DEFAULT_SERVER_DEPS_LIST } from '../core/default-server-deps-list.js';
6
5
  export function withNativeFederation(config) {
7
6
  const skip = prepareSkipList(config.skip ?? []);
7
+ const chunks = config.chunks ?? true;
8
8
  const normalized = {
9
+ $type: 'classic',
9
10
  name: config.name ?? '',
10
11
  exposes: config.exposes ?? {},
11
- shared: normalizeShared(config, skip),
12
- sharedMappings: normalizeSharedMappings(config, skip),
12
+ shared: normalizeShared(config, skip, chunks),
13
+ sharedMappings: removeSkippedMappings(config, skip),
14
+ chunks,
13
15
  skip,
14
16
  externals: config.externals ?? [],
15
17
  features: {
16
18
  mappingVersion: config.features?.mappingVersion ?? false,
17
- ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? false,
19
+ ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? true,
20
+ denseChunking: config.features?.denseChunking ?? false,
18
21
  },
19
22
  ...(config.shareScope && { shareScope: config.shareScope }),
20
23
  };
21
- // This is for being backwards compatible
22
- if (!normalized.features.ignoreUnusedDeps) {
23
- normalized.shared = filterShared(normalized.shared);
24
- }
25
24
  return normalized;
26
25
  }
27
- function filterShared(shared) {
28
- const keys = Object.keys(shared).filter(k => !k.startsWith('@angular/common/locales'));
29
- const filtered = keys.reduce((acc, curr) => ({
30
- ...acc,
31
- [curr]: shared[curr],
32
- }), {});
33
- return filtered;
34
- }
35
- function normalizeShared(config, skip) {
26
+ function normalizeShared(config, skip, chunks) {
36
27
  let result = {};
37
- const shared = config.shared;
38
- if (!shared) {
39
- result = shareAll({
28
+ const shared = config.shared ??
29
+ shareAll({
40
30
  singleton: true,
41
31
  strictVersion: true,
42
32
  requiredVersion: 'auto',
43
33
  platform: 'browser',
44
34
  });
45
- }
46
- else {
47
- result = Object.keys(shared).reduce((acc, cur) => {
48
- const key = cur.replace(/\\/g, '/');
49
- const sharedConfig = shared[cur];
50
- const normalizedConfig = {
51
- requiredVersion: sharedConfig.requiredVersion ?? 'auto',
52
- singleton: sharedConfig.singleton ?? false,
53
- strictVersion: sharedConfig.strictVersion ?? false,
54
- version: sharedConfig.version,
55
- includeSecondaries: sharedConfig.includeSecondaries,
56
- packageInfo: sharedConfig.packageInfo,
57
- platform: sharedConfig.platform ?? getDefaultPlatform(cur),
58
- build: sharedConfig.build ?? 'default',
59
- ...(sharedConfig.shareScope && { shareScope: sharedConfig.shareScope }),
60
- };
61
- return {
62
- ...acc,
63
- [key]: normalizedConfig,
64
- };
65
- }, {});
66
- }
35
+ result = Object.keys(shared).reduce((acc, cur) => {
36
+ const key = cur.replace(/\\/g, '/');
37
+ const sharedConfig = shared[cur];
38
+ if (!!sharedConfig.chunks && !sharedConfig.build && sharedConfig.chunks !== chunks) {
39
+ logger.warn(`External '${cur}' has explicit chunk settings, consider switching build type to { build: 'package' }.`);
40
+ sharedConfig.chunks = chunks;
41
+ }
42
+ const normalizedConfig = {
43
+ requiredVersion: sharedConfig.requiredVersion ?? 'auto',
44
+ singleton: sharedConfig.singleton ?? false,
45
+ strictVersion: sharedConfig.strictVersion ?? false,
46
+ version: sharedConfig.version,
47
+ chunks: sharedConfig.chunks ?? chunks,
48
+ includeSecondaries: sharedConfig.includeSecondaries,
49
+ packageInfo: sharedConfig.packageInfo,
50
+ platform: sharedConfig.platform ?? config.platform ?? 'browser',
51
+ build: sharedConfig.build ?? 'default',
52
+ ...(sharedConfig.shareScope && { shareScope: sharedConfig.shareScope }),
53
+ };
54
+ return {
55
+ ...acc,
56
+ [key]: normalizedConfig,
57
+ };
58
+ }, {});
67
59
  result = Object.keys(result)
68
60
  .filter(key => !isInSkipList(key, skip))
69
61
  .reduce((acc, cur) => ({ ...acc, [cur]: result[cur] }), {});
70
62
  return result;
71
63
  }
72
- function normalizeSharedMappings(config, skip) {
64
+ function removeSkippedMappings(config, skipList) {
73
65
  const rootTsConfigPath = findRootTsConfigJson();
74
- const paths = getMappedPaths({
75
- rootTsConfigPath,
76
- sharedMappings: config.sharedMappings,
77
- });
78
- const result = paths.filter(p => !isInSkipList(p.key, skip) && !p.key.includes('*'));
79
- if (paths.find(p => p.key.includes('*'))) {
80
- logger.warn('Sharing mapped paths with wildcards (*) not supported');
81
- }
82
- return result;
83
- }
84
- function getDefaultPlatform(cur) {
85
- if (DEFAULT_SERVER_DEPS_LIST.find(e => cur.startsWith(e))) {
86
- return 'node';
87
- }
88
- else {
89
- return 'browser';
90
- }
66
+ const paths = getRawMappedPaths(rootTsConfigPath, config.sharedMappings);
67
+ return Object.entries(paths)
68
+ .filter(([, _import]) => !isInSkipList(_import, skipList))
69
+ .reduce((acc, [_path, _import]) => ({ ...acc, [_path]: _import }), {});
91
70
  }
@@ -5,62 +5,63 @@ import { writeImportMap } from './write-import-map.js';
5
5
  import { logger } from '../utils/logger.js';
6
6
  import { normalizePackageName } from '../utils/normalize.js';
7
7
  import { AbortedError } from '../utils/errors.js';
8
- import { resolveProjectName } from '../utils/config-utils.js';
9
8
  import { addExternalsToCache } from './federation-cache.js';
10
9
  import path from 'path';
11
10
  export async function buildForFederation(config, fedOptions, externals, signal) {
12
- // 1. Caching
13
- fedOptions.federationCache.cachePath = path.join(fedOptions.federationCache.cachePath, resolveProjectName(config));
14
- const start = process.hrtime();
15
- // 2. Shared mappings and exposed modules
16
- const artifactInfo = await bundleExposedAndMappings(config, fedOptions, externals, undefined, signal);
17
- logger.measure(start, '[build artifacts] - To bundle all mappings and exposed.');
18
- if (signal?.aborted)
19
- throw new AbortedError('[buildForFederation] After exposed-and-mappings bundle');
20
- const exposedInfo = !artifactInfo ? describeExposed(config, fedOptions) : artifactInfo.exposes;
21
- // 3. Externals
11
+ // 1. Setup
12
+ fedOptions.federationCache.cachePath = path.join(fedOptions.federationCache.cachePath, fedOptions.projectName);
13
+ logger.info('Building federation artifacts');
14
+ logger.notice("Skip packages you don't want to share in your federation config");
15
+ // 2. Externals
22
16
  if (fedOptions.federationCache.externals.length > 0) {
23
17
  logger.info('Checksum matched, re-using cached externals.');
24
18
  }
25
19
  if (fedOptions.federationCache.externals.length === 0) {
26
20
  const { sharedBrowser, sharedServer, separateBrowser, separateServer } = splitShared(config.shared);
27
21
  if (Object.keys(sharedBrowser).length > 0) {
28
- notifyBundling('browser-shared');
22
+ logger.info(`Bundling external npm packages with bundle type 'browser-shared'`);
29
23
  const start = process.hrtime();
30
- const sharedPackageInfoBrowser = await bundleShared(sharedBrowser, config, fedOptions, externals, { platform: 'browser', bundleName: 'browser-shared' });
31
- logger.measure(start, '[build artifacts] - To bundle all shared browser externals');
24
+ const sharedPackageInfoBrowser = await bundleShared(sharedBrowser, config, fedOptions, externals, { platform: 'browser', bundleName: 'browser-shared', chunks: config.chunks });
25
+ logger.measure(start, 'Step 2.1) Bundling all shared browser externals');
32
26
  addExternalsToCache(fedOptions.federationCache, sharedPackageInfoBrowser);
33
27
  if (signal?.aborted)
34
28
  throw new AbortedError('[buildForFederation] After shared-browser bundle');
35
29
  }
36
30
  if (Object.keys(sharedServer).length > 0) {
37
- notifyBundling('browser-shared');
31
+ logger.info(`Bundling external npm packages with bundle type 'server-shared'`);
38
32
  const start = process.hrtime();
39
- const sharedPackageInfoServer = await bundleShared(sharedServer, config, fedOptions, externals, { platform: 'node', bundleName: 'node-shared' });
40
- logger.measure(start, '[build artifacts] - To bundle all shared node externals');
33
+ const sharedPackageInfoServer = await bundleShared(sharedServer, config, fedOptions, externals, { platform: 'node', bundleName: 'node-shared', chunks: config.chunks });
34
+ logger.measure(start, 'Step 2.1) Bundling all shared node externals');
41
35
  addExternalsToCache(fedOptions.federationCache, sharedPackageInfoServer);
42
36
  if (signal?.aborted)
43
37
  throw new AbortedError('[buildForFederation] After shared-node bundle');
44
38
  }
45
39
  if (Object.keys(separateBrowser).length > 0) {
46
- notifyBundling('browser-shared');
40
+ logger.info(`Bundling external npm packages with bundle type 'browser-separate'`);
47
41
  const start = process.hrtime();
48
42
  const separatePackageInfoBrowser = await bundleSeparatePackages(separateBrowser, externals, config, fedOptions, { platform: 'browser' });
49
- logger.measure(start, '[build artifacts] - To bundle all separate browser externals');
43
+ logger.measure(start, 'Step 2.2) Bundling all separate browser external packages');
50
44
  addExternalsToCache(fedOptions.federationCache, separatePackageInfoBrowser);
51
45
  if (signal?.aborted)
52
46
  throw new AbortedError('[buildForFederation] After separate-browser bundle');
53
47
  }
54
48
  if (Object.keys(separateServer).length > 0) {
55
- notifyBundling('browser-shared');
49
+ logger.info(`Bundling external npm packages with bundle type 'node-separate'`);
56
50
  const start = process.hrtime();
57
51
  const separatePackageInfoServer = await bundleSeparatePackages(separateServer, externals, config, fedOptions, { platform: 'node' });
58
- logger.measure(start, '[build artifacts] - To bundle all separate node externals');
52
+ logger.measure(start, 'Step 2.2) Bundling all separate node external packages');
59
53
  addExternalsToCache(fedOptions.federationCache, separatePackageInfoServer);
60
54
  }
61
55
  if (signal?.aborted)
62
56
  throw new AbortedError('[buildForFederation] After separate-node bundle');
63
57
  }
58
+ // 2. Shared mappings and exposed modules
59
+ const start = process.hrtime();
60
+ const artifactInfo = await bundleExposedAndMappings(config, fedOptions, externals, undefined, signal);
61
+ logger.measure(start, 'Step 3) Bundling all internal libraries and exposed modules.');
62
+ if (signal?.aborted)
63
+ throw new AbortedError('[buildForFederation] After exposed-and-mappings bundle');
64
+ const exposedInfo = !artifactInfo ? describeExposed(config, fedOptions) : artifactInfo.exposes;
64
65
  const sharedMappingInfo = !artifactInfo
65
66
  ? describeSharedMappings(config, fedOptions)
66
67
  : artifactInfo.mappings;
@@ -102,13 +103,17 @@ async function bundleSeparatePackages(separateBrowser, externals, config, fedOpt
102
103
  for (const [key, shared] of Object.entries(separateBrowser)) {
103
104
  const packageName = shared.build === 'separate' ? key : inferPackageFromSecondary(key);
104
105
  if (!groupedByPackage[packageName]) {
105
- groupedByPackage[packageName] = {};
106
+ groupedByPackage[packageName] = {
107
+ chunks: shared.chunks,
108
+ entries: {},
109
+ };
106
110
  }
107
- groupedByPackage[packageName][key] = shared;
111
+ groupedByPackage[packageName].entries[key] = shared;
108
112
  }
109
- const bundlePromises = Object.entries(groupedByPackage).map(async ([packageName, sharedGroup]) => {
110
- return bundleShared(sharedGroup, config, fedOptions, externals.filter(e => !e.startsWith(packageName)), {
113
+ const bundlePromises = Object.entries(groupedByPackage).map(async ([packageName, packageConfig]) => {
114
+ return bundleShared(packageConfig.entries, config, fedOptions, externals.filter(e => !e.startsWith(packageName)), {
111
115
  platform: buildOptions.platform,
116
+ chunks: packageConfig.chunks,
112
117
  bundleName: `${buildOptions.platform}-${normalizePackageName(packageName)}`,
113
118
  });
114
119
  });
@@ -121,11 +126,6 @@ async function bundleSeparatePackages(separateBrowser, externals, config, fedOpt
121
126
  return { externals: [...acc.externals, ...r.externals], chunks };
122
127
  }, { externals: [] });
123
128
  }
124
- function notifyBundling(platform) {
125
- logger.info('Preparing shared npm packages for the platform ' + platform);
126
- logger.notice('This only needs to be done once, as results are cached');
127
- logger.notice("Skip packages you don't want to share in your federation config");
128
- }
129
129
  function splitShared(shared) {
130
130
  const sharedServer = {};
131
131
  const sharedBrowser = {};
@@ -10,11 +10,12 @@ export async function bundleExposedAndMappings(config, fedOptions, externals, mo
10
10
  if (signal?.aborted) {
11
11
  throw new AbortedError('[bundle-exposed-and-mappings] Aborted before bundling');
12
12
  }
13
- const shared = config.sharedMappings.map(sm => {
14
- const entryPoint = sm.path;
15
- const tmp = sm.key.replace(/[^A-Za-z0-9]/g, '_');
16
- const outFilePath = tmp + '.js';
17
- return { fileName: entryPoint, outName: outFilePath, key: sm.key };
13
+ const shared = Object.entries(config.sharedMappings).map(([entryPoint, mappedImport]) => {
14
+ return {
15
+ fileName: entryPoint,
16
+ outName: mappedImport.replace(/[^A-Za-z0-9]/g, '_') + '.js',
17
+ key: mappedImport,
18
+ };
18
19
  });
19
20
  const exposes = Object.entries(config.exposes).map(([key, entry]) => {
20
21
  const outFilePath = key + '.js';
@@ -22,11 +23,10 @@ export async function bundleExposedAndMappings(config, fedOptions, externals, mo
22
23
  });
23
24
  const entryPoints = [...shared, ...exposes];
24
25
  const hash = !fedOptions.dev;
25
- logger.info('Building federation artifacts');
26
26
  let result;
27
27
  try {
28
28
  if (!modifiedFiles) {
29
- await getBuildAdapter().setup({
29
+ await getBuildAdapter().setup('mapping-or-exposed', {
30
30
  entryPoints,
31
31
  outdir: fedOptions.outputPath,
32
32
  tsConfigPath: fedOptions.tsConfig,
@@ -34,18 +34,16 @@ export async function bundleExposedAndMappings(config, fedOptions, externals, mo
34
34
  dev: !!fedOptions.dev,
35
35
  watch: fedOptions.watch,
36
36
  mappedPaths: config.sharedMappings,
37
- bundleName: 'mapping-or-exposed',
38
- chunks: (typeof fedOptions.chunks === 'boolean' && fedOptions.chunks) ||
39
- (typeof fedOptions.chunks === 'object' && !!fedOptions.chunks.enable),
37
+ chunks: config.chunks,
40
38
  hash,
41
39
  optimizedMappings: config.features.ignoreUnusedDeps,
42
- isNodeModules: false,
40
+ isMappingOrExposed: true,
43
41
  cache: fedOptions.federationCache,
44
42
  });
45
43
  }
46
44
  result = await getBuildAdapter().build('mapping-or-exposed', {
47
45
  signal,
48
- files: modifiedFiles,
46
+ modifiedFiles,
49
47
  });
50
48
  if (signal?.aborted) {
51
49
  throw new AbortedError('[bundle-exposed-and-mappings] Aborted after bundle');
@@ -99,7 +97,7 @@ export async function bundleExposedAndMappings(config, fedOptions, externals, mo
99
97
  .forEach(f => popFromResultMap(resultMap, f));
100
98
  // Process remaining chunks and lazy loaded internal modules
101
99
  let exportedChunks = undefined;
102
- if (typeof fedOptions.chunks === 'object' && fedOptions.chunks.dense === true) {
100
+ if (config.chunks && config.features.denseChunking) {
103
101
  for (const entryFile of entryFiles)
104
102
  rewriteChunkImports(entryFile);
105
103
  exportedChunks = {
@@ -126,18 +124,18 @@ export function describeExposed(config, options) {
126
124
  }
127
125
  export function describeSharedMappings(config, fedOptions) {
128
126
  const result = [];
129
- for (const m of config.sharedMappings) {
127
+ for (const [mappedPath, mappedImport] of Object.entries(config.sharedMappings)) {
130
128
  result.push({
131
- packageName: m.key,
129
+ packageName: mappedImport,
132
130
  outFileName: '',
133
131
  requiredVersion: '',
134
132
  singleton: true,
135
133
  strictVersion: false,
136
- version: config.features.mappingVersion ? getMappingVersion(m.path) : '',
134
+ version: config.features.mappingVersion ? getMappingVersion(mappedPath) : '',
137
135
  dev: !fedOptions.dev
138
136
  ? undefined
139
137
  : {
140
- entryPoint: normalize(path.normalize(m.path)),
138
+ entryPoint: normalize(path.normalize(mappedPath)),
141
139
  },
142
140
  });
143
141
  }
@@ -5,6 +5,7 @@ import type { NormalizedExternalConfig } from '../domain/config/external-config.
5
5
  export declare function bundleShared(sharedBundles: Record<string, NormalizedExternalConfig>, config: NormalizedFederationConfig, fedOptions: NormalizedFederationOptions, externals: string[], buildOptions: {
6
6
  platform: 'browser' | 'node';
7
7
  bundleName: string;
8
+ chunks: boolean;
8
9
  }): Promise<{
9
10
  externals: SharedInfo[];
10
11
  chunks?: Record<string, string[]>;
@@ -53,18 +53,16 @@ export async function bundleShared(sharedBundles, config, fedOptions, externals,
53
53
  const additionalExternals = useDefaultExternalList ? DEFAULT_EXTERNAL_LIST : [];
54
54
  let bundleResult = null;
55
55
  try {
56
- await getBuildAdapter().setup({
56
+ await getBuildAdapter().setup(buildOptions.bundleName, {
57
57
  entryPoints,
58
58
  tsConfigPath: fedOptions.tsConfig,
59
59
  external: [...additionalExternals, ...externals],
60
60
  outdir: fedOptions.federationCache.cachePath,
61
61
  mappedPaths: config.sharedMappings,
62
62
  dev: fedOptions.dev,
63
- bundleName: buildOptions.bundleName,
64
- isNodeModules: true,
63
+ isMappingOrExposed: false,
65
64
  hash: false,
66
- chunks: (typeof fedOptions.chunks === 'boolean' && fedOptions.chunks) ||
67
- (typeof fedOptions.chunks === 'object' && !!fedOptions.chunks.enable),
65
+ chunks: buildOptions.chunks,
68
66
  platform: buildOptions.platform,
69
67
  optimizedMappings: config.features.ignoreUnusedDeps,
70
68
  cache: fedOptions.federationCache,
@@ -102,11 +100,13 @@ export async function bundleShared(sharedBundles, config, fedOptions, externals,
102
100
  * Chunking
103
101
  */
104
102
  let exportedChunks = undefined;
105
- if (typeof fedOptions.chunks === 'object' && fedOptions.chunks.dense === true) {
103
+ if (buildOptions.chunks && config.features.denseChunking) {
106
104
  result.forEach(external => {
107
105
  external.bundle = buildOptions.bundleName;
108
106
  });
109
- exportedChunks = { [buildOptions.bundleName]: getChunkFileNames(chunks) };
107
+ if (chunks.length > 0) {
108
+ exportedChunks = { [buildOptions.bundleName]: getChunkFileNames(chunks) };
109
+ }
110
110
  }
111
111
  else {
112
112
  addChunksToResult(chunks, result);
@@ -115,6 +115,7 @@ export async function bundleShared(sharedBundles, config, fedOptions, externals,
115
115
  checksum,
116
116
  externals: result,
117
117
  files: bundleResult.map(r => r.fileName.split(path.sep).pop() ?? r.fileName),
118
+ chunks: exportedChunks,
118
119
  });
119
120
  bundleCache.copyFiles(path.join(fedOptions.workspaceRoot, fedOptions.outputPath));
120
121
  return { externals: result, chunks: exportedChunks };
@@ -7,10 +7,15 @@ export interface BuildHelperParams {
7
7
  adapter: NFBuildAdapter;
8
8
  }
9
9
  declare function init(params: BuildHelperParams): Promise<void>;
10
- declare function build(signal?: AbortSignal): Promise<void>;
10
+ declare function build(opts?: {
11
+ modifiedFiles?: string[];
12
+ signal?: AbortSignal;
13
+ }): Promise<void>;
14
+ declare function close(): Promise<void>;
11
15
  export declare const federationBuilder: {
12
16
  init: typeof init;
13
17
  build: typeof build;
18
+ close: typeof close;
14
19
  readonly federationInfo: FederationInfo;
15
20
  readonly externals: string[];
16
21
  readonly config: NormalizedFederationConfig;
@@ -1,28 +1,38 @@
1
1
  import { getConfigContext, usePackageJson, useWorkspace } from '../config/configuration-context.js';
2
- import { setBuildAdapter } from './build-adapter.js';
2
+ import { getBuildAdapter, setBuildAdapter } from './build-adapter.js';
3
3
  import { buildForFederation } from './build-for-federation.js';
4
4
  import { getExternals } from './get-externals.js';
5
- import { loadFederationConfig } from './load-federation-config.js';
6
- import { normalizeFederationOptions } from './normalize-federation-options.js';
5
+ import { normalizeFederationOptions } from './normalize-options.js';
6
+ import { rebuildForFederation } from './rebuild-for-federation.js';
7
7
  let externals = [];
8
8
  let config;
9
- let fedOptions;
9
+ let options;
10
10
  let fedInfo;
11
11
  async function init(params) {
12
12
  setBuildAdapter(params.adapter);
13
- fedOptions = normalizeFederationOptions(params.options);
14
13
  useWorkspace(params.options.workspaceRoot);
15
14
  usePackageJson(params.options.packageJson);
16
- config = await loadFederationConfig(fedOptions);
17
15
  params.options.workspaceRoot = getConfigContext().workspaceRoot ?? params.options.workspaceRoot;
16
+ const normalized = await normalizeFederationOptions(params.options);
17
+ options = normalized.options;
18
+ config = normalized.config;
18
19
  externals = getExternals(config);
19
20
  }
20
- async function build(signal) {
21
- fedInfo = await buildForFederation(config, fedOptions, externals, signal);
21
+ async function build(opts = {}) {
22
+ if (!fedInfo) {
23
+ fedInfo = await buildForFederation(config, options, externals, opts.signal);
24
+ }
25
+ else {
26
+ fedInfo = await rebuildForFederation(config, options, externals, opts.modifiedFiles ?? [], opts.signal);
27
+ }
28
+ }
29
+ async function close() {
30
+ return getBuildAdapter().dispose();
22
31
  }
23
32
  export const federationBuilder = {
24
33
  init,
25
34
  build,
35
+ close,
26
36
  get federationInfo() {
27
37
  return fedInfo;
28
38
  },