@@ -160,6 +160,51 @@ const ensureTreeShakingFixtures = (testDirectory: string) => {
160160 }
161161} ;
162162
163+ const collectTreeShakingMissingModuleStubs = ( outputDirectory : string ) => {
164+ const independentPackagesDir = path . join (
165+ outputDirectory ,
166+ 'independent-packages' ,
167+ ) ;
168+ if ( ! fs . existsSync ( independentPackagesDir ) ) {
169+ return [ ] as string [ ] ;
170+ }
171+ const missing = new Set < string > ( ) ;
172+ const pending = [ independentPackagesDir ] ;
173+ const pattern =
174+ / C a n n o t f i n d m o d u l e [ ' " ] ( [ ^ ' " ] * t r e e - s h a k i n g - s h a r e [ ^ ' " ] * n o d e _ m o d u l e s [ ^ ' " ] * ) [ ' " ] / g;
175+ while ( pending . length ) {
176+ const currentDir = pending . pop ( ) as string ;
177+ let entries : fs . Dirent [ ] = [ ] ;
178+ try {
179+ entries = fs . readdirSync ( currentDir , { withFileTypes : true } ) ;
180+ } catch {
181+ continue ;
182+ }
183+ for ( const entry of entries ) {
184+ const fullPath = path . join ( currentDir , entry . name ) ;
185+ if ( entry . isDirectory ( ) ) {
186+ pending . push ( fullPath ) ;
187+ continue ;
188+ }
189+ if ( ! entry . isFile ( ) || entry . name !== 'share-entry.js' ) {
190+ continue ;
191+ }
192+ let content = '' ;
193+ try {
194+ content = fs . readFileSync ( fullPath , 'utf-8' ) ;
195+ } catch {
196+ continue ;
197+ }
198+ pattern . lastIndex = 0 ;
199+ let match : RegExpExecArray | null = null ;
200+ while ( ( match = pattern . exec ( content ) ) ) {
201+ missing . add ( match [ 1 ] ) ;
202+ }
203+ }
204+ }
205+ return Array . from ( missing ) . sort ( ) ;
206+ } ;
207+
163208const dedupeByMessage = ( items : any [ ] ) => {
164209 if ( ! Array . isArray ( items ) || items . length === 0 ) {
165210 return [ ] as any [ ] ;
@@ -589,25 +634,28 @@ export const describeCases = (config: any) => {
589634 `${ testName } should compile` ,
590635 async ( ) => {
591636 ensureTreeShakingFixturesIfNeeded ( ) ;
592- try {
593- // Robust cleanup to avoid ENOTEMPTY and race conditions
594- ( fs as any ) . rmSync ?.( outputDirectory , {
595- recursive : true ,
596- force : true ,
597- } ) ;
598- } catch {
637+ const isTreeShakingFixtureCase = testDirectory . includes (
638+ `${ path . sep } tree-shaking-share${ path . sep } ` ,
639+ ) ;
640+ const cleanOutputDirectory = ( ) => {
599641 try {
600- rimrafSync ( outputDirectory ) ;
642+ // Robust cleanup to avoid ENOTEMPTY and race conditions
643+ ( fs as any ) . rmSync ?.( outputDirectory , {
644+ recursive : true ,
645+ force : true ,
646+ } ) ;
601647 } catch {
602- /* ignore */
648+ try {
649+ rimrafSync ( outputDirectory ) ;
650+ } catch {
651+ /* ignore */
652+ }
603653 }
604- }
605- fs . mkdirSync ( outputDirectory , { recursive : true } ) ;
606- infraStructureLog . length = 0 ;
607-
608- // 运行 webpack
609- const { stats } = await new Promise < { stats : any } > (
610- ( resolve , reject ) => {
654+ } ;
655+ const runWebpackCompile = async ( ) => {
656+ infraStructureLog . length = 0 ;
657+ stderr . reset ( ) ;
658+ return new Promise < { stats : any } > ( ( resolve , reject ) => {
611659 const onCompiled = ( err : any , stats : any ) => {
612660 if ( err ) return reject ( err ) ;
613661 resolve ( { stats } ) ;
@@ -631,12 +679,58 @@ export const describeCases = (config: any) => {
631679 } catch ( e : any ) {
632680 reject ( e ) ;
633681 }
634- } ,
635- ) . catch ( ( e ) => {
682+ } ) ;
683+ } ;
684+
685+ cleanOutputDirectory ( ) ;
686+ fs . mkdirSync ( outputDirectory , { recursive : true } ) ;
687+ let { stats } = await runWebpackCompile ( ) . catch ( ( e ) => {
636688 handleFatalError ( e ) ;
637689 throw e ; // rethrow for rstest to mark failure otherwise
638690 } ) ;
639691
692+ // Under heavy IO/CPU contention, tree-shaking fixture packages can
693+ // transiently resolve as missing. Rebuild with bounded retries
694+ // after re-ensuring fixtures when share-entry stubs contain
695+ // webpackMissingModule.
696+ if ( isTreeShakingFixtureCase ) {
697+ const maxTreeShakingAttempts = 4 ;
698+ for (
699+ let attempt = 1 ;
700+ attempt < maxTreeShakingAttempts ;
701+ attempt ++
702+ ) {
703+ const missingModules =
704+ collectTreeShakingMissingModuleStubs ( outputDirectory ) ;
705+ if ( ! missingModules . length ) {
706+ break ;
707+ }
708+ ensureTreeShakingFixturesIfNeeded ( ) ;
709+ // Give the FS a tiny settle window under extreme IO pressure.
710+ await new Promise < void > ( ( resolve ) =>
711+ setTimeout ( resolve , 25 * attempt ) ,
712+ ) ;
713+ cleanOutputDirectory ( ) ;
714+ fs . mkdirSync ( outputDirectory , { recursive : true } ) ;
715+ ( { stats } = await runWebpackCompile ( ) . catch ( ( e ) => {
716+ handleFatalError ( e ) ;
717+ throw e ;
718+ } ) ) ;
719+ if ( attempt === maxTreeShakingAttempts - 1 ) {
720+ const remainingMissingModules =
721+ collectTreeShakingMissingModuleStubs ( outputDirectory ) ;
722+ if ( remainingMissingModules . length ) {
723+ throw new Error (
724+ [
725+ `Tree-shaking fixture modules remained unresolved after ${ maxTreeShakingAttempts } compilation attempts:` ,
726+ ...remainingMissingModules ,
727+ ] . join ( '\n' ) ,
728+ ) ;
729+ }
730+ }
731+ }
732+ }
733+
640734 // 写入 stats
641735 const statOptions = { preset : 'verbose' , colors : false } ;
642736 fs . mkdirSync ( outputDirectory , { recursive : true } ) ;
0 commit comments