@softarc/native-federation 4.0.0-RC8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/index.d.ts +1 -2
- package/src/index.js +1 -2
- package/src/internal.d.ts +4 -1
- package/src/internal.js +2 -0
- package/src/lib/config/default-skip-list.js +3 -15
- package/src/lib/config/remove-unused-deps.d.ts +3 -0
- package/src/lib/config/remove-unused-deps.js +10 -0
- package/src/lib/config/share-utils.d.ts +0 -1
- package/src/lib/config/share-utils.js +7 -13
- package/src/lib/config/with-native-federation.js +40 -61
- package/src/lib/core/build-for-federation.js +21 -22
- package/src/lib/core/bundle-exposed-and-mappings.js +15 -16
- package/src/lib/core/bundle-shared.d.ts +1 -0
- package/src/lib/core/bundle-shared.js +8 -7
- package/src/lib/core/federation-builder.d.ts +6 -1
- package/src/lib/core/federation-builder.js +18 -8
- package/src/lib/core/get-externals.js +1 -1
- package/src/lib/core/normalize-options.d.ts +12 -0
- package/src/lib/core/normalize-options.js +58 -0
- package/src/lib/core/rebuild-for-federation.js +2 -2
- package/src/lib/domain/config/external-config.contract.d.ts +3 -1
- package/src/lib/domain/config/federation-config.contract.d.ts +8 -2
- package/src/lib/domain/config/with-native-federation.contract.d.ts +2 -0
- package/src/lib/domain/config/with-native-federation.contract.js +1 -0
- package/src/lib/domain/core/build-adapter.contract.d.ts +12 -6
- package/src/lib/domain/core/federation-options.contract.d.ts +5 -5
- package/src/lib/domain/core/index.d.ts +1 -1
- package/src/lib/domain/utils/file-watcher.contract.d.ts +10 -0
- package/src/lib/domain/utils/file-watcher.contract.js +1 -0
- package/src/lib/domain/utils/index.d.ts +1 -1
- package/src/lib/domain/utils/mapped-path.contract.d.ts +1 -4
- package/src/lib/domain/utils/mapped-path.contract.js +4 -0
- package/src/lib/domain/utils/used-dependencies.contract.d.ts +5 -0
- package/src/lib/domain/utils/used-dependencies.contract.js +1 -0
- package/src/lib/utils/file-watcher.d.ts +5 -0
- package/src/lib/utils/file-watcher.js +51 -0
- package/src/lib/utils/get-used-dependencies.d.ts +7 -0
- package/src/lib/utils/get-used-dependencies.js +123 -0
- package/src/lib/utils/mapped-paths.d.ts +7 -7
- package/src/lib/utils/mapped-paths.js +14 -12
- package/src/lib/utils/resolve-wildcard-keys.d.ts +29 -2
- package/src/lib/utils/resolve-wildcard-keys.js +105 -38
- package/src/lib/core/default-server-deps-list.d.ts +0 -2
- package/src/lib/core/default-server-deps-list.js +0 -6
- package/src/lib/core/load-federation-config.d.ts +0 -3
- package/src/lib/core/load-federation-config.js +0 -18
- package/src/lib/core/normalize-federation-options.d.ts +0 -4
- package/src/lib/core/normalize-federation-options.js +0 -10
- package/src/lib/core/remove-unused-deps.d.ts +0 -2
- package/src/lib/core/remove-unused-deps.js +0 -90
- package/src/lib/utils/config-utils.d.ts +0 -2
- package/src/lib/utils/config-utils.js +0 -9
|
@@ -1,45 +1,111 @@
|
|
|
1
1
|
import fg from 'fast-glob';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
// TypeScript's module resolution for directories checks these in order
|
|
6
|
+
// @see https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution
|
|
7
|
+
const TS_INDEX_FILES = [
|
|
8
|
+
'index.ts',
|
|
9
|
+
'index.tsx',
|
|
10
|
+
'index.mts',
|
|
11
|
+
'index.cts',
|
|
12
|
+
'index.d.ts',
|
|
13
|
+
'index.js',
|
|
14
|
+
'index.jsx',
|
|
15
|
+
'index.mjs',
|
|
16
|
+
'index.cjs',
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Resolves tsconfig wildcard paths.
|
|
20
|
+
*
|
|
21
|
+
* In tsconfig.json, paths like `@features/*` → `libs/features/src/*` work as follows:
|
|
22
|
+
* - The `*` captures a single path segment (the module name)
|
|
23
|
+
* - When importing `@features/feature-a`, TypeScript captures `feature-a`
|
|
24
|
+
* - It then replaces `*` in the value pattern: `libs/features/src/feature-a`
|
|
25
|
+
*
|
|
26
|
+
* For discovery, we find all directories at the wildcard position that TypeScript
|
|
27
|
+
* would recognize as valid modules (directories with index files or package.json).
|
|
28
|
+
*
|
|
29
|
+
// @see https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution
|
|
30
|
+
*/
|
|
31
|
+
export function resolveTsConfigWildcard(keyPattern, valuePattern, cwd) {
|
|
32
|
+
const normalizedPattern = valuePattern.replace(/^\.?\/+/, '');
|
|
33
|
+
const asteriskIndex = normalizedPattern.indexOf('*');
|
|
34
|
+
if (asteriskIndex === -1) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const prefix = normalizedPattern.substring(0, asteriskIndex);
|
|
38
|
+
const suffix = normalizedPattern.substring(asteriskIndex + 1);
|
|
39
|
+
const searchPath = path.join(cwd, prefix);
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = fs.readdirSync(searchPath);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const keys = [];
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const entryPath = path.join(searchPath, entry);
|
|
50
|
+
let stats;
|
|
51
|
+
try {
|
|
52
|
+
stats = fs.statSync(entryPath);
|
|
17
53
|
}
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
20
56
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function withoutWildcard(template, wildcardValues) {
|
|
25
|
-
const tokens = template.split(/(\*\*|\*)/);
|
|
26
|
-
let result = '';
|
|
27
|
-
let i = 0;
|
|
28
|
-
for (const token of tokens) {
|
|
29
|
-
if (token === '*') {
|
|
30
|
-
result += wildcardValues[i++];
|
|
57
|
+
if (!stats.isDirectory()) {
|
|
58
|
+
// Skipping individual files, we only process modules
|
|
59
|
+
continue;
|
|
31
60
|
}
|
|
32
|
-
|
|
33
|
-
|
|
61
|
+
let modulePath = path.join(prefix, entry, suffix).replace(/\\/g, '/');
|
|
62
|
+
const fullPath = path.join(cwd, modulePath);
|
|
63
|
+
let fullPathStats;
|
|
64
|
+
try {
|
|
65
|
+
fullPathStats = fs.statSync(fullPath);
|
|
34
66
|
}
|
|
67
|
+
catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const key = keyPattern.replace('*', entry);
|
|
71
|
+
if (fullPathStats.isDirectory()) {
|
|
72
|
+
const indexFile = TS_INDEX_FILES.find(indexFile => fs.existsSync(path.join(fullPath, indexFile)));
|
|
73
|
+
if (!indexFile) {
|
|
74
|
+
logger.warn(`[shared-mappings] Internal lib '${key}' does not contain an entryPoint (barrel file).`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
modulePath = path.join(modulePath, indexFile);
|
|
78
|
+
}
|
|
79
|
+
else if (!fullPathStats.isFile()) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
keys.push({
|
|
83
|
+
key,
|
|
84
|
+
value: modulePath,
|
|
85
|
+
});
|
|
35
86
|
}
|
|
36
|
-
return
|
|
87
|
+
return keys;
|
|
37
88
|
}
|
|
38
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Resolves package.json exports wildcard patterns.
|
|
91
|
+
*
|
|
92
|
+
* In package.json exports, patterns like `./features/*.js` → `./src/features/*.js` work as follows:
|
|
93
|
+
* - The `*` is a literal string replacement that can include path separators
|
|
94
|
+
* - Importing `pkg/features/a/b.js` captures `a/b` and replaces `*` → `./src/features/a/b.js`
|
|
95
|
+
* - This matches actual files, not directories
|
|
96
|
+
*
|
|
97
|
+
* @see https://nodejs.org/api/packages.html#subpath-patterns
|
|
98
|
+
*/
|
|
99
|
+
export function resolvePackageJsonExportsWildcard(keyPattern, valuePattern, cwd) {
|
|
39
100
|
const normalizedPattern = valuePattern.replace(/^\.?\/+/, '');
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
101
|
+
const asteriskIndex = normalizedPattern.indexOf('*');
|
|
102
|
+
if (asteriskIndex === -1) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const prefix = normalizedPattern.substring(0, asteriskIndex);
|
|
106
|
+
const suffix = normalizedPattern.substring(asteriskIndex + 1);
|
|
107
|
+
// fast-glob requires **/* pattern for matching files at any depth
|
|
108
|
+
const files = fg.sync(prefix + '**/*' + suffix, {
|
|
43
109
|
cwd,
|
|
44
110
|
onlyFiles: true,
|
|
45
111
|
deep: Infinity,
|
|
@@ -47,11 +113,12 @@ export function resolveWildcardKeys(keyPattern, valuePattern, cwd) {
|
|
|
47
113
|
const keys = [];
|
|
48
114
|
for (const file of files) {
|
|
49
115
|
const relPath = file.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
116
|
+
const captured = suffix
|
|
117
|
+
? relPath.slice(prefix.length, -suffix.length)
|
|
118
|
+
: relPath.slice(prefix.length);
|
|
119
|
+
const key = keyPattern.replace('*', captured);
|
|
53
120
|
keys.push({
|
|
54
|
-
key
|
|
121
|
+
key,
|
|
55
122
|
value: relPath,
|
|
56
123
|
});
|
|
57
124
|
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { NormalizedFederationConfig } from '../domain/config/federation-config.contract.js';
|
|
2
|
-
import type { FederationOptions } from '../domain/core/federation-options.contract.js';
|
|
3
|
-
export declare function loadFederationConfig(fedOptions: FederationOptions): Promise<NormalizedFederationConfig>;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import { removeUnusedDeps } from './remove-unused-deps.js';
|
|
4
|
-
export async function loadFederationConfig(fedOptions) {
|
|
5
|
-
const fullConfigPath = path.join(fedOptions.workspaceRoot, fedOptions.federationConfig);
|
|
6
|
-
if (!fs.existsSync(fullConfigPath)) {
|
|
7
|
-
throw new Error('Expected ' + fullConfigPath);
|
|
8
|
-
}
|
|
9
|
-
const config = (await import(fullConfigPath))?.default;
|
|
10
|
-
if (config.features.ignoreUnusedDeps && !fedOptions.entryPoint) {
|
|
11
|
-
throw new Error(`The feature ignoreUnusedDeps needs the application's entry point. Please set it in your federation options!`);
|
|
12
|
-
}
|
|
13
|
-
if (config.features.ignoreUnusedDeps) {
|
|
14
|
-
// const entryPoint = path.join(fedOptions.workspaceRoot, fedOptions.entryPoint ?? '');
|
|
15
|
-
return removeUnusedDeps(config, fedOptions.entryPoint ?? '', fedOptions.workspaceRoot);
|
|
16
|
-
}
|
|
17
|
-
return config;
|
|
18
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { FederationCache } from '../../domain.js';
|
|
2
|
-
import type { FederationOptions, NormalizedFederationOptions } from '../domain/core/federation-options.contract.js';
|
|
3
|
-
export declare function normalizeFederationOptions(options: FederationOptions): NormalizedFederationOptions<undefined>;
|
|
4
|
-
export declare function normalizeFederationOptions<TBundlerCache>(options: FederationOptions, cache: FederationCache<TBundlerCache>): NormalizedFederationOptions<TBundlerCache>;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { getDefaultCachePath } from '../utils/cache-persistence.js';
|
|
2
|
-
import { createFederationCache } from './federation-cache.js';
|
|
3
|
-
export function normalizeFederationOptions(options, cache) {
|
|
4
|
-
const federationCache = cache ??
|
|
5
|
-
createFederationCache(getDefaultCachePath(options.workspaceRoot));
|
|
6
|
-
return {
|
|
7
|
-
...options,
|
|
8
|
-
federationCache,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { getProjectData } from '@softarc/sheriff-core';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import { cwd } from 'process';
|
|
5
|
-
import { getPackageInfo } from '../utils/package-info.js';
|
|
6
|
-
import { getExternalImports as extractExternalImports } from '../utils/get-external-imports.js';
|
|
7
|
-
import { normalizePackageName } from '../utils/normalize.js';
|
|
8
|
-
import { resolveProjectName } from '../utils/config-utils.js';
|
|
9
|
-
export function removeUnusedDeps(config, main, workspaceRoot) {
|
|
10
|
-
const fileInfos = getProjectData(main, cwd(), {
|
|
11
|
-
includeExternalLibraries: true,
|
|
12
|
-
});
|
|
13
|
-
const usedDeps = findUsedDeps(fileInfos, workspaceRoot, config);
|
|
14
|
-
const usedPackageNames = usedDeps.usedPackageNames;
|
|
15
|
-
const usedMappings = usedDeps.usedMappings;
|
|
16
|
-
const projectName = resolveProjectName(config);
|
|
17
|
-
const usedPackageNamesWithTransient = addTransientDeps(usedPackageNames, workspaceRoot, projectName);
|
|
18
|
-
const filteredShared = filterShared(config, usedPackageNamesWithTransient);
|
|
19
|
-
return {
|
|
20
|
-
...config,
|
|
21
|
-
shared: filteredShared,
|
|
22
|
-
sharedMappings: [...usedMappings],
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function filterShared(config, usedPackageNamesWithTransient) {
|
|
26
|
-
return Object.entries(config.shared)
|
|
27
|
-
.filter(([shared, meta]) => !!meta.includeSecondaries || usedPackageNamesWithTransient.has(shared))
|
|
28
|
-
.reduce((acc, [shared, meta]) => ({ ...acc, [shared]: meta }), {});
|
|
29
|
-
}
|
|
30
|
-
function findUsedDeps(fileInfos, workspaceRoot, config) {
|
|
31
|
-
const usedPackageNames = new Set();
|
|
32
|
-
const usedMappings = new Set();
|
|
33
|
-
for (const fileName of Object.keys(fileInfos)) {
|
|
34
|
-
const fileInfo = fileInfos[fileName];
|
|
35
|
-
if (!fileInfo) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
const libs = [...(fileInfo.externalLibraries || []), ...(fileInfo.unresolvedImports || [])];
|
|
39
|
-
for (const pckg of libs) {
|
|
40
|
-
usedPackageNames.add(pckg);
|
|
41
|
-
}
|
|
42
|
-
const fullFileName = path.join(workspaceRoot, fileName);
|
|
43
|
-
const mappings = config.sharedMappings.filter(sm => fullFileName.startsWith(sm.path));
|
|
44
|
-
for (const mapping of mappings) {
|
|
45
|
-
usedMappings.add(mapping);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return { usedPackageNames, usedMappings };
|
|
49
|
-
}
|
|
50
|
-
function addTransientDeps(packages, workspaceRoot, projectName) {
|
|
51
|
-
const packagesAndPeers = new Set([...packages]);
|
|
52
|
-
const discovered = new Set(packagesAndPeers);
|
|
53
|
-
const stack = [...packagesAndPeers];
|
|
54
|
-
while (stack.length > 0) {
|
|
55
|
-
const dep = stack.pop();
|
|
56
|
-
if (!dep) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const pInfo = getPackageInfo(dep, workspaceRoot);
|
|
60
|
-
if (!pInfo) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const peerDeps = getExternalImports(pInfo, workspaceRoot, projectName);
|
|
64
|
-
for (const peerDep of peerDeps) {
|
|
65
|
-
if (!discovered.has(peerDep)) {
|
|
66
|
-
discovered.add(peerDep);
|
|
67
|
-
stack.push(peerDep);
|
|
68
|
-
packagesAndPeers.add(peerDep);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return packagesAndPeers;
|
|
73
|
-
}
|
|
74
|
-
function getExternalImports(pInfo, workspaceRoot, projectName) {
|
|
75
|
-
const encodedPackageName = normalizePackageName(pInfo.packageName);
|
|
76
|
-
const cacheFileName = `${encodedPackageName}-${pInfo.version}.deps.json`;
|
|
77
|
-
const cachePath = path.join(workspaceRoot, 'node_modules/.cache/native-federation/_externals-metadata', projectName);
|
|
78
|
-
const cacheFilePath = path.join(cachePath, cacheFileName);
|
|
79
|
-
const cacheHit = fs.existsSync(cacheFilePath);
|
|
80
|
-
let peerDeps;
|
|
81
|
-
if (cacheHit) {
|
|
82
|
-
peerDeps = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
peerDeps = extractExternalImports(pInfo.entryPoint);
|
|
86
|
-
fs.mkdirSync(cachePath, { recursive: true });
|
|
87
|
-
fs.writeFileSync(cacheFilePath, JSON.stringify(peerDeps, undefined, 2), 'utf-8');
|
|
88
|
-
}
|
|
89
|
-
return peerDeps;
|
|
90
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { logger } from './logger.js';
|
|
2
|
-
import { normalizePackageName } from './normalize.js';
|
|
3
|
-
export function resolveProjectName(config) {
|
|
4
|
-
const normalizedProjectName = normalizePackageName(config.name);
|
|
5
|
-
if (normalizedProjectName.length < 1) {
|
|
6
|
-
logger.warn("Project name in 'federation.config.js' is empty, defaulting to 'shell' cache folder (could collide with other projects in the workspace).");
|
|
7
|
-
}
|
|
8
|
-
return normalizedProjectName.length < 1 ? 'shell' : normalizedProjectName;
|
|
9
|
-
}
|