Skip to content

Commit 11dd80e

Browse files
authored
Merge pull request #963 from Aukevanoost/issues/916
nf(fix): Add support for external package.json wildcards
2 parents 36549fd + ad08172 commit 11dd80e

File tree

7 files changed

+78
-31
lines changed

7 files changed

+78
-31
lines changed

apps/mfe2/federation.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
} = require('@angular-architects/native-federation/config');
55

66
module.exports = withNativeFederation({
7+
name: 'mfe2',
78
shared: {
89
...shareAll({
910
singleton: true,

apps/playground/federation.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const {
44
} = require('@angular-architects/native-federation/config');
55

66
module.exports = withNativeFederation({
7+
name: 'playground-shell',
78
shared: {
89
...shareAll({
910
singleton: true,

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ function readConfiguredSecondaries(
232232
);
233233

234234
const result = {} as Record<string, SharedConfig>;
235+
const discoveredFiles = new Set<string>();
235236

236237
for (const key of keys) {
237238
const secondaryName = path.join(parent, key).replace(/\\/g, '/');
@@ -265,6 +266,10 @@ function readConfiguredSecondaries(
265266
parent,
266267
secondaryName,
267268
entry,
269+
{ discovered: discoveredFiles, skip: exclude },
270+
);
271+
items.forEach((e) =>
272+
discoveredFiles.add(typeof e === 'string' ? e : e.value),
268273
);
269274

270275
for (const item of items) {
@@ -294,14 +299,29 @@ function resolveSecondaries(
294299
parent: string,
295300
secondaryName: string,
296301
entry: string,
302+
excludes: { discovered: Set<string>; skip: string[] },
297303
): Array<string | KeyValuePair> {
298304
let items: Array<string | KeyValuePair> = [];
299305
if (key.includes('*')) {
300306
const expanded = resolveWildcardKeys(key, entry, libPath);
301-
items = expanded.map((e) => ({
302-
key: path.join(parent, e.key),
303-
value: path.join(libPath, e.value),
304-
}));
307+
items = expanded
308+
.map((e) => ({
309+
key: path.join(parent, e.key),
310+
value: path.join(libPath, e.value),
311+
}))
312+
.filter((i) => {
313+
if (
314+
excludes.skip.some((e) =>
315+
e.endsWith('*') ? i.key.startsWith(e.slice(0, -1)) : e === i.key,
316+
)
317+
) {
318+
return false;
319+
}
320+
if (excludes.discovered.has(i.value)) {
321+
return false;
322+
}
323+
return true;
324+
});
305325
} else {
306326
items = [secondaryName];
307327
}

libs/native-federation-core/src/lib/utils/package-info.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,25 +176,42 @@ export function _getPackageInfo(
176176
return null;
177177
}
178178

179-
let relSecondaryPath = path.relative(mainPkgName, packageName);
180-
if (!relSecondaryPath) {
181-
relSecondaryPath = '.';
182-
} else {
183-
relSecondaryPath = './' + relSecondaryPath.replace(/\\/g, '/');
184-
}
179+
const pathToSecondary = path.relative(mainPkgName, packageName);
180+
const relSecondaryPath = !pathToSecondary
181+
? '.'
182+
: './' + pathToSecondary.replace(/\\/g, '/');
183+
184+
let secondaryEntryPoint = mainPkgJson?.exports?.[relSecondaryPath];
185185

186-
let cand = mainPkgJson?.exports?.[relSecondaryPath];
186+
// wildcard
187+
if (!secondaryEntryPoint) {
188+
const wildcardEntry = Object.keys(mainPkgJson?.exports ?? []).find((e) =>
189+
e.startsWith('./*'),
190+
);
191+
if (wildcardEntry) {
192+
secondaryEntryPoint = mainPkgJson?.exports?.[wildcardEntry];
193+
if (typeof secondaryEntryPoint === 'string')
194+
secondaryEntryPoint = secondaryEntryPoint.replace('*', pathToSecondary);
195+
if (typeof secondaryEntryPoint === 'object')
196+
Object.keys(secondaryEntryPoint).forEach(function (key) {
197+
secondaryEntryPoint[key] = secondaryEntryPoint[key].replace(
198+
'*',
199+
pathToSecondary,
200+
);
201+
});
202+
}
203+
}
187204

188-
if (typeof cand === 'string') {
205+
if (typeof secondaryEntryPoint === 'string') {
189206
return {
190-
entryPoint: path.join(mainPkgPath, cand),
207+
entryPoint: path.join(mainPkgPath, secondaryEntryPoint),
191208
packageName,
192209
version,
193210
esm,
194211
};
195212
}
196213

197-
cand = mainPkgJson?.exports?.[relSecondaryPath]?.import;
214+
let cand = secondaryEntryPoint?.import;
198215

199216
if (typeof cand === 'object') {
200217
if (cand.module) {
@@ -229,7 +246,7 @@ export function _getPackageInfo(
229246
};
230247
}
231248

232-
cand = mainPkgJson?.exports?.[relSecondaryPath]?.module;
249+
cand = secondaryEntryPoint?.module;
233250

234251
if (typeof cand === 'object') {
235252
if (cand.module) {
@@ -252,7 +269,7 @@ export function _getPackageInfo(
252269
};
253270
}
254271

255-
cand = mainPkgJson?.exports?.[relSecondaryPath]?.default;
272+
cand = secondaryEntryPoint?.default;
256273
if (cand) {
257274
if (typeof cand === 'object') {
258275
if (cand.module) {

libs/native-federation-core/src/lib/utils/resolve-wildcard-keys.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@ function escapeRegex(str: string) {
99
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1010
}
1111

12+
// Convert package.json exports pattern to glob pattern
13+
// * in exports means "one segment", but for glob we need ** for deep matching
14+
// Src: https://hirok.io/posts/package-json-exports#exposing-all-package-files
15+
function convertExportsToGlob(pattern: string) {
16+
return pattern.replace(/(?<!\*)\*(?!\*)/g, '**');
17+
}
18+
1219
function compilePattern(pattern: string) {
1320
const tokens = pattern.split(/(\*\*|\*)/);
1421
const regexParts = [];
1522

1623
for (const token of tokens) {
1724
if (token === '*') {
18-
regexParts.push('([^/]+)');
19-
} else if (token === '**') {
2025
regexParts.push('(.*)');
2126
} else {
2227
regexParts.push(escapeRegex(token));
@@ -26,12 +31,12 @@ function compilePattern(pattern: string) {
2631
return new RegExp(`^${regexParts.join('')}$`);
2732
}
2833

29-
function applyWildcards(template: string, wildcardValues: string[]) {
34+
function withoutWildcard(template: string, wildcardValues: string[]) {
3035
const tokens = template.split(/(\*\*|\*)/);
3136
let result = '';
3237
let i = 0;
3338
for (const token of tokens) {
34-
if (token === '*' || token === '**') {
39+
if (token === '*') {
3540
result += wildcardValues[i++];
3641
} else {
3742
result += token;
@@ -46,28 +51,29 @@ export function resolveWildcardKeys(
4651
cwd: string,
4752
): KeyValuePair[] {
4853
const normalizedPattern = valuePattern.replace(/^\.?\/+/, '');
54+
55+
const globPattern = convertExportsToGlob(normalizedPattern);
56+
4957
const regex = compilePattern(normalizedPattern);
5058

51-
const files = fg.sync(valuePattern, {
59+
const files = fg.sync(globPattern, {
5260
cwd,
5361
onlyFiles: true,
62+
deep: Infinity,
5463
});
5564

5665
const keys: KeyValuePair[] = [];
5766

5867
for (const file of files) {
5968
const relPath = file.replace(/\\/g, '/').replace(/^\.\//, '');
6069

61-
const match = relPath.match(regex);
62-
if (!match) {
63-
continue;
64-
}
65-
66-
const wildcards = match.slice(1);
67-
const key = applyWildcards(keyPattern, wildcards);
70+
const wildcards = relPath.match(regex);
71+
if (!wildcards) continue;
6872

69-
// Change this:
70-
keys.push({ key, value: relPath });
73+
keys.push({
74+
key: withoutWildcard(keyPattern, wildcards.slice(1)),
75+
value: relPath,
76+
});
7177
}
7278

7379
return keys;

libs/native-federation/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ The host configuration (`projects/shell/federation.config.js`) looks like what y
175175
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
176176

177177
module.exports = withNativeFederation({
178+
name: 'my-host',
178179
shared: {
179180
...shareAll({
180181
singleton: true,

libs/native-federation/src/schematics/init/files/federation.config.js__tmpl__

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
22

33
module.exports = withNativeFederation({
4-
<% if (type === 'remote') { %>
54
name: '<%=project%>',
65

6+
<% if (type === 'remote') { %>
7+
78
exposes: {
89
'./Component': './<%=appComponentPath%>',
910
},

0 commit comments

Comments
 (0)