Skip to content

Commit 378c980

Browse files
feat(enhanced): add include/exclude filtering support for shared modules
- Add testRequestFilters and addSingletonFilterWarning utilities - Update ConsumeSharedPlugin with version and request filtering - Update ProvideSharedPlugin with version and request filtering - Update schemas to include include/exclude filter properties - Add comprehensive unit tests for filtering functionality - Add config test cases for consume and provide filtering 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b42ddf1 commit 378c980

31 files changed

+1726
-322
lines changed

.changeset/feat-share-filtering.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@module-federation/enhanced": minor
3+
---
4+
5+
feat: add include/exclude filtering support for shared modules
6+
7+
- Add testRequestFilters and addSingletonFilterWarning utilities
8+
- Update ConsumeSharedPlugin with version and request filtering
9+
- Update ProvideSharedPlugin with version and request filtering
10+
- Update schemas to include include/exclude filter properties
11+
- Add comprehensive unit tests for filtering functionality
12+
- Add config test cases for consume and provide filtering
13+

packages/enhanced/src/declarations/plugins/sharing/ConsumeSharedModule.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,30 @@ export type ConsumeOptions = {
5050
* Issuer layer in which the module should be resolved
5151
*/
5252
issuerLayer?: string | null;
53+
/**
54+
* Include filters for consuming shared modules
55+
*/
56+
include?: {
57+
/**
58+
* Version requirement that must be satisfied for the shared module to be included
59+
*/
60+
version?: string;
61+
/**
62+
* Request pattern that must match for the shared module to be included
63+
*/
64+
request?: string | RegExp;
65+
};
66+
/**
67+
* Exclude filters for consuming shared modules
68+
*/
69+
exclude?: {
70+
/**
71+
* Version requirement that if satisfied will exclude the shared module
72+
*/
73+
version?: string;
74+
/**
75+
* Request pattern that if matched will exclude the shared module
76+
*/
77+
request?: string | RegExp;
78+
};
5379
};

packages/enhanced/src/declarations/plugins/sharing/ProvideSharedPlugin.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,30 @@ export interface ProvidesConfig {
7272
* The actual request to use for importing the module. If not specified, the property name/key will be used.
7373
*/
7474
request?: string;
75+
/**
76+
* Include filters for providing shared modules.
77+
*/
78+
include?: {
79+
/**
80+
* Version requirement that must be satisfied for the module to be provided.
81+
*/
82+
version?: string;
83+
/**
84+
* Request pattern that must match for the module to be provided.
85+
*/
86+
request?: string | RegExp;
87+
};
88+
/**
89+
* Exclude filters for providing shared modules.
90+
*/
91+
exclude?: {
92+
/**
93+
* Version requirement that if satisfied will exclude the module from being provided.
94+
*/
95+
version?: string;
96+
/**
97+
* Request pattern that if matched will exclude the module from being provided.
98+
*/
99+
request?: string | RegExp;
100+
};
75101
}

packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import { resolveMatchedConfigs } from './resolveMatchedConfigs';
1616
import {
1717
getDescriptionFile,
1818
getRequiredVersionFromDescriptionFile,
19+
addSingletonFilterWarning,
20+
testRequestFilters,
21+
createLookupKeyForSharing,
22+
extractPathAfterNodeModules,
1923
} from './utils';
2024
import type {
2125
ResolveOptionsWithDependencyType,
@@ -32,6 +36,8 @@ import type { ResolveData } from 'webpack/lib/NormalModuleFactory';
3236
import type { ModuleFactoryCreateDataContextInfo } from 'webpack/lib/ModuleFactory';
3337
import type { ConsumeOptions } from '../../declarations/plugins/sharing/ConsumeSharedModule';
3438
import { createSchemaValidation } from '../../utils';
39+
import path from 'path';
40+
import { satisfy } from '@module-federation/runtime-tools/runtime-core';
3541

3642
const ModuleNotFoundError = require(
3743
normalizeWebpackPath('webpack/lib/ModuleNotFoundError'),
@@ -66,9 +72,7 @@ function createLookupKey(
6672
request: string,
6773
contextInfo: ModuleFactoryCreateDataContextInfo,
6874
): string {
69-
return contextInfo.issuerLayer
70-
? `(${contextInfo.issuerLayer})${request}`
71-
: request;
75+
return createLookupKeyForSharing(request, contextInfo.issuerLayer);
7276
}
7377

7478
class ConsumeSharedPlugin {
@@ -298,6 +302,81 @@ class ConsumeSharedPlugin {
298302
);
299303
}),
300304
]).then(([importResolved, requiredVersion]) => {
305+
// Apply version filters if defined
306+
if (requiredVersion && typeof requiredVersion === 'string') {
307+
// Check include version filter
308+
if (config.include?.version) {
309+
const includeVersion = config.include.version;
310+
if (typeof includeVersion === 'string') {
311+
if (!satisfy(requiredVersion, includeVersion)) {
312+
const error = new WebpackError(
313+
`Shared module "${request}" version "${requiredVersion}" does not satisfy include filter "${includeVersion}"`,
314+
);
315+
error.file = `shared module ${request}`;
316+
compilation.warnings.push(error);
317+
return new ConsumeSharedModule(
318+
directFallback ? compiler.context : context,
319+
{
320+
...config,
321+
importResolved: undefined,
322+
import: undefined,
323+
requiredVersion: false,
324+
},
325+
);
326+
}
327+
}
328+
}
329+
330+
// Check exclude version filter
331+
if (config.exclude?.version) {
332+
const excludeVersion = config.exclude.version;
333+
if (typeof excludeVersion === 'string') {
334+
if (satisfy(requiredVersion, excludeVersion)) {
335+
const error = new WebpackError(
336+
`Shared module "${request}" version "${requiredVersion}" matches exclude filter "${excludeVersion}"`,
337+
);
338+
error.file = `shared module ${request}`;
339+
compilation.warnings.push(error);
340+
return new ConsumeSharedModule(
341+
directFallback ? compiler.context : context,
342+
{
343+
...config,
344+
importResolved: undefined,
345+
import: undefined,
346+
requiredVersion: false,
347+
},
348+
);
349+
}
350+
}
351+
}
352+
353+
// Check singleton warnings for version filters
354+
if (config.singleton) {
355+
if (config.include?.version) {
356+
addSingletonFilterWarning(
357+
compilation,
358+
config.shareKey,
359+
'include',
360+
'version',
361+
config.include.version,
362+
request,
363+
importResolved,
364+
);
365+
}
366+
if (config.exclude?.version) {
367+
addSingletonFilterWarning(
368+
compilation,
369+
config.shareKey,
370+
'exclude',
371+
'version',
372+
config.exclude.version,
373+
request,
374+
importResolved,
375+
);
376+
}
377+
}
378+
}
379+
301380
return new ConsumeSharedModule(
302381
directFallback ? compiler.context : context,
303382
{
@@ -333,12 +412,50 @@ class ConsumeSharedPlugin {
333412
const lookup = options.request || prefix;
334413
if (request.startsWith(lookup)) {
335414
const remainder = request.slice(lookup.length);
415+
416+
// Apply request filters if defined
417+
if (
418+
!testRequestFilters(
419+
remainder,
420+
options.include?.request,
421+
options.exclude?.request,
422+
)
423+
) {
424+
continue; // Skip this match if filters don't pass
425+
}
426+
427+
const shareKey = options.shareKey + remainder;
428+
429+
// Check singleton warning for request filters
430+
if (options.singleton) {
431+
if (options.include?.request) {
432+
addSingletonFilterWarning(
433+
compilation,
434+
shareKey,
435+
'include',
436+
'request',
437+
options.include.request,
438+
request,
439+
);
440+
}
441+
if (options.exclude?.request) {
442+
addSingletonFilterWarning(
443+
compilation,
444+
shareKey,
445+
'exclude',
446+
'request',
447+
options.exclude.request,
448+
request,
449+
);
450+
}
451+
}
452+
336453
return createConsumeSharedModule(context, request, {
337454
...options,
338455
import: options.import
339456
? options.import + remainder
340457
: undefined,
341-
shareKey: options.shareKey + remainder,
458+
shareKey,
342459
layer: options.layer || contextInfo.issuerLayer,
343460
});
344461
}

packages/enhanced/src/lib/sharing/ProvideSharedPlugin.ts

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ import type {
2323
} from '../../declarations/plugins/sharing/ProvideSharedPlugin';
2424
import FederationRuntimePlugin from '../container/runtime/FederationRuntimePlugin';
2525
import { createSchemaValidation } from '../../utils';
26+
import path from 'path';
27+
import { satisfy } from '@module-federation/runtime-tools/runtime-core';
28+
import {
29+
addSingletonFilterWarning,
30+
testRequestFilters,
31+
createLookupKeyForSharing,
32+
extractPathAfterNodeModules,
33+
} from './utils';
2634
const WebpackError = require(
2735
normalizeWebpackPath('webpack/lib/WebpackError'),
2836
) as typeof import('webpack/lib/WebpackError');
@@ -62,10 +70,7 @@ function createLookupKey(
6270
request: string,
6371
config: { layer?: string | null },
6472
): string {
65-
if (config.layer) {
66-
return `(${config.layer})${request}`;
67-
}
68-
return request;
73+
return createLookupKeyForSharing(request, config.layer);
6974
}
7075

7176
class ProvideSharedPlugin {
@@ -193,6 +198,66 @@ class ProvideSharedPlugin {
193198
compilation.warnings.push(error);
194199
}
195200
}
201+
202+
// Apply version filters if defined
203+
if (version && typeof version === 'string') {
204+
// Check include version filter
205+
if (config.include?.version) {
206+
const includeVersion = config.include.version;
207+
if (typeof includeVersion === 'string') {
208+
if (!satisfy(version, includeVersion)) {
209+
const error = new WebpackError(
210+
`Provided module "${key}" version "${version}" does not satisfy include filter "${includeVersion}"`,
211+
);
212+
error.file = `shared module ${key} -> ${resource}`;
213+
compilation.warnings.push(error);
214+
return; // Skip providing this module
215+
}
216+
}
217+
}
218+
219+
// Check exclude version filter
220+
if (config.exclude?.version) {
221+
const excludeVersion = config.exclude.version;
222+
if (typeof excludeVersion === 'string') {
223+
if (satisfy(version, excludeVersion)) {
224+
const error = new WebpackError(
225+
`Provided module "${key}" version "${version}" matches exclude filter "${excludeVersion}"`,
226+
);
227+
error.file = `shared module ${key} -> ${resource}`;
228+
compilation.warnings.push(error);
229+
return; // Skip providing this module
230+
}
231+
}
232+
}
233+
234+
// Check singleton warnings for version filters
235+
if (config.singleton) {
236+
if (config.include?.version) {
237+
addSingletonFilterWarning(
238+
compilation,
239+
config.shareKey!,
240+
'include',
241+
'version',
242+
config.include.version,
243+
key,
244+
resource,
245+
);
246+
}
247+
if (config.exclude?.version) {
248+
addSingletonFilterWarning(
249+
compilation,
250+
config.shareKey!,
251+
'exclude',
252+
'version',
253+
config.exclude.version,
254+
key,
255+
resource,
256+
);
257+
}
258+
}
259+
}
260+
196261
const lookupKey = createLookupKey(resource, config);
197262
resolvedProvideMap.set(lookupKey, {
198263
config,
@@ -231,11 +296,51 @@ class ProvideSharedPlugin {
231296
const lookup = config.request || prefix;
232297
if (request.startsWith(lookup) && resource) {
233298
const remainder = request.slice(lookup.length);
299+
300+
// Apply request filters if defined
301+
if (
302+
!testRequestFilters(
303+
remainder,
304+
config.include?.request,
305+
config.exclude?.request,
306+
)
307+
) {
308+
continue; // Skip this match if filters don't pass
309+
}
310+
311+
const shareKey = config.shareKey + remainder;
312+
313+
// Check singleton warning for request filters
314+
if (config.singleton) {
315+
if (config.include?.request) {
316+
addSingletonFilterWarning(
317+
compilation,
318+
shareKey,
319+
'include',
320+
'request',
321+
config.include.request,
322+
request,
323+
resource,
324+
);
325+
}
326+
if (config.exclude?.request) {
327+
addSingletonFilterWarning(
328+
compilation,
329+
shareKey,
330+
'exclude',
331+
'request',
332+
config.exclude.request,
333+
request,
334+
resource,
335+
);
336+
}
337+
}
338+
234339
provideSharedModule(
235340
resource,
236341
{
237342
...config,
238-
shareKey: config.shareKey + remainder,
343+
shareKey,
239344
},
240345
resource,
241346
resourceResolveData,

0 commit comments

Comments
 (0)