@@ -7,6 +7,12 @@ import { createMockCompilation, createWebpackMock, shareScopes } from './utils';
77// Use rs.hoisted() to create mock functions that are hoisted along with rs.mock()
88const mocks = rs . hoisted ( ( ) => ( {
99 mockNormalizeWebpackPath : rs . fn ( ( path : string ) => path ) ,
10+ mockCompareModulesByIdentifier : rs . fn (
11+ ( a : { identifier ?: ( ) => string } , b : { identifier ?: ( ) => string } ) => {
12+ if ( ! a . identifier || ! b . identifier ) return 0 ;
13+ return a . identifier ( ) . localeCompare ( b . identifier ( ) ) ;
14+ } ,
15+ ) ,
1016} ) ) ;
1117
1218// Mock normalize-webpack-path
@@ -17,6 +23,10 @@ rs.mock('@module-federation/sdk/normalize-webpack-path', () => ({
1723// Mock webpack - createWebpackMock already includes RuntimeModule with STAGE_* constants
1824rs . mock ( 'webpack' , ( ) => createWebpackMock ( ) ) ;
1925
26+ rs . mock ( 'webpack/lib/util/comparators' , ( ) => ( {
27+ compareModulesByIdentifier : mocks . mockCompareModulesByIdentifier ,
28+ } ) ) ;
29+
2030// Import the real implementation
2131import ConsumeSharedRuntimeModule from '../../../src/lib/sharing/ConsumeSharedRuntimeModule' ;
2232
@@ -91,6 +101,14 @@ describe('ConsumeSharedRuntimeModule', () => {
91101 } ) ;
92102
93103 describe ( 'generate' , ( ) => {
104+ const createChunk = ( id : string | number ) =>
105+ ( {
106+ id,
107+ ids : [ ] ,
108+ runtime : 'runtime' ,
109+ getAllInitialChunks : rs . fn ( ) . mockReturnValue ( [ ] ) ,
110+ } ) as any ;
111+
94112 it ( 'should generate code with string shareScope' , ( ) => {
95113 // Create a set of runtime requirements for the constructor
96114 const runtimeRequirements = new Set ( [ 'some-requirement' ] ) ;
@@ -310,5 +328,107 @@ describe('ConsumeSharedRuntimeModule', () => {
310328 // The result might be null or a string
311329 expect ( result === null || typeof result === 'string' ) . toBeTruthy ( ) ;
312330 } ) ;
331+
332+ it ( 'should use ordered consume-shared modules for referenced and initial chunks' , ( ) => {
333+ const runtimeRequirements = new Set ( [ 'some-requirement' ] ) ;
334+ const module = new ConsumeSharedRuntimeModule ( runtimeRequirements ) ;
335+ const referencedChunk = createChunk ( 'referenced' ) ;
336+ const initialChunk = createChunk ( 'initial' ) ;
337+
338+ module . chunk = {
339+ id : 'test-chunk' ,
340+ ids : [ ] ,
341+ getAllReferencedChunks : rs . fn ( ) . mockReturnValue ( [ referencedChunk ] ) ,
342+ getAllInitialChunks : rs . fn ( ) . mockReturnValue ( [ initialChunk ] ) ,
343+ } as any ;
344+
345+ mockCompilation . chunkGraph . getOrderedChunkModulesIterableBySourceType = rs
346+ . fn ( )
347+ . mockReturnValue ( [ ] ) ;
348+
349+ module . compilation = mockCompilation as any ;
350+ module . chunkGraph = mockCompilation . chunkGraph as any ;
351+
352+ expect ( module . generate ( ) ) . toBeNull ( ) ;
353+
354+ expect (
355+ mockCompilation . chunkGraph . getOrderedChunkModulesIterableBySourceType ,
356+ ) . toHaveBeenCalledWith (
357+ referencedChunk ,
358+ 'consume-shared' ,
359+ expect . any ( Function ) ,
360+ ) ;
361+ expect (
362+ mockCompilation . chunkGraph . getOrderedChunkModulesIterableBySourceType ,
363+ ) . toHaveBeenCalledWith (
364+ initialChunk ,
365+ 'consume-shared' ,
366+ expect . any ( Function ) ,
367+ ) ;
368+ expect (
369+ mockCompilation . chunkGraph . getChunkModulesIterableBySourceType ,
370+ ) . not . toHaveBeenCalled ( ) ;
371+ } ) ;
372+
373+ it ( 'should generate deterministic output when consume-shared insertion order changes' , ( ) => {
374+ const createConsumeModule = ( identifier : string , id : string ) =>
375+ ( {
376+ id,
377+ identifier : ( ) => identifier ,
378+ } ) as any ;
379+ const moduleA = createConsumeModule ( 'consume ./a' , 'a' ) ;
380+ const moduleB = createConsumeModule ( 'consume ./b' , 'b' ) ;
381+
382+ const generate = ( modules : any [ ] ) => {
383+ const { mockCompilation : compilation } = createMockCompilation ( ) ;
384+ const runtimeRequirements = new Set ( [ '__webpack_require__.f' ] ) ;
385+ const runtimeModule = new ConsumeSharedRuntimeModule (
386+ runtimeRequirements ,
387+ ) ;
388+ const chunk = createChunk ( 'async' ) ;
389+
390+ runtimeModule . chunk = {
391+ id : 'test-chunk' ,
392+ ids : [ ] ,
393+ getAllReferencedChunks : rs . fn ( ) . mockReturnValue ( [ chunk ] ) ,
394+ getAllInitialChunks : rs . fn ( ) . mockReturnValue ( [ chunk ] ) ,
395+ } as any ;
396+
397+ compilation . chunkGraph . getChunkModulesIterableBySourceType = rs
398+ . fn ( )
399+ . mockReturnValue ( modules ) ;
400+ compilation . chunkGraph . getOrderedChunkModulesIterableBySourceType = rs
401+ . fn ( )
402+ . mockImplementation (
403+ (
404+ _chunk : unknown ,
405+ _sourceType : string ,
406+ compare : ( a : unknown , b : unknown ) => number ,
407+ ) => [ ...modules ] . sort ( compare ) ,
408+ ) ;
409+ compilation . chunkGraph . getModuleId = rs . fn ( ( m : any ) => m . id ) ;
410+ compilation . codeGenerationResults . getSource = rs . fn ( ( m : any ) => ( {
411+ source : ( ) => `fallback_${ m . id } ` ,
412+ } ) ) ;
413+ compilation . codeGenerationResults . getData = rs . fn ( ( m : any ) => ( {
414+ shareScope : 'default' ,
415+ shareConfig : {
416+ singleton : false ,
417+ requiredVersion : false ,
418+ strictVersion : false ,
419+ eager : false ,
420+ layer : null ,
421+ } ,
422+ shareKey : m . id ,
423+ } ) ) ;
424+
425+ runtimeModule . compilation = compilation as any ;
426+ runtimeModule . chunkGraph = compilation . chunkGraph as any ;
427+
428+ return runtimeModule . generate ( ) ;
429+ } ;
430+
431+ expect ( generate ( [ moduleB , moduleA ] ) ) . toBe ( generate ( [ moduleA , moduleB ] ) ) ;
432+ } ) ;
313433 } ) ;
314434} ) ;
0 commit comments