@@ -206,13 +206,11 @@ class ModuleLoader {
206
206
}
207
207
208
208
async eval ( source , url , isEntryPoint = false ) {
209
- const evalInstance = ( url ) => {
210
- return compileSourceTextModule ( url , source , this ) ;
211
- } ;
212
209
const { ModuleJob } = require ( 'internal/modules/esm/module_job' ) ;
210
+ const wrap = compileSourceTextModule ( url , source , this ) ;
213
211
const module = await onImport . tracePromise ( async ( ) => {
214
212
const job = new ModuleJob (
215
- this , url , undefined , evalInstance , false , false ) ;
213
+ this , url , undefined , wrap , false , false ) ;
216
214
this . loadCache . set ( url , undefined , job ) ;
217
215
const { module } = await job . run ( isEntryPoint ) ;
218
216
return module ;
@@ -230,40 +228,49 @@ class ModuleLoader {
230
228
}
231
229
232
230
/**
233
- * Get a (possibly still pending) module job from the cache,
234
- * or create one and return its Promise.
235
- * @param {string } specifier The string after `from` in an `import` statement,
236
- * or the first parameter of an `import()`
237
- * expression
238
- * @param {string | undefined } parentURL The URL of the module importing this
239
- * one, unless this is the Node.js entry
240
- * point.
241
- * @param {Record<string, string> } importAttributes Validations for the
242
- * module import.
243
- * @returns {Promise<ModuleJob> } The (possibly pending) module job
231
+ * Get a (possibly not yet fully linked) module job from the cache, or create one and return its Promise.
232
+ * @param {string } specifier The module request of the module to be resolved. Typically, what's
233
+ * requested by `import '<specifier>'` or `import('<specifier>')`.
234
+ * @param {string } [parentURL] The URL of the module where the module request is initiated.
235
+ * It's undefined if it's from the root module.
236
+ * @param {ImportAttributes } importAttributes Attributes from the import statement or expression.
237
+ * @returns {Promise<ModuleJobBase }
244
238
*/
245
- async getModuleJob ( specifier , parentURL , importAttributes ) {
239
+ async getModuleJobForImport ( specifier , parentURL , importAttributes ) {
246
240
const resolveResult = await this . resolve ( specifier , parentURL , importAttributes ) ;
247
- return this . getJobFromResolveResult ( resolveResult , parentURL , importAttributes ) ;
241
+ return this . # getJobFromResolveResult( resolveResult , parentURL , importAttributes , false ) ;
248
242
}
249
243
250
- getModuleJobSync ( specifier , parentURL , importAttributes ) {
244
+ /**
245
+ * Similar to {@link getModuleJobForImport} but it's used for `require()` resolved by the ESM loader
246
+ * in imported CJS modules. This runs synchronously and when it returns, the module job's module
247
+ * requests are all linked.
248
+ * @param {string } specifier See {@link getModuleJobForImport}
249
+ * @param {string } [parentURL] See {@link getModuleJobForImport}
250
+ * @param {ImportAttributes } importAttributes See {@link getModuleJobForImport}
251
+ * @returns {Promise<ModuleJobBase }
252
+ */
253
+ getModuleJobForRequireInImportedCJS ( specifier , parentURL , importAttributes ) {
251
254
const resolveResult = this . resolveSync ( specifier , parentURL , importAttributes ) ;
252
- return this . getJobFromResolveResult ( resolveResult , parentURL , importAttributes , true ) ;
255
+ return this . # getJobFromResolveResult( resolveResult , parentURL , importAttributes , true ) ;
253
256
}
254
257
255
- getJobFromResolveResult ( resolveResult , parentURL , importAttributes , sync ) {
258
+ /**
259
+ * Given a resolved module request, obtain a ModuleJobBase from it - if it's already cached,
260
+ * return the cached ModuleJobBase. Otherwise, load its source and translate it into a ModuleWrap first.
261
+ * @param {{ format: string, url: string } } resolveResult Resolved module request.
262
+ * @param {string } [parentURL] See {@link getModuleJobForImport}
263
+ * @param {ImportAttributes } importAttributes See {@link getModuleJobForImport}
264
+ * @param {boolean } isForRequireInImportedCJS Whether this is done for require() in imported CJS.
265
+ * @returns {ModuleJobBase }
266
+ */
267
+ #getJobFromResolveResult( resolveResult , parentURL , importAttributes , isForRequireInImportedCJS = false ) {
256
268
const { url, format } = resolveResult ;
257
269
const resolvedImportAttributes = resolveResult . importAttributes ?? importAttributes ;
258
270
let job = this . loadCache . get ( url , resolvedImportAttributes . type ) ;
259
271
260
- // CommonJS will set functions for lazy job evaluation.
261
- if ( typeof job === 'function' ) {
262
- this . loadCache . set ( url , undefined , job = job ( ) ) ;
263
- }
264
-
265
272
if ( job === undefined ) {
266
- job = this . #createModuleJob( url , resolvedImportAttributes , parentURL , format , sync ) ;
273
+ job = this . #createModuleJob( url , resolvedImportAttributes , parentURL , format , isForRequireInImportedCJS ) ;
267
274
}
268
275
269
276
return job ;
@@ -336,13 +343,9 @@ class ModuleLoader {
336
343
assert ( protocol === 'file:' || protocol === 'node:' || protocol === 'data:' ) ;
337
344
}
338
345
339
- const requestKey = this . #resolveCache. serializeKey ( specifier , importAttributes ) ;
340
- let resolveResult = this . #resolveCache. get ( requestKey , parentURL ) ;
341
- if ( resolveResult == null ) {
342
- resolveResult = this . defaultResolve ( specifier , parentURL , importAttributes ) ;
343
- this . #resolveCache. set ( requestKey , parentURL , resolveResult ) ;
344
- }
345
-
346
+ // TODO(joyeecheung): consolidate cache behavior and use resolveSync() and
347
+ // loadSync() here.
348
+ const resolveResult = this . #cachedDefaultResolve( specifier , parentURL , importAttributes ) ;
346
349
const { url, format } = resolveResult ;
347
350
if ( ! getOptionValue ( '--experimental-require-module' ) ) {
348
351
throw new ERR_REQUIRE_ESM ( url , true ) ;
@@ -371,23 +374,16 @@ class ModuleLoader {
371
374
const loadResult = defaultLoadSync ( url , { format, importAttributes } ) ;
372
375
const {
373
376
format : finalFormat ,
374
- responseURL,
375
377
source,
376
378
} = loadResult ;
377
379
378
- this . validateLoadResult ( url , finalFormat ) ;
379
380
if ( finalFormat === 'wasm' ) {
380
381
assert . fail ( 'WASM is currently unsupported by require(esm)' ) ;
381
382
}
382
383
383
- const translator = getTranslators ( ) . get ( finalFormat ) ;
384
- if ( ! translator ) {
385
- throw new ERR_UNKNOWN_MODULE_FORMAT ( finalFormat , responseURL ) ;
386
- }
387
-
388
384
const isMain = ( parentURL === undefined ) ;
389
- const wrap = FunctionPrototypeCall ( translator , this , responseURL , source , isMain ) ;
390
- assert ( wrap instanceof ModuleWrap ) ; // No asynchronous translators should be called.
385
+ const wrap = this . #translate ( url , finalFormat , source , isMain ) ;
386
+ assert ( wrap instanceof ModuleWrap , `Translator used for require( ${ url } ) should not be async` ) ;
391
387
392
388
if ( process . env . WATCH_REPORT_DEPENDENCIES && process . send ) {
393
389
process . send ( { 'watch:import' : [ url ] } ) ;
@@ -416,33 +412,96 @@ class ModuleLoader {
416
412
}
417
413
418
414
/**
419
- * Create and cache an object representing a loaded module.
420
- * @param {string } url The absolute URL that was resolved for this module
421
- * @param {Record<string, string> } importAttributes Validations for the
422
- * module import.
423
- * @param {string } [parentURL] The absolute URL of the module importing this
424
- * one, unless this is the Node.js entry point
425
- * @param {string } [format] The format hint possibly returned by the
426
- * `resolve` hook
427
- * @returns {Promise<ModuleJob> } The (possibly pending) module job
415
+ * Translate a loaded module source into a ModuleWrap. This is run synchronously,
416
+ * but the translator may return the ModuleWrap in a Promise.
417
+ * @param {stirng } url URL of the module to be translated.
418
+ * @param {string } format Format of the module to be translated. This is used to find
419
+ * matching translators.
420
+ * @param {ModuleSource } source Source of the module to be translated.
421
+ * @param {boolean } isMain Whether the module to be translated is the entry point.
422
+ * @returns {ModuleWrap | Promise<ModuleWrap> }
423
+ */
424
+ #translate( url , format , source , isMain ) {
425
+ this . validateLoadResult ( url , format ) ;
426
+ const translator = getTranslators ( ) . get ( format ) ;
427
+
428
+ if ( ! translator ) {
429
+ throw new ERR_UNKNOWN_MODULE_FORMAT ( format , url ) ;
430
+ }
431
+
432
+ return FunctionPrototypeCall ( translator , this , url , source , isMain ) ;
433
+ }
434
+
435
+ /**
436
+ * Load a module and translate it into a ModuleWrap for require() in imported CJS.
437
+ * This is run synchronously, and the translator always return a ModuleWrap synchronously.
438
+ * @param {string } url URL of the module to be translated.
439
+ * @param {object } loadContext See {@link load}
440
+ * @param {boolean } isMain Whether the module to be translated is the entry point.
441
+ * @returns {ModuleWrap }
428
442
*/
429
- #createModuleJob( url , importAttributes , parentURL , format , sync ) {
430
- const callTranslator = ( { format : finalFormat , responseURL, source } , isMain ) => {
431
- const translator = getTranslators ( ) . get ( finalFormat ) ;
443
+ loadAndTranslateForRequireInImportedCJS ( url , loadContext , isMain ) {
444
+ const { format : formatFromLoad , source } = this . #loadSync( url , loadContext ) ;
445
+
446
+ if ( formatFromLoad === 'wasm' ) { // require(wasm) is not supported.
447
+ throw new ERR_UNKNOWN_MODULE_FORMAT ( formatFromLoad , url ) ;
448
+ }
432
449
433
- if ( ! translator ) {
434
- throw new ERR_UNKNOWN_MODULE_FORMAT ( finalFormat , responseURL ) ;
450
+ if ( formatFromLoad === 'module' || formatFromLoad === 'module-typescript' ) {
451
+ if ( ! getOptionValue ( '--experimental-require-module' ) ) {
452
+ throw new ERR_REQUIRE_ESM ( url , true ) ;
435
453
}
454
+ }
436
455
437
- return FunctionPrototypeCall ( translator , this , responseURL , source , isMain ) ;
438
- } ;
439
- const context = { format, importAttributes } ;
456
+ let finalFormat = formatFromLoad ;
457
+ if ( formatFromLoad === 'commonjs' ) {
458
+ finalFormat = 'require-commonjs' ;
459
+ }
460
+ if ( formatFromLoad === 'commonjs-typescript' ) {
461
+ finalFormat = 'require-commonjs-typescript' ;
462
+ }
440
463
441
- const moduleProvider = sync ?
442
- ( url , isMain ) => callTranslator ( this . loadSync ( url , context ) , isMain ) :
443
- async ( url , isMain ) => callTranslator ( await this . load ( url , context ) , isMain ) ;
464
+ const wrap = this . #translate( url , finalFormat , source , isMain ) ;
465
+ assert ( wrap instanceof ModuleWrap , `Translator used for require(${ url } ) should not be async` ) ;
466
+ return wrap ;
467
+ }
468
+
469
+ /**
470
+ * Load a module and translate it into a ModuleWrap for ordinary imported ESM.
471
+ * This is run asynchronously.
472
+ * @param {string } url URL of the module to be translated.
473
+ * @param {object } loadContext See {@link load}
474
+ * @param {boolean } isMain Whether the module to be translated is the entry point.
475
+ * @returns {Promise<ModuleWrap> }
476
+ */
477
+ async loadAndTranslate ( url , loadContext , isMain ) {
478
+ const { format, source } = await this . load ( url , loadContext ) ;
479
+ return this . #translate( url , format , source , isMain ) ;
480
+ }
481
+
482
+ /**
483
+ * Load a module and translate it into a ModuleWrap, and create a ModuleJob from it.
484
+ * This runs synchronously. If isForRequireInImportedCJS is true, the module should be linked
485
+ * by the time this returns. Otherwise it may still have pending module requests.
486
+ * @param {string } url The URL that was resolved for this module.
487
+ * @param {ImportAttributes } importAttributes See {@link getModuleJobForImport}
488
+ * @param {string } [parentURL] See {@link getModuleJobForImport}
489
+ * @param {string } [format] The format hint possibly returned by the `resolve` hook
490
+ * @param {boolean } isForRequireInImportedCJS Whether this module job is created for require()
491
+ * in imported CJS.
492
+ * @returns {ModuleJobBase } The (possibly pending) module job
493
+ */
494
+ #createModuleJob( url , importAttributes , parentURL , format , isForRequireInImportedCJS ) {
495
+ const context = { format, importAttributes } ;
444
496
445
497
const isMain = parentURL === undefined ;
498
+ let moduleOrModulePromise ;
499
+ if ( isForRequireInImportedCJS ) {
500
+ moduleOrModulePromise = this . loadAndTranslateForRequireInImportedCJS ( url , context , isMain ) ;
501
+ } else {
502
+ moduleOrModulePromise = this . loadAndTranslate ( url , context , isMain ) ;
503
+ }
504
+
446
505
const inspectBrk = (
447
506
isMain &&
448
507
getOptionValue ( '--inspect-brk' )
@@ -457,10 +516,10 @@ class ModuleLoader {
457
516
this ,
458
517
url ,
459
518
importAttributes ,
460
- moduleProvider ,
519
+ moduleOrModulePromise ,
461
520
isMain ,
462
521
inspectBrk ,
463
- sync ,
522
+ isForRequireInImportedCJS ,
464
523
) ;
465
524
466
525
this . loadCache . set ( url , importAttributes . type , job ) ;
@@ -479,7 +538,7 @@ class ModuleLoader {
479
538
*/
480
539
async import ( specifier , parentURL , importAttributes , isEntryPoint = false ) {
481
540
return onImport . tracePromise ( async ( ) => {
482
- const moduleJob = await this . getModuleJob ( specifier , parentURL , importAttributes ) ;
541
+ const moduleJob = await this . getModuleJobForImport ( specifier , parentURL , importAttributes ) ;
483
542
const { module } = await moduleJob . run ( isEntryPoint ) ;
484
543
return module . getNamespace ( ) ;
485
544
} , {
@@ -504,39 +563,72 @@ class ModuleLoader {
504
563
}
505
564
506
565
/**
507
- * Resolve the location of the module.
508
- * @param {string } originalSpecifier The specified URL path of the module to
509
- * be resolved.
510
- * @param {string } [parentURL] The URL path of the module's parent.
511
- * @param {ImportAttributes } importAttributes Attributes from the import
512
- * statement or expression.
513
- * @returns {{ format: string, url: URL['href'] } }
566
+ * Resolve a module request to a URL identifying the location of the module. Handles customization hooks,
567
+ * if any.
568
+ * @param {string|URL } specifier The module request of the module to be resolved. Typically, what's
569
+ * requested by `import specifier`, `import(specifier)` or
570
+ * `import.meta.resolve(specifier)`.
571
+ * @param {string } [parentURL] The URL of the module where the module request is initiated.
572
+ * It's undefined if it's from the root module.
573
+ * @param {ImportAttributes } importAttributes Attributes from the import statement or expression.
574
+ * @returns {Promise<{format: string, url: string}> }
514
575
*/
515
- resolve ( originalSpecifier , parentURL , importAttributes ) {
516
- originalSpecifier = `${ originalSpecifier } ` ;
517
- if ( this . #customizations) {
518
- return this . #customizations. resolve ( originalSpecifier , parentURL , importAttributes ) ;
576
+ resolve ( specifier , parentURL , importAttributes ) {
577
+ specifier = `${ specifier } ` ;
578
+ if ( this . #customizations) { // Only has module.register hooks.
579
+ return this . #customizations. resolve ( specifier , parentURL , importAttributes ) ;
519
580
}
520
- const requestKey = this . #resolveCache. serializeKey ( originalSpecifier , importAttributes ) ;
581
+ return this . #cachedDefaultResolve( specifier , parentURL , importAttributes ) ;
582
+ }
583
+
584
+ /**
585
+ * Either return a cached resolution, or perform the default resolution which is synchronous, and
586
+ * cache the result.
587
+ * @param {string } specifier See {@link resolve}.
588
+ * @param {string } [parentURL] See {@link resolve}.
589
+ * @param {ImportAttributes } importAttributes See {@link resolve}.
590
+ * @returns {{ format: string, url: string } }
591
+ */
592
+ #cachedDefaultResolve( specifier , parentURL , importAttributes ) {
593
+ const requestKey = this . #resolveCache. serializeKey ( specifier , importAttributes ) ;
521
594
const cachedResult = this . #resolveCache. get ( requestKey , parentURL ) ;
522
595
if ( cachedResult != null ) {
523
596
return cachedResult ;
524
597
}
525
- const result = this . defaultResolve ( originalSpecifier , parentURL , importAttributes ) ;
598
+ const result = this . defaultResolve ( specifier , parentURL , importAttributes ) ;
526
599
this . #resolveCache. set ( requestKey , parentURL , result ) ;
527
600
return result ;
528
601
}
529
602
530
603
/**
531
- * Just like `resolve` except synchronous. This is here specifically to support
532
- * `import.meta.resolve` which must happen synchronously.
604
+ * This is the default resolve step for future synchronous hooks, which incorporates asynchronous hooks
605
+ * from module.register() which are run in a blocking fashion for it to be synchronous.
606
+ * @param {string|URL } specifier See {@link resolveSync}.
607
+ * @param {{ parentURL?: string, importAttributes: ImportAttributes} } context See {@link resolveSync}.
608
+ * @returns {{ format: string, url: string } }
533
609
*/
534
- resolveSync ( originalSpecifier , parentURL , importAttributes ) {
535
- originalSpecifier = `${ originalSpecifier } ` ;
610
+ #resolveAndMaybeBlockOnLoaderThread( specifier , context ) {
536
611
if ( this . #customizations) {
537
- return this . #customizations. resolveSync ( originalSpecifier , parentURL , importAttributes ) ;
612
+ return this . #customizations. resolveSync ( specifier , context . parentURL , context . importAttributes ) ;
538
613
}
539
- return this . defaultResolve ( originalSpecifier , parentURL , importAttributes ) ;
614
+ return this . #cachedDefaultResolve( specifier , context . parentURL , context . importAttributes ) ;
615
+ }
616
+
617
+ /**
618
+ * Similar to {@link resolve}, but the results are always synchronously returned. If there are any
619
+ * asynchronous resolve hooks from module.register(), it will block until the results are returned
620
+ * from the loader thread for this to be synchornous.
621
+ * This is here to support `import.meta.resolve()`, `require()` in imported CJS, and
622
+ * future synchronous hooks.
623
+ *
624
+ * TODO(joyeecheung): consolidate the cache behavior and use this in require(esm).
625
+ * @param {string|URL } specifier See {@link resolve}.
626
+ * @param {string } [parentURL] See {@link resolve}.
627
+ * @param {ImportAttributes } [importAttributes] See {@link resolve}.
628
+ * @returns {{ format: string, url: string } }
629
+ */
630
+ resolveSync ( specifier , parentURL , importAttributes = { __proto__ : null } ) {
631
+ return this . #resolveAndMaybeBlockOnLoaderThread( `${ specifier } ` , { parentURL, importAttributes } ) ;
540
632
}
541
633
542
634
/**
@@ -558,41 +650,49 @@ class ModuleLoader {
558
650
}
559
651
560
652
/**
561
- * Provide source that is understood by one of Node's translators.
562
- * @param {URL['href'] } url The URL/path of the module to be loaded
563
- * @param {object } [context] Metadata about the module
653
+ * Provide source that is understood by one of Node's translators. Handles customization hooks,
654
+ * if any.
655
+ * @param {string } url The URL of the module to be loaded.
656
+ * @param {object } context Metadata about the module
564
657
* @returns {Promise<{ format: ModuleFormat, source: ModuleSource }> }
565
658
*/
566
659
async load ( url , context ) {
660
+ if ( this . #customizations) {
661
+ return this . #customizations. load ( url , context ) ;
662
+ }
663
+
567
664
defaultLoad ??= require ( 'internal/modules/esm/load' ) . defaultLoad ;
568
- const result = this . #customizations ?
569
- await this . #customizations. load ( url , context ) :
570
- await defaultLoad ( url , context ) ;
571
- this . validateLoadResult ( url , result ?. format ) ;
572
- return result ;
665
+ return defaultLoad ( url , context ) ;
573
666
}
574
667
575
- loadSync ( url , context ) {
576
- defaultLoadSync ??= require ( 'internal/modules/esm/load' ) . defaultLoadSync ;
577
-
578
- let result = this . #customizations ?
579
- this . #customizations. loadSync ( url , context ) :
580
- defaultLoadSync ( url , context ) ;
581
- let format = result ?. format ;
582
- if ( format === 'module' || format === 'module-typescript' ) {
583
- throw new ERR_REQUIRE_ESM ( url , true ) ;
584
- }
585
- if ( format === 'commonjs' ) {
586
- format = 'require-commonjs' ;
587
- result = { __proto__ : result , format } ;
588
- }
589
- if ( format === 'commonjs-typescript' ) {
590
- format = 'require-commonjs-typescript' ;
591
- result = { __proto__ : result , format } ;
668
+ /**
669
+ * This is the default load step for future synchronous hooks, which incorporates asynchronous hooks
670
+ * from module.register() which are run in a blocking fashion for it to be synchronous.
671
+ * @param {string } url See {@link load}
672
+ * @param {object } context See {@link load}
673
+ * @returns {{ format: ModuleFormat, source: ModuleSource } }
674
+ */
675
+ #loadAndMaybeBlockOnLoaderThread( url , context ) {
676
+ if ( this . #customizations) {
677
+ return this . #customizations. loadSync ( url , context ) ;
592
678
}
679
+ defaultLoadSync ??= require ( 'internal/modules/esm/load' ) . defaultLoadSync ;
680
+ return defaultLoadSync ( url , context ) ;
681
+ }
593
682
594
- this . validateLoadResult ( url , format ) ;
595
- return result ;
683
+ /**
684
+ * Similar to {@link load} but this is always run synchronously. If there are asynchronous hooks
685
+ * from module.register(), this blocks on the loader thread for it to return synchronously.
686
+ *
687
+ * This is here to support `require()` in imported CJS and future synchronous hooks.
688
+ *
689
+ * TODO(joyeecheung): consolidate the cache behavior and use this in require(esm).
690
+ * @param {string } url See {@link load}
691
+ * @param {object } [context] See {@link load}
692
+ * @returns {{ format: ModuleFormat, source: ModuleSource } }
693
+ */
694
+ #loadSync( url , context ) {
695
+ return this . #loadAndMaybeBlockOnLoaderThread( url , context ) ;
596
696
}
597
697
598
698
validateLoadResult ( url , format ) {
0 commit comments