Skip to content

Commit c335e3b

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

File tree

1 file changed

+345
-2
lines changed

1 file changed

+345
-2
lines changed

packages/enhanced/test/compiler-unit/sharing/ConsumeSharedPlugin.test.ts

Lines changed: 345 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ describe('ConsumeSharedPlugin', () => {
4747
path.join(nodeModulesDir, 'react/index.js'),
4848
'module.exports = { version: "17.0.2" };',
4949
);
50+
51+
// Add a project-level package.json to testDir
52+
fs.writeFileSync(
53+
path.join(testDir, 'package.json'),
54+
JSON.stringify({
55+
name: 'test-nested-include',
56+
version: '1.0.0',
57+
dependencies: {
58+
react: '16.8.0',
59+
'some-package': '1.0.0',
60+
},
61+
devDependencies: {
62+
jest: '^29.0.0',
63+
webpack: '^5.0.0',
64+
},
65+
}, null, 2),
66+
);
5067
});
5168

5269
afterEach(() => {
@@ -264,6 +281,18 @@ describe('ConsumeSharedPlugin', () => {
264281
'module.exports = { version: "16.8.0" };',
265282
);
266283

284+
// Create a root package.json for the test project with react dependency
285+
fs.writeFileSync(
286+
path.join(testDir, 'package.json'),
287+
JSON.stringify({
288+
name: 'test-project',
289+
version: '1.0.0',
290+
dependencies: {
291+
react: '16.8.0',
292+
},
293+
}, null, 2),
294+
);
295+
267296
// Create entry file
268297
fs.writeFileSync(
269298
path.join(srcDir, 'index.js'),
@@ -295,6 +324,7 @@ describe('ConsumeSharedPlugin', () => {
295324
import: 'react',
296325
shareKey: 'react',
297326
shareScope: 'default',
327+
requiredVersion: '^16.0.0', // Explicitly set requiredVersion
298328
exclude: {
299329
version: '^16.0.0', // Should exclude React 16.x.x
300330
},
@@ -309,8 +339,9 @@ describe('ConsumeSharedPlugin', () => {
309339
const stats = await compile(compiler);
310340

311341
expect(stats.hasErrors()).toBe(false);
342+
expect(stats.hasWarnings()).toBe(false); // Assert no warnings
312343

313-
const output = stats.toJson({ modules: true });
344+
const output = stats.toJson();
314345
const consumeSharedModule = output.modules?.find(
315346
(m) =>
316347
m.moduleType === 'consume-shared-module' &&
@@ -546,7 +577,6 @@ describe('ConsumeSharedPlugin', () => {
546577
new ConsumeSharedPlugin({
547578
consumes: {
548579
react: {
549-
import: 'react',
550580
shareKey: 'react',
551581
shareScope: 'default',
552582
requiredVersion: false, // Allow any version
@@ -648,4 +678,317 @@ describe('ConsumeSharedPlugin', () => {
648678
// Module should be excluded since version matches exclude pattern
649679
expect(consumeSharedModule).toBeUndefined();
650680
});
681+
682+
describe('include functionality', () => {
683+
describe('version-based inclusion', () => {
684+
it('should include module when version matches include.version', async () => {
685+
// Setup React v17.0.2 which should be included
686+
fs.writeFileSync(
687+
path.join(nodeModulesDir, 'react/package.json'),
688+
JSON.stringify({
689+
name: 'react',
690+
version: '17.0.2',
691+
}),
692+
);
693+
fs.writeFileSync(
694+
path.join(nodeModulesDir, 'react/index.js'),
695+
'module.exports = { version: "17.0.2" };',
696+
);
697+
698+
// Create a root package.json for the test project with react dependency
699+
fs.writeFileSync(
700+
path.join(testDir, 'package.json'),
701+
JSON.stringify({
702+
name: 'test-project',
703+
version: '1.0.0',
704+
dependencies: {
705+
react: '17.0.2',
706+
},
707+
}, null, 2),
708+
);
709+
710+
// Create entry file
711+
fs.writeFileSync(
712+
path.join(srcDir, 'index.js'),
713+
`
714+
import React from 'react';
715+
console.log('React version:', React.version);
716+
`,
717+
);
718+
719+
const config: Configuration = {
720+
mode: 'development',
721+
context: testDir,
722+
entry: path.join(srcDir, 'index.js'),
723+
output: {
724+
path: path.join(testDir, 'dist'),
725+
filename: 'bundle.js',
726+
},
727+
resolve: {
728+
extensions: ['.js', '.json'],
729+
},
730+
plugins: [
731+
new FederationRuntimePlugin({
732+
name: 'consumer',
733+
filename: 'remoteEntry.js',
734+
}),
735+
new ConsumeSharedPlugin({
736+
consumes: {
737+
react: {
738+
import: 'react',
739+
shareKey: 'react',
740+
shareScope: 'default',
741+
requiredVersion: '^17.0.0',
742+
include: {
743+
version: '^17.0.0', // Should include React 17.x.x
744+
},
745+
singleton: false,
746+
},
747+
},
748+
}),
749+
],
750+
};
751+
752+
const compiler = webpack(config);
753+
const stats = await compile(compiler);
754+
755+
expect(stats.hasErrors()).toBe(false);
756+
expect(stats.hasWarnings()).toBe(false);
757+
758+
const output = stats.toJson();
759+
console.log(output);
760+
const consumeSharedModules = output.modules?.filter(
761+
(m) =>
762+
m.moduleType === 'consume-shared-module' &&
763+
m.name?.includes('react'),
764+
);
765+
console.log(consumeSharedModules);
766+
// Module should be included since version matches include pattern
767+
expect(consumeSharedModules).toBeDefined();
768+
expect(consumeSharedModules?.length).toBeGreaterThan(0);
769+
// There should be at least one module
770+
expect(consumeSharedModules.length).toBeGreaterThan(0);
771+
// All included modules should point to the correct fallback path ([email protected])
772+
consumeSharedModules.forEach(module => {
773+
expect(module.identifier).toContain('node_modules/react/index.js');
774+
expect(module.identifier).not.toContain('16.');
775+
});
776+
});
777+
778+
it('should include only root module when version matches include.version', async () => {
779+
// Setup React v17.0.2 in the root node_modules (should be included)
780+
fs.writeFileSync(
781+
path.join(nodeModulesDir, 'react/package.json'),
782+
JSON.stringify({
783+
name: 'react',
784+
version: '17.0.2',
785+
}),
786+
);
787+
fs.writeFileSync(
788+
path.join(nodeModulesDir, 'react/index.js'),
789+
'module.exports = { version: "17.0.2" };',
790+
);
791+
792+
// Setup React v16.8.0 in a nested node_modules (should be excluded)
793+
const nestedPackageDir = path.join(nodeModulesDir, 'some-package');
794+
const nestedReactDir = path.join(nestedPackageDir, 'node_modules/react');
795+
fs.mkdirSync(nestedReactDir, { recursive: true });
796+
fs.writeFileSync(
797+
path.join(nestedReactDir, 'package.json'),
798+
JSON.stringify({
799+
name: 'react',
800+
version: '16.8.0',
801+
}),
802+
);
803+
fs.writeFileSync(
804+
path.join(nestedReactDir, 'index.js'),
805+
'module.exports = { version: "16.8.0" };',
806+
);
807+
fs.writeFileSync(
808+
path.join(nestedPackageDir, 'package.json'),
809+
JSON.stringify({
810+
name: 'some-package',
811+
version: '1.0.0',
812+
dependencies: { react: '^16.0.0' },
813+
}),
814+
);
815+
// Ensure some-package/index.js imports its own local react
816+
fs.writeFileSync(
817+
path.join(nestedPackageDir, 'index.js'),
818+
'import React from "react"; export default React;',
819+
);
820+
821+
// Create entry file that imports from both paths
822+
fs.writeFileSync(
823+
path.join(srcDir, 'index.js'),
824+
`
825+
import RootReact from "react";
826+
import NestedReactPkg from "some-package";
827+
console.log(RootReact.version, NestedReactPkg.default.version);
828+
`,
829+
);
830+
831+
const config = {
832+
mode: 'development',
833+
context: testDir,
834+
entry: path.join(srcDir, 'index.js'),
835+
output: {
836+
path: path.join(testDir, 'dist'),
837+
filename: 'bundle.js',
838+
},
839+
resolve: {
840+
extensions: ['.js', '.json'],
841+
modules: [
842+
'node_modules',
843+
path.join(testDir, 'node_modules'),
844+
],
845+
},
846+
plugins: [
847+
new FederationRuntimePlugin({
848+
name: 'consumer',
849+
filename: 'remoteEntry.js',
850+
}),
851+
new ConsumeSharedPlugin({
852+
consumes: {
853+
react: {
854+
shareKey: 'react',
855+
shareScope: 'default',
856+
include: {
857+
version: '^17.0.0', // Should only include React 17.x.x
858+
},
859+
singleton: false,
860+
},
861+
},
862+
}),
863+
],
864+
};
865+
866+
const compiler = webpack(config);
867+
const stats = await compile(compiler);
868+
869+
expect(stats.hasErrors()).toBe(false);
870+
expect(stats.hasWarnings()).toBe(false);
871+
872+
const output = stats.toJson({ modules: true });
873+
const consumeSharedModules = output.modules?.filter(
874+
(m) => m.moduleType === 'consume-shared-module' && m.name?.includes('react'),
875+
) || [];
876+
// There should be at least one module for the correct version (root)
877+
expect(consumeSharedModules.some(m => m.identifier.includes('node_modules/react/index.js') && !m.identifier.includes('some-package'))).toBe(true);
878+
// There should be no modules for the nested version (16.x.x)
879+
expect(consumeSharedModules.some(m => m.identifier.includes('some-package/node_modules/react/index.js'))).toBe(false);
880+
});
881+
882+
it('should include only nested module when version matches include.version (multi-version structure)', async () => {
883+
// Setup [email protected] in the root node_modules (should be excluded)
884+
const sharedRootDir = path.join(nodeModulesDir, 'shared');
885+
fs.mkdirSync(sharedRootDir, { recursive: true });
886+
fs.writeFileSync(
887+
path.join(sharedRootDir, 'package.json'),
888+
JSON.stringify({ name: 'shared', version: '1.0.0' }),
889+
);
890+
fs.writeFileSync(
891+
path.join(sharedRootDir, 'index.js'),
892+
'module.exports = { version: "1.0.0" };',
893+
);
894+
895+
// Add a base package.json to testDir to avoid missing dependency warnings
896+
fs.writeFileSync(
897+
path.join(testDir, 'package.json'),
898+
JSON.stringify({
899+
name: 'test-multi-version-include',
900+
version: '1.0.0',
901+
dependencies: {
902+
shared: '1.0.0',
903+
'my-module': '1.0.0',
904+
},
905+
}, null, 2),
906+
);
907+
908+
// Setup my-module with its own node_modules/[email protected] (should be included)
909+
const myModuleDir = path.join(nodeModulesDir, 'my-module');
910+
const myModuleNodeModules = path.join(myModuleDir, 'node_modules');
911+
const sharedNestedDir = path.join(myModuleNodeModules, 'shared');
912+
fs.mkdirSync(sharedNestedDir, { recursive: true });
913+
fs.writeFileSync(
914+
path.join(sharedNestedDir, 'package.json'),
915+
JSON.stringify({ name: 'shared', version: '2.0.0' }),
916+
);
917+
fs.writeFileSync(
918+
path.join(sharedNestedDir, 'index.js'),
919+
'module.exports = { version: "2.0.0" };',
920+
);
921+
fs.writeFileSync(
922+
path.join(myModuleDir, 'package.json'),
923+
JSON.stringify({ name: 'my-module', version: '1.0.0', dependencies: { shared: '^2.0.0' } }),
924+
);
925+
fs.writeFileSync(
926+
path.join(myModuleDir, 'index.js'),
927+
'import shared from "shared"; export const version = shared.version;',
928+
);
929+
930+
// Create entry file that imports from both paths
931+
fs.writeFileSync(
932+
path.join(srcDir, 'index.js'),
933+
`
934+
import shared from "shared";
935+
import * as myModule from "my-module";
936+
console.log(shared.version, myModule.version);
937+
`,
938+
);
939+
940+
const config = {
941+
mode: 'development',
942+
context: testDir,
943+
entry: path.join(srcDir, 'index.js'),
944+
output: {
945+
path: path.join(testDir, 'dist'),
946+
filename: 'bundle.js',
947+
},
948+
resolve: {
949+
extensions: ['.js', '.json'],
950+
modules: [
951+
'node_modules',
952+
path.join(testDir, 'node_modules'),
953+
],
954+
},
955+
plugins: [
956+
new FederationRuntimePlugin({
957+
name: 'consumer',
958+
filename: 'remoteEntry.js',
959+
}),
960+
new ConsumeSharedPlugin({
961+
consumes: {
962+
shared: {
963+
shareKey: 'shared',
964+
shareScope: 'default',
965+
include: {
966+
version: '^2.0.0', // Should only include [email protected]
967+
},
968+
singleton: false,
969+
},
970+
},
971+
}),
972+
],
973+
};
974+
975+
const compiler = webpack(config);
976+
const stats = await compile(compiler);
977+
978+
expect(stats.hasErrors()).toBe(false);
979+
expect(stats.hasWarnings()).toBe(false);
980+
981+
const output = stats.toJson({ modules: true });
982+
const consumeSharedModules = output.modules?.filter(
983+
(m) => m.moduleType === 'consume-shared-module' && m.name?.includes('shared'),
984+
) || [];
985+
// --- DEBUG LOGGING ---
986+
console.log('DEBUG: consumeSharedModules:', consumeSharedModules.map(m => ({ identifier: m.identifier, version: m.version })));
987+
// There should be at least one module for the correct version (nested)
988+
expect(consumeSharedModules.some(m => m.identifier.includes('my-module/node_modules/shared/index.js'))).toBe(true);
989+
// There should be no modules for the root version (1.0.0)
990+
expect(consumeSharedModules.some(m => m.identifier.includes('node_modules/shared/index.js') && !m.identifier.includes('my-module'))).toBe(false);
991+
});
992+
});
993+
});
651994
});

0 commit comments

Comments
 (0)