@@ -12,12 +12,16 @@ const {
12
12
FunctionPrototypeCall,
13
13
ObjectAssign,
14
14
ObjectCreate,
15
+ ObjectDefineProperty,
15
16
ObjectSetPrototypeOf,
16
17
PromiseAll,
18
+ RangeError,
17
19
RegExpPrototypeExec,
18
20
SafeArrayIterator,
19
21
SafeWeakMap,
22
+ StringPrototypeSlice,
20
23
StringPrototypeStartsWith,
24
+ StringPrototypeToUpperCase,
21
25
globalThis,
22
26
} = primordials ;
23
27
const { MessageChannel } = require ( 'internal/worker/io' ) ;
@@ -96,6 +100,59 @@ const {
96
100
97
101
let emittedSpecifierResolutionWarning = false ;
98
102
103
+ function nextHookFactory ( chain , meta , validate ) {
104
+ // First, prepare the current
105
+ const { hookName } = meta ;
106
+ const {
107
+ fn : hook ,
108
+ url : hookFilePath ,
109
+ } = chain [ meta . hookIndex ] ;
110
+
111
+ const nextHookName = `next${
112
+ StringPrototypeToUpperCase ( hookName [ 0 ] ) +
113
+ StringPrototypeSlice ( hookName , 1 )
114
+ } `;
115
+
116
+ // When hookIndex is 0, it's reached the default, which does not call next()
117
+ // so feed it a noop that blows up if called, so the problem is obvious.
118
+ const generatedHookIndex = meta . hookIndex ;
119
+ let nextNextHook ;
120
+ if ( meta . hookIndex > 0 ) {
121
+ // Now, prepare the next: decrement the pointer so the next call to the
122
+ // factory generates the next link in the chain.
123
+ meta . hookIndex -- ;
124
+
125
+ nextNextHook = nextHookFactory ( chain , meta , validate ) ;
126
+ } else {
127
+ // eslint-disable-next-line func-name-matching
128
+ nextNextHook = function chainAdvancedTooFar ( ) {
129
+ throw new RangeError (
130
+ `ESM custom loader '${ hookName } ' advanced beyond the end of the chain; this is a bug in Node.js itself.`
131
+ ) ;
132
+ } ;
133
+ }
134
+
135
+ return ObjectDefineProperty (
136
+ async ( ...args ) => {
137
+ // Update only when hook is invoked to avoid fingering the wrong filePath
138
+ meta . hookErrIdentifier = `${ hookFilePath } '${ hookName } '` ;
139
+
140
+ validate ( meta . hookErrIdentifier , ...args ) ;
141
+
142
+ // Set when next<HookName> is actually called, not just generated.
143
+ if ( generatedHookIndex === 0 ) { meta . chainFinished = true ; }
144
+
145
+ const output = await hook ( ...args , nextNextHook ) ;
146
+
147
+ if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
148
+
149
+ return output ;
150
+ } ,
151
+ 'name' ,
152
+ { value : nextHookName } ,
153
+ ) ;
154
+ }
155
+
99
156
/**
100
157
* An ESMLoader instance is used as the main entry point for loading ES modules.
101
158
* Currently, this is a singleton -- there is only one used for loading
@@ -528,27 +585,17 @@ class ESMLoader {
528
585
* @returns {{ format: ModuleFormat, source: ModuleSource } }
529
586
*/
530
587
async load ( url , context = { } ) {
531
- const loaders = this . #loaders;
532
- let hookIndex = loaders . length - 1 ;
533
- let {
534
- fn : loader ,
535
- url : loaderFilePath ,
536
- } = loaders [ hookIndex ] ;
537
- let chainFinished = hookIndex === 0 ;
538
- let shortCircuited = false ;
539
-
540
- const nextLoad = async ( nextUrl , ctx = context ) => {
541
- -- hookIndex ; // `nextLoad` has been called, so decrement our pointer.
542
-
543
- ( {
544
- fn : loader ,
545
- url : loaderFilePath ,
546
- } = loaders [ hookIndex ] ) ;
547
-
548
- if ( hookIndex === 0 ) { chainFinished = true ; }
549
-
550
- const hookErrIdentifier = `${ loaderFilePath } "load"` ;
588
+ const chain = this . #loaders;
589
+ const hookIndex = chain . length - 1 ;
590
+ const meta = {
591
+ chainFinished : hookIndex === 0 ,
592
+ hookErrIdentifier : '' ,
593
+ hookIndex,
594
+ hookName : 'load' ,
595
+ shortCircuited : false ,
596
+ } ;
551
597
598
+ const validate = ( hookErrIdentifier , nextUrl , ctx ) => {
552
599
if ( typeof nextUrl !== 'string' ) {
553
600
// non-strings can be coerced to a url string
554
601
// validateString() throws a less-specific error
@@ -565,29 +612,20 @@ class ESMLoader {
565
612
new URL ( nextUrl ) ;
566
613
} catch {
567
614
throw new ERR_INVALID_ARG_VALUE (
568
- `${ hookErrIdentifier } nextLoad(url)` ,
615
+ `${ hookErrIdentifier } s nextLoad(url)` ,
569
616
nextUrl ,
570
617
'should be a url string' ,
571
618
) ;
572
619
}
573
620
}
574
621
575
- validateObject ( ctx , `${ hookErrIdentifier } nextLoad(, context)` ) ;
576
-
577
- const output = await loader ( nextUrl , ctx , nextLoad ) ;
578
-
579
- if ( output ?. shortCircuit === true ) { shortCircuited = true ; }
580
-
581
- return output ;
622
+ validateObject ( ctx , `${ hookErrIdentifier } s nextLoad(, context)` ) ;
582
623
} ;
583
624
584
- const loaded = await loader (
585
- url ,
586
- context ,
587
- nextLoad ,
588
- ) ;
625
+ const nextLoad = nextHookFactory ( chain , meta , validate ) ;
589
626
590
- const hookErrIdentifier = `${ loaderFilePath } load` ;
627
+ const loaded = await nextLoad ( url , context ) ;
628
+ const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
591
629
592
630
if ( typeof loaded !== 'object' ) { // [2]
593
631
throw new ERR_INVALID_RETURN_VALUE (
@@ -597,10 +635,10 @@ class ESMLoader {
597
635
) ;
598
636
}
599
637
600
- if ( loaded ?. shortCircuit === true ) { shortCircuited = true ; }
638
+ if ( loaded ?. shortCircuit === true ) { meta . shortCircuited = true ; }
601
639
602
- if ( ! chainFinished && ! shortCircuited ) {
603
- throw new ERR_LOADER_CHAIN_INCOMPLETE ( 'load' , loaderFilePath ) ;
640
+ if ( ! meta . chainFinished && ! meta . shortCircuited ) {
641
+ throw new ERR_LOADER_CHAIN_INCOMPLETE ( hookErrIdentifier ) ;
604
642
}
605
643
606
644
const {
@@ -769,55 +807,35 @@ class ESMLoader {
769
807
parentURL ,
770
808
) ;
771
809
}
772
- const resolvers = this . #resolvers;
773
-
774
- let hookIndex = resolvers . length - 1 ;
775
- let {
776
- fn : resolver ,
777
- url : resolverFilePath ,
778
- } = resolvers [ hookIndex ] ;
779
- let chainFinished = hookIndex === 0 ;
780
- let shortCircuited = false ;
810
+ const chain = this . #resolvers;
811
+ const hookIndex = chain . length - 1 ;
812
+ const meta = {
813
+ chainFinished : hookIndex === 0 ,
814
+ hookErrIdentifier : '' ,
815
+ hookIndex ,
816
+ hookName : 'resolve' ,
817
+ shortCircuited : false ,
818
+ } ;
781
819
782
820
const context = {
783
821
conditions : DEFAULT_CONDITIONS ,
784
822
importAssertions,
785
823
parentURL,
786
824
} ;
787
825
788
- const nextResolve = async ( suppliedSpecifier , ctx = context ) => {
789
- -- hookIndex ; // `nextResolve` has been called, so decrement our pointer.
790
-
791
- ( {
792
- fn : resolver ,
793
- url : resolverFilePath ,
794
- } = resolvers [ hookIndex ] ) ;
795
-
796
- if ( hookIndex === 0 ) { chainFinished = true ; }
797
-
798
- const hookErrIdentifier = `${ resolverFilePath } "resolve"` ;
799
-
826
+ const validate = ( hookErrIdentifier , suppliedSpecifier , ctx ) => {
800
827
validateString (
801
828
suppliedSpecifier ,
802
- `${ hookErrIdentifier } nextResolve(specifier)` ,
829
+ `${ hookErrIdentifier } s nextResolve(specifier)` ,
803
830
) ; // non-strings can be coerced to a url string
804
831
805
- validateObject ( ctx , `${ hookErrIdentifier } nextResolve(, context)` ) ;
806
-
807
- const output = await resolver ( suppliedSpecifier , ctx , nextResolve ) ;
808
-
809
- if ( output ?. shortCircuit === true ) { shortCircuited = true ; }
810
-
811
- return output ;
832
+ validateObject ( ctx , `${ hookErrIdentifier } s nextResolve(, context)` ) ;
812
833
} ;
813
834
814
- const resolution = await resolver (
815
- originalSpecifier ,
816
- context ,
817
- nextResolve ,
818
- ) ;
835
+ const nextResolve = nextHookFactory ( chain , meta , validate ) ;
819
836
820
- const hookErrIdentifier = `${ resolverFilePath } resolve` ;
837
+ const resolution = await nextResolve ( originalSpecifier , context ) ;
838
+ const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
821
839
822
840
if ( typeof resolution !== 'object' ) { // [2]
823
841
throw new ERR_INVALID_RETURN_VALUE (
@@ -827,10 +845,10 @@ class ESMLoader {
827
845
) ;
828
846
}
829
847
830
- if ( resolution ?. shortCircuit === true ) { shortCircuited = true ; }
848
+ if ( resolution ?. shortCircuit === true ) { meta . shortCircuited = true ; }
831
849
832
- if ( ! chainFinished && ! shortCircuited ) {
833
- throw new ERR_LOADER_CHAIN_INCOMPLETE ( 'resolve' , resolverFilePath ) ;
850
+ if ( ! meta . chainFinished && ! meta . shortCircuited ) {
851
+ throw new ERR_LOADER_CHAIN_INCOMPLETE ( hookErrIdentifier ) ;
834
852
}
835
853
836
854
const {
0 commit comments