Skip to content

Commit d097605

Browse files
test: add consume share plugin compiler tests
1 parent c335e3b commit d097605

18 files changed

+730
-150
lines changed

packages/enhanced/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
},
9292
"dependencies": {
9393
"@module-federation/bridge-react-webpack-plugin": "workspace:*",
94+
"@module-federation/cli": "workspace:*",
9495
"@module-federation/data-prefetch": "workspace:*",
9596
"@module-federation/dts-plugin": "workspace:*",
9697
"@module-federation/error-codes": "workspace:*",
@@ -100,9 +101,8 @@
100101
"@module-federation/rspack": "workspace:*",
101102
"@module-federation/runtime-tools": "workspace:*",
102103
"@module-federation/sdk": "workspace:*",
103-
"@module-federation/cli": "workspace:*",
104104
"btoa": "^1.2.1",
105-
"upath": "2.0.1",
106-
"schema-utils": "^4.3.0"
105+
"schema-utils": "^4.3.0",
106+
"upath": "2.0.1"
107107
}
108108
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ export interface ConsumeSharedPluginOptions {
2525
* Share scope name used for all consumed modules (defaults to 'default').
2626
*/
2727
shareScope?: string | string[];
28+
/**
29+
* Experimental features options
30+
*/
31+
experiments?: {
32+
/**
33+
* Enable reconstructed lookup for node_modules paths
34+
*/
35+
nodeModulesReconstructedLookup?: boolean;
36+
};
2837
}
2938
/**
3039
* Modules that should be consumed from share scope. Property names are used to match requested modules in this compilation. Relative requests are resolved, module requests are matched unresolved, absolute paths will match resolved requests. A trailing slash will match all requests with this prefix. In this case shareKey must also have a trailing slash.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ export interface ProvideSharedPluginOptions {
2222
* Share scope name used for all provided modules (defaults to 'default').
2323
*/
2424
shareScope?: string | string[];
25+
/**
26+
* Experimental features options
27+
*/
28+
experiments?: {
29+
/**
30+
* Enable reconstructed lookup for node_modules paths
31+
*/
32+
nodeModulesReconstructedLookup?: boolean;
33+
};
2534
}
2635
/**
2736
* Modules that should be provided as shared modules to the share scope. Property names are used as share keys.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ export interface SharePluginOptions {
2525
* Modules that should be shared in the share scope. When provided, property names are used to match requested modules in this compilation.
2626
*/
2727
shared: Shared;
28+
/**
29+
* Experimental features options
30+
*/
31+
experiments?: {
32+
/**
33+
* Enable reconstructed lookup for node_modules paths
34+
*/
35+
nodeModulesReconstructedLookup?: boolean;
36+
};
2837
}
2938
/**
3039
* Modules that should be shared in the share scope. Property names are used to match requested modules in this compilation. Relative requests are resolved, module requests are matched unresolved, absolute paths will match resolved requests. A trailing slash will match all requests with this prefix. In this case shareKey must also have a trailing slash.

packages/enhanced/src/lib/container/ModuleFederationPlugin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ class ModuleFederationPlugin implements WebpackPluginInstance {
189189
new SharePlugin({
190190
shared: options.shared,
191191
shareScope: options.shareScope,
192+
experiments: {
193+
nodeModulesReconstructedLookup:
194+
options.experiments?.nodeModulesReconstructedLookup,
195+
},
192196
}).apply(compiler);
193197
}
194198
});

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

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { isRequiredVersion } from '@module-federation/sdk';
1212
import type { Compiler, Compilation, Module } from 'webpack';
1313
import { parseOptions } from '../container/options';
14-
import { ConsumeSharedPluginOptions } from '../../declarations/plugins/sharing/ConsumeSharedPlugin';
14+
import type { ConsumeSharedPluginOptions } from '../../declarations/plugins/sharing/ConsumeSharedPlugin';
1515
import { resolveMatchedConfigs } from './resolveMatchedConfigs';
1616
import {
1717
getDescriptionFile,
@@ -75,12 +75,15 @@ function createLookupKey(
7575

7676
class ConsumeSharedPlugin {
7777
private _consumes: [string, ConsumeOptions][];
78+
private _experiments: NonNullable<ConsumeSharedPluginOptions['experiments']>;
7879

7980
constructor(options: ConsumeSharedPluginOptions) {
8081
if (typeof options !== 'string') {
8182
validate(options);
8283
}
8384

85+
this._experiments = options.experiments || {};
86+
8487
this._consumes = parseOptions(
8588
options.consumes,
8689
(item, key) => {
@@ -101,6 +104,8 @@ class ConsumeSharedPlugin {
101104
issuerLayer: undefined,
102105
layer: undefined,
103106
request: key,
107+
include: undefined,
108+
exclude: undefined,
104109
}
105110
: // key is a request/key
106111
// item is a version
@@ -117,6 +122,8 @@ class ConsumeSharedPlugin {
117122
issuerLayer: undefined,
118123
layer: undefined,
119124
request: key,
125+
include: undefined,
126+
exclude: undefined,
120127
};
121128
return result;
122129
},
@@ -139,6 +146,7 @@ class ConsumeSharedPlugin {
139146
singleton: !!item.singleton,
140147
eager: !!item.eager,
141148
exclude: item.exclude,
149+
include: item.include,
142150
issuerLayer: item.issuerLayer ? item.issuerLayer : undefined,
143151
layer: item.layer ? item.layer : undefined,
144152
request,
@@ -433,10 +441,13 @@ class ConsumeSharedPlugin {
433441
) {
434442
return;
435443
}
444+
const { context, request, contextInfo } = resolveData;
445+
436446
const match = unresolvedConsumes.get(
437447
createLookupKey(request, contextInfo),
438448
);
439449

450+
// First check direct match
440451
if (match !== undefined) {
441452
// Use the bound function
442453
return boundCreateConsumeSharedModule(
@@ -446,6 +457,60 @@ class ConsumeSharedPlugin {
446457
match,
447458
);
448459
}
460+
461+
// Then try relative path handling and node_modules paths
462+
let reconstructed: string | null = null;
463+
let modulePathAfterNodeModules: string | null = null;
464+
465+
if (
466+
this._experiments.nodeModulesReconstructedLookup &&
467+
request &&
468+
!path.isAbsolute(request) &&
469+
/^(\.\.?(\/|$)|\/|[A-Za-z]:|\\\\)/.test(request)
470+
) {
471+
reconstructed = path.join(context, request);
472+
473+
// Check if path contains node_modules and extract the part after it
474+
if (reconstructed.includes('node_modules')) {
475+
const nodeModulesIndex =
476+
reconstructed.lastIndexOf('node_modules');
477+
modulePathAfterNodeModules = reconstructed.substring(
478+
nodeModulesIndex + 'node_modules/'.length,
479+
);
480+
}
481+
482+
// Try to match with module path after node_modules
483+
if (modulePathAfterNodeModules) {
484+
const moduleMatch = unresolvedConsumes.get(
485+
createLookupKey(modulePathAfterNodeModules, contextInfo),
486+
);
487+
488+
if (moduleMatch !== undefined) {
489+
return boundCreateConsumeSharedModule(
490+
compilation,
491+
context,
492+
modulePathAfterNodeModules,
493+
moduleMatch,
494+
);
495+
}
496+
}
497+
498+
// Try to match with the full reconstructed path
499+
const reconstructedMatch = unresolvedConsumes.get(
500+
createLookupKey(reconstructed, contextInfo),
501+
);
502+
503+
if (reconstructedMatch !== undefined) {
504+
return boundCreateConsumeSharedModule(
505+
compilation,
506+
context,
507+
reconstructed,
508+
reconstructedMatch,
509+
);
510+
}
511+
}
512+
513+
// Check for prefixed consumes with original request
449514
for (const [prefix, options] of prefixedConsumes) {
450515
const lookup = options.request || prefix;
451516
if (request.startsWith(lookup)) {
@@ -489,6 +554,99 @@ class ConsumeSharedPlugin {
489554
);
490555
}
491556
}
557+
558+
// Also check prefixed consumes with modulePathAfterNodeModules
559+
if (modulePathAfterNodeModules) {
560+
for (const [prefix, options] of prefixedConsumes) {
561+
const lookup = options.request || prefix;
562+
if (modulePathAfterNodeModules.startsWith(lookup)) {
563+
const remainder = modulePathAfterNodeModules.slice(
564+
lookup.length,
565+
);
566+
567+
// Check include/exclude as before
568+
if (
569+
options.include &&
570+
options.include.request &&
571+
!(options.include.request instanceof RegExp
572+
? options.include.request.test(remainder)
573+
: remainder === options.include.request)
574+
) {
575+
continue;
576+
}
577+
578+
if (
579+
options.exclude &&
580+
options.exclude.request &&
581+
(options.exclude.request instanceof RegExp
582+
? options.exclude.request.test(remainder)
583+
: remainder === options.exclude.request)
584+
) {
585+
continue;
586+
}
587+
588+
return boundCreateConsumeSharedModule(
589+
compilation,
590+
context,
591+
modulePathAfterNodeModules,
592+
{
593+
...options,
594+
import: options.import
595+
? options.import + remainder
596+
: undefined,
597+
shareKey: options.shareKey + remainder,
598+
layer: options.layer || contextInfo.issuerLayer,
599+
},
600+
);
601+
}
602+
}
603+
}
604+
605+
// Finally check prefixed consumes with reconstructed path
606+
if (reconstructed) {
607+
for (const [prefix, options] of prefixedConsumes) {
608+
const lookup = options.request || prefix;
609+
if (reconstructed.startsWith(lookup)) {
610+
const remainder = reconstructed.slice(lookup.length);
611+
612+
// Check include/exclude as before
613+
if (
614+
options.include &&
615+
options.include.request &&
616+
!(options.include.request instanceof RegExp
617+
? options.include.request.test(remainder)
618+
: remainder === options.include.request)
619+
) {
620+
continue;
621+
}
622+
623+
if (
624+
options.exclude &&
625+
options.exclude.request &&
626+
(options.exclude.request instanceof RegExp
627+
? options.exclude.request.test(remainder)
628+
: remainder === options.exclude.request)
629+
) {
630+
continue;
631+
}
632+
633+
return boundCreateConsumeSharedModule(
634+
compilation,
635+
context,
636+
reconstructed,
637+
{
638+
...options,
639+
import: options.import
640+
? options.import + remainder
641+
: undefined,
642+
shareKey: options.shareKey + remainder,
643+
layer: options.layer || contextInfo.issuerLayer,
644+
},
645+
);
646+
}
647+
}
648+
}
649+
492650
return;
493651
});
494652
},

0 commit comments

Comments
 (0)