Skip to content

Commit 229a79f

Browse files
committed
feat: remove unused imports
1 parent b0a7112 commit 229a79f

File tree

20 files changed

+297
-23
lines changed

20 files changed

+297
-23
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
{
22
"name": "@softarc/native-federation",
3-
"version": "3.0.2",
3+
"version": "3.1.0",
44
"type": "commonjs",
55
"license": "MIT",
66
"dependencies": {
77
"json5": "^2.2.0",
88
"chalk": "^4.1.2",
9-
"@softarc/native-federation-runtime": "3.0.2"
9+
"@softarc/native-federation-runtime": "3.1.0"
10+
},
11+
"peerDependencies": {
12+
"@softarc/sheriff-core": "^0.18.0"
1013
}
1114
}

libs/native-federation-core/src/lib/config/federation-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface FederationConfig {
2626
externals?: string[];
2727
features?: {
2828
mappingVersion?: boolean;
29+
ignoreUnusedDeps?: boolean;
2930
};
3031
}
3132

@@ -53,5 +54,6 @@ export interface NormalizedFederationConfig {
5354
externals: string[];
5455
features: {
5556
mappingVersion: boolean;
57+
ignoreUnusedDeps: boolean;
5658
};
5759
}

libs/native-federation-core/src/lib/config/share-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,14 @@ function getDefaultEntry(
290290
if (!entry) {
291291
entry = exports[key]?.['import'];
292292
if (typeof entry === 'object') {
293-
entry = entry['import'];
293+
entry = entry['import'] ?? entry['default'];
294+
}
295+
}
296+
297+
if (!entry) {
298+
entry = exports[key]?.['require'];
299+
if (typeof entry === 'object') {
300+
entry = entry['require'] ?? entry['default'];
294301
}
295302
}
296303

libs/native-federation-core/src/lib/config/with-native-federation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function withNativeFederation(
2727
externals: config.externals ?? [],
2828
features: {
2929
mappingVersion: config.features?.mappingVersion ?? false,
30+
ignoreUnusedDeps: config.features?.ignoreUnusedDeps ?? false,
3031
},
3132
};
3233
}

libs/native-federation-core/src/lib/core/bundle-shared.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { SharedInfo } from '@softarc/native-federation-runtime';
1010
import { FederationOptions } from './federation-options';
1111
import { copySrcMapIfExists } from '../utils/copy-src-map-if-exists';
1212
import { logger } from '../utils/logger';
13-
import { normalize } from '../utils/normalize';
1413
import crypto from 'crypto';
1514
import { DEFAULT_EXTERNAL_LIST } from './default-external-list';
1615
import { BuildResult } from './build-adapter';
@@ -77,9 +76,13 @@ export async function bundleShared(
7776
"Skip packages you don't want to share in your federation config"
7877
);
7978
}
79+
80+
// If we build for the browser and don't remote unused deps from the shared config,
81+
// we need to exclude typical node libs to avoid compilation issues
82+
const useDefaultExternalList = platform === 'browser' && !config.features.ignoreUnusedDeps;
8083

8184
const additionalExternals =
82-
platform === 'browser' ? DEFAULT_EXTERNAL_LIST : [];
85+
useDefaultExternalList ? DEFAULT_EXTERNAL_LIST : [];
8386

8487
let bundleResult: BuildResult[] | null = null;
8588

libs/native-federation-core/src/lib/core/federation-options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export interface FederationOptions {
77
dev?: boolean;
88
watch?: boolean;
99
packageJson?: string;
10+
entryPoint?: string;
1011
}

libs/native-federation-core/src/lib/core/load-federation-config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NormalizedFederationConfig } from '../config/federation-config';
22
import { FederationOptions } from './federation-options';
33
import * as path from 'path';
44
import * as fs from 'fs';
5+
import { removeUnusedDeps } from './remove-unused-deps';
56

67
export async function loadFederationConfig(
78
fedOptions: FederationOptions
@@ -16,5 +17,18 @@ export async function loadFederationConfig(
1617
}
1718

1819
const config = (await import(fullConfigPath)) as NormalizedFederationConfig;
20+
21+
if (config.features.ignoreUnusedDeps && !fedOptions.entryPoint) {
22+
throw new Error(`The feature ignoreUnusedDeps needs the application's entry point. Please set it in your federation options!`);
23+
}
24+
25+
if (config.features.ignoreUnusedDeps) {
26+
return removeUnusedDeps(
27+
config,
28+
fedOptions.entryPoint ?? '',
29+
fedOptions.workspaceRoot,
30+
)
31+
}
32+
1933
return config;
2034
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { getProjectData } from '@softarc/sheriff-core';
2+
import path from 'path';
3+
import fs from 'fs';
4+
import { cwd } from 'process';
5+
import { NormalizedFederationConfig } from '../config/federation-config';
6+
import { getPackageInfo, PackageInfo } from '../utils/package-info';
7+
import { getExternalImports as extractExternalImports } from '../utils/get-external-imports';
8+
import { MappedPath } from '../utils/mapped-paths';
9+
import { ProjectData } from '@softarc/sheriff-core/src/lib/api/get-project-data';
10+
11+
export function removeUnusedDeps(config: NormalizedFederationConfig, main: string, workspaceRoot: string): NormalizedFederationConfig {
12+
const fileInfos = getProjectData(main, cwd(), {
13+
includeExternalLibraries: true,
14+
});
15+
16+
const usedDeps = findUsedDeps(fileInfos, workspaceRoot, config);
17+
const usedPackageNames = usedDeps.usedPackageNames;
18+
const usedMappings = usedDeps.usedMappings;
19+
20+
const usedPackageNamesWithTransient = addTransientDeps(usedPackageNames, workspaceRoot);
21+
const filteredShared = filterShared(config, usedPackageNamesWithTransient);
22+
23+
return {
24+
...config,
25+
shared: filteredShared,
26+
sharedMappings: [...usedMappings],
27+
};
28+
}
29+
30+
function filterShared(config: NormalizedFederationConfig, usedPackageNamesWithTransient: Set<string>) {
31+
const filteredSharedNames = Object.keys(config.shared).filter(
32+
(shared) => usedPackageNamesWithTransient.has(shared)
33+
);
34+
35+
const filteredShared = filteredSharedNames.reduce(
36+
(acc, curr) => ({ ...acc, [curr]: config.shared[curr] }),
37+
{}
38+
);
39+
return filteredShared;
40+
}
41+
42+
function findUsedDeps(fileInfos: ProjectData, workspaceRoot: string, config: NormalizedFederationConfig) {
43+
const usedPackageNames = new Set<string>();
44+
const usedMappings = new Set<MappedPath>();
45+
46+
for (const fileName of Object.keys(fileInfos)) {
47+
const fileInfo = fileInfos[fileName];
48+
49+
if (!fileInfo || !fileInfo.externalLibraries) {
50+
continue;
51+
}
52+
53+
for (const pckg of fileInfo.externalLibraries) {
54+
usedPackageNames.add(pckg);
55+
}
56+
57+
const fullFileName = path.join(workspaceRoot, fileName);
58+
const mappings = config.sharedMappings.filter(
59+
(sm) => sm.path === fullFileName
60+
);
61+
62+
for (const mapping of mappings) {
63+
usedMappings.add(mapping);
64+
}
65+
}
66+
return { usedPackageNames, usedMappings };
67+
}
68+
69+
function addTransientDeps(packages: Set<string>, workspaceRoot: string) {
70+
const packagesAndPeers = new Set<string>([...packages]);
71+
const discovered = new Set<string>(packagesAndPeers);
72+
const stack = [...packagesAndPeers];
73+
74+
while (stack.length > 0) {
75+
const dep = stack.pop();
76+
77+
if (!dep) {
78+
continue;
79+
}
80+
81+
const pInfo = getPackageInfo(dep, workspaceRoot);
82+
83+
if (!pInfo) {
84+
continue;
85+
}
86+
87+
const peerDeps = getExternalImports(pInfo, workspaceRoot);
88+
89+
for (const peerDep of peerDeps) {
90+
if (!discovered.has(peerDep)) {
91+
discovered.add(peerDep);
92+
stack.push(peerDep);
93+
packagesAndPeers.add(peerDep);
94+
}
95+
}
96+
}
97+
return packagesAndPeers;
98+
}
99+
100+
function getExternalImports(pInfo: PackageInfo, workspaceRoot: string) {
101+
const encodedPackageName = pInfo.packageName.replace(/[^A-Za-z0-9]/g, '_');
102+
const cacheFileName = `${encodedPackageName}-${pInfo.version}.deps.json`;
103+
const cachePath = path.join(workspaceRoot, 'node_modules/.cache/native-federation');
104+
const cacheFilePath = path.join(cachePath, cacheFileName);
105+
106+
const cacheHit = fs.existsSync(cacheFilePath);
107+
108+
let peerDeps;
109+
if (cacheHit) {
110+
peerDeps = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
111+
}
112+
else {
113+
peerDeps = extractExternalImports(pInfo.entryPoint);
114+
fs.mkdirSync(cachePath, { recursive: true });
115+
fs.writeFileSync(cacheFilePath, JSON.stringify(peerDeps, undefined, 2), 'utf-8');
116+
}
117+
return peerDeps;
118+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs';
3+
import * as ts from 'typescript';
4+
5+
export function getExternalImports(entryFilePath: string) {
6+
const visited = new Set<string>();
7+
const externals = new Set<string>();
8+
9+
function isExternal(specifier: string) {
10+
return !specifier.startsWith('.') && !path.isAbsolute(specifier);
11+
}
12+
13+
function normalizeExternal(specifier: string) {
14+
return specifier;
15+
}
16+
17+
function resolveAsFileOrDirectory(p: string) {
18+
const abs = path.resolve(p);
19+
20+
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) return abs;
21+
22+
const extensions = ['.ts', '.js', '.mjs', '.cjs'];
23+
for (const ext of extensions) {
24+
if (fs.existsSync(abs + ext) && fs.statSync(abs + ext).isFile()) {
25+
return abs + ext;
26+
}
27+
}
28+
29+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
30+
for (const file of extensions.map((e) => 'index' + e)) {
31+
const indexPath = path.join(abs, file);
32+
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
33+
return indexPath;
34+
}
35+
}
36+
}
37+
38+
return null;
39+
}
40+
41+
function visit(filePath: string) {
42+
const absPath = path.resolve(filePath);
43+
if (visited.has(absPath)) return;
44+
visited.add(absPath);
45+
46+
const resolved = resolveAsFileOrDirectory(absPath);
47+
if (!resolved) return;
48+
49+
const source = fs.readFileSync(resolved, 'utf8');
50+
const sourceFile = ts.createSourceFile(
51+
resolved,
52+
source,
53+
ts.ScriptTarget.Latest,
54+
true
55+
);
56+
57+
function walk(node: ts.Node) {
58+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
59+
const moduleSpecifier = node.moduleSpecifier;
60+
if (moduleSpecifier && ts.isStringLiteral(moduleSpecifier)) {
61+
const spec = moduleSpecifier.text;
62+
if (isExternal(spec)) {
63+
externals.add(normalizeExternal(spec));
64+
} else {
65+
const resolvedPath = resolveAsFileOrDirectory(
66+
path.resolve(path.dirname(resolved ?? ''), spec)
67+
);
68+
if (resolvedPath) visit(resolvedPath);
69+
}
70+
}
71+
}
72+
73+
if (
74+
ts.isCallExpression(node) &&
75+
ts.isIdentifier(node.expression) &&
76+
node.expression.kind === ts.SyntaxKind.Identifier &&
77+
node.expression.escapedText === 'require' &&
78+
node.arguments.length === 1 &&
79+
ts.isStringLiteral(node.arguments[0])
80+
) {
81+
const spec = node.arguments[0].text;
82+
if (isExternal(spec)) {
83+
externals.add(normalizeExternal(spec));
84+
} else {
85+
const resolvedPath = resolveAsFileOrDirectory(
86+
path.resolve(path.dirname(resolved ?? ''), spec)
87+
);
88+
if (resolvedPath) visit(resolvedPath);
89+
}
90+
}
91+
92+
ts.forEachChild(node, walk);
93+
}
94+
95+
ts.forEachChild(sourceFile, walk);
96+
}
97+
98+
visit(entryFilePath);
99+
100+
return Array.from(externals);
101+
}

libs/native-federation-esbuild/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@softarc/native-federation-esbuild",
3-
"version": "3.0.2",
3+
"version": "3.1.0",
44
"type": "commonjs",
55
"dependencies": {
66
"@rollup/plugin-commonjs": "^22.0.2",

0 commit comments

Comments
 (0)