@@ -57,11 +57,6 @@ import type {Context} from './types';
57
57
58
58
export type { Context } from './types' ;
59
59
60
- interface EsmModuleCache {
61
- beforeEvaluation : Promise < VMModule > ;
62
- fullyEvaluated : Promise < VMModule > ;
63
- }
64
-
65
60
const esmIsAvailable = typeof SourceTextModule === 'function' ;
66
61
67
62
interface JestGlobals extends Global . TestFrameworkGlobals {
@@ -176,8 +171,9 @@ export default class Runtime {
176
171
private readonly _moduleMocker : ModuleMocker ;
177
172
private _isolatedModuleRegistry : ModuleRegistry | null ;
178
173
private _moduleRegistry : ModuleRegistry ;
179
- private readonly _esmoduleRegistry : Map < Config . Path , EsmModuleCache > ;
174
+ private readonly _esmoduleRegistry : Map < Config . Path , VMModule > ;
180
175
private readonly _cjsNamedExports : Map < Config . Path , Set < string > > ;
176
+ private readonly _esmModuleLinkingMap : WeakMap < VMModule , Promise < unknown > > ;
181
177
private readonly _testPath : Config . Path ;
182
178
private readonly _resolver : Resolver ;
183
179
private _shouldAutoMock : boolean ;
@@ -227,6 +223,7 @@ export default class Runtime {
227
223
this . _moduleRegistry = new Map ( ) ;
228
224
this . _esmoduleRegistry = new Map ( ) ;
229
225
this . _cjsNamedExports = new Map ( ) ;
226
+ this . _esmModuleLinkingMap = new WeakMap ( ) ;
230
227
this . _testPath = testPath ;
231
228
this . _resolver = resolver ;
232
229
this . _scriptTransformer = new ScriptTransformer ( config , this . _cacheFS ) ;
@@ -368,10 +365,10 @@ export default class Runtime {
368
365
) ;
369
366
}
370
367
368
+ // not async _now_, but transform will be
371
369
private async loadEsmModule (
372
370
modulePath : Config . Path ,
373
371
query = '' ,
374
- isStaticImport = false ,
375
372
) : Promise < VMModule > {
376
373
const cacheKey = modulePath + query ;
377
374
@@ -383,14 +380,11 @@ export default class Runtime {
383
380
384
381
const context = this . _environment . getVmContext ( ) ;
385
382
386
- invariant ( context ) ;
383
+ invariant ( context , 'Test environment has been torn down' ) ;
387
384
388
385
if ( this . _resolver . isCoreModule ( modulePath ) ) {
389
386
const core = this . _importCoreModule ( modulePath , context ) ;
390
- this . _esmoduleRegistry . set ( cacheKey , {
391
- beforeEvaluation : core ,
392
- fullyEvaluated : core ,
393
- } ) ;
387
+ this . _esmoduleRegistry . set ( cacheKey , core ) ;
394
388
return core ;
395
389
}
396
390
@@ -405,89 +399,49 @@ export default class Runtime {
405
399
const module = new SourceTextModule ( transformedCode , {
406
400
context,
407
401
identifier : modulePath ,
408
- importModuleDynamically : (
402
+ importModuleDynamically : async (
409
403
specifier : string ,
410
404
referencingModule : VMModule ,
411
- ) =>
412
- this . linkModules (
405
+ ) => {
406
+ const module = await this . resolveModule (
413
407
specifier ,
414
408
referencingModule . identifier ,
415
409
referencingModule . context ,
416
- false ,
417
- ) ,
410
+ ) ;
411
+
412
+ return this . linkAndEvaluateModule ( module ) ;
413
+ } ,
418
414
initializeImportMeta ( meta : ImportMeta ) {
419
415
meta . url = pathToFileURL ( modulePath ) . href ;
420
416
} ,
421
417
} ) ;
422
418
423
- let resolve : ( value : VMModule ) => void ;
424
- let reject : ( value : any ) => void ;
425
- const promise = new Promise < VMModule > ( ( _resolve , _reject ) => {
426
- resolve = _resolve ;
427
- reject = _reject ;
428
- } ) ;
429
-
430
- // add to registry before link so that circular import won't end up stack overflow
431
- this . _esmoduleRegistry . set (
432
- cacheKey ,
433
- // we wanna put the linking promise in the cache so modules loaded in
434
- // parallel can all await it. We then await it synchronously below, so
435
- // we shouldn't get any unhandled rejections
436
- {
437
- beforeEvaluation : Promise . resolve ( module ) ,
438
- fullyEvaluated : promise ,
439
- } ,
440
- ) ;
441
-
442
- module
443
- . link ( ( specifier : string , referencingModule : VMModule ) =>
444
- this . linkModules (
445
- specifier ,
446
- referencingModule . identifier ,
447
- referencingModule . context ,
448
- true ,
449
- ) ,
450
- )
451
- . then ( ( ) => module . evaluate ( ) )
452
- . then (
453
- ( ) => resolve ( module ) ,
454
- ( e : any ) => reject ( e ) ,
455
- ) ;
419
+ this . _esmoduleRegistry . set ( cacheKey , module ) ;
456
420
}
457
421
458
- const entry = this . _esmoduleRegistry . get ( cacheKey ) ;
422
+ const module = this . _esmoduleRegistry . get ( cacheKey ) ;
459
423
460
- // return the already resolved, pre-evaluation promise
461
- // is loaded through static import to prevent promise deadlock
462
- // because module is evaluated after all static import is resolved
463
- const module = isStaticImport
464
- ? entry ?. beforeEvaluation
465
- : entry ?. fullyEvaluated ;
466
-
467
- invariant ( module ) ;
424
+ invariant (
425
+ module ,
426
+ 'Module cache does not contain module. This is a bug in Jest, please open up an issue' ,
427
+ ) ;
468
428
469
429
return module ;
470
430
}
471
431
472
- private linkModules (
432
+ private resolveModule (
473
433
specifier : string ,
474
434
referencingIdentifier : string ,
475
435
context : VMContext ,
476
- isStaticImport : boolean ,
477
436
) {
478
437
if ( specifier === '@jest/globals' ) {
479
438
const fromCache = this . _esmoduleRegistry . get ( '@jest/globals' ) ;
480
439
481
440
if ( fromCache ) {
482
- return isStaticImport
483
- ? fromCache . beforeEvaluation
484
- : fromCache . fullyEvaluated ;
441
+ return fromCache ;
485
442
}
486
443
const globals = this . getGlobalsForEsm ( referencingIdentifier , context ) ;
487
- this . _esmoduleRegistry . set ( '@jest/globals' , {
488
- beforeEvaluation : globals ,
489
- fullyEvaluated : globals ,
490
- } ) ;
444
+ this . _esmoduleRegistry . set ( '@jest/globals' , globals ) ;
491
445
492
446
return globals ;
493
447
}
@@ -504,12 +458,37 @@ export default class Runtime {
504
458
this . _resolver . isCoreModule ( resolved ) ||
505
459
this . unstable_shouldLoadAsEsm ( resolved )
506
460
) {
507
- return this . loadEsmModule ( resolved , query , isStaticImport ) ;
461
+ return this . loadEsmModule ( resolved , query ) ;
508
462
}
509
463
510
464
return this . loadCjsAsEsm ( referencingIdentifier , resolved , context ) ;
511
465
}
512
466
467
+ private async linkAndEvaluateModule ( module : VMModule ) {
468
+ if ( module . status === 'unlinked' ) {
469
+ // since we might attempt to link the same module in parallel, stick the promise in a weak map so every call to
470
+ // this method can await it
471
+ this . _esmModuleLinkingMap . set (
472
+ module ,
473
+ module . link ( ( specifier : string , referencingModule : VMModule ) =>
474
+ this . resolveModule (
475
+ specifier ,
476
+ referencingModule . identifier ,
477
+ referencingModule . context ,
478
+ ) ,
479
+ ) ,
480
+ ) ;
481
+ }
482
+
483
+ await this . _esmModuleLinkingMap . get ( module ) ;
484
+
485
+ if ( module . status === 'linked' ) {
486
+ await module . evaluate ( ) ;
487
+ }
488
+
489
+ return module ;
490
+ }
491
+
513
492
async unstable_importModule (
514
493
from : Config . Path ,
515
494
moduleName ?: string ,
@@ -523,7 +502,9 @@ export default class Runtime {
523
502
524
503
const modulePath = this . _resolveModule ( from , path ) ;
525
504
526
- return this . loadEsmModule ( modulePath , query ) ;
505
+ const module = await this . loadEsmModule ( modulePath , query ) ;
506
+
507
+ return this . linkAndEvaluateModule ( module ) ;
527
508
}
528
509
529
510
private loadCjsAsEsm (
@@ -1227,12 +1208,18 @@ export default class Runtime {
1227
1208
displayErrors : true ,
1228
1209
filename : scriptFilename ,
1229
1210
// @ts -expect-error: Experimental ESM API
1230
- importModuleDynamically : ( specifier : string ) => {
1211
+ importModuleDynamically : async ( specifier : string ) => {
1231
1212
const context = this . _environment . getVmContext ?.( ) ;
1232
1213
1233
- invariant ( context ) ;
1214
+ invariant ( context , 'Test environment has been torn down' ) ;
1215
+
1216
+ const module = await this . resolveModule (
1217
+ specifier ,
1218
+ scriptFilename ,
1219
+ context ,
1220
+ ) ;
1234
1221
1235
- return this . linkModules ( specifier , scriptFilename , context , false ) ;
1222
+ return this . linkAndEvaluateModule ( module ) ;
1236
1223
} ,
1237
1224
} ) ;
1238
1225
} catch ( e ) {
0 commit comments