@@ -54,7 +54,6 @@ const {
54
54
} = require ( 'internal/util' ) ;
55
55
56
56
const {
57
- defaultResolve,
58
57
throwIfInvalidParentURL,
59
58
} = require ( 'internal/modules/esm/resolve' ) ;
60
59
const {
@@ -87,51 +86,26 @@ let importMetaInitializer;
87
86
// [2] `validate...()`s throw the wrong error
88
87
89
88
class Hooks {
90
- #chains = {
91
- /**
92
- * Prior to ESM loading. These are called once before any modules are started.
93
- * @private
94
- * @property {KeyedHook[] } globalPreload Last-in-first-out list of preload hooks.
95
- */
96
- globalPreload : [ ] ,
97
-
98
- /**
99
- * Phase 1 of 2 in ESM loading.
100
- * The output of the `resolve` chain of hooks is passed into the `load` chain of hooks.
101
- * @private
102
- * @property {KeyedHook[] } resolve Last-in-first-out collection of resolve hooks.
103
- */
104
- resolve : [
105
- {
106
- fn : defaultResolve ,
107
- url : 'node:internal/modules/esm/resolve' ,
108
- } ,
109
- ] ,
110
-
111
- /**
112
- * Phase 2 of 2 in ESM loading.
113
- * @private
114
- * @property {KeyedHook[] } load Last-in-first-out collection of loader hooks.
115
- */
116
- load : [
117
- {
118
- fn : require ( 'internal/modules/esm/load' ) . defaultLoad ,
119
- url : 'node:internal/modules/esm/load' ,
120
- } ,
121
- ] ,
122
- } ;
89
+ #loaderInstances = [ ] ;
90
+ #lastInstanceId = 0 ;
123
91
124
92
// Cache URLs we've already validated to avoid repeated validation
125
93
#validatedUrls = new SafeSet ( ) ;
126
94
127
95
allowImportMetaResolve = false ;
128
96
97
+ constructor ( ) {
98
+ const defaultLoader = 'internal/modules/esm/default_loader' ;
99
+ this . addCustomLoader ( `node:${ defaultLoader } ` , require ( defaultLoader ) ) ;
100
+ }
101
+
129
102
/**
130
103
* Import and register custom/user-defined module loader hook(s).
131
104
* @param {string } urlOrSpecifier
132
105
* @param {string } parentURL
133
106
* @param {any } [data] Arbitrary data to be passed from the custom
134
107
* loader (user-land) to the worker.
108
+ * @returns {Promise<number> } The id of the registered loader.
135
109
*/
136
110
async register ( urlOrSpecifier , parentURL , data ) {
137
111
const moduleLoader = require ( 'internal/process/esm_loader' ) . esmLoader ;
@@ -143,46 +117,71 @@ class Hooks {
143
117
return this . addCustomLoader ( urlOrSpecifier , keyedExports , data ) ;
144
118
}
145
119
120
+ deregister ( id ) {
121
+ return this . removeCustomLoader ( id ) ;
122
+ }
123
+
146
124
/**
147
125
* Collect custom/user-defined module loader hook(s).
148
126
* After all hooks have been collected, the global preload hook(s) must be initialized.
149
127
* @param {string } url Custom loader specifier
150
128
* @param {Record<string, unknown> } exports
151
129
* @param {any } [data] Arbitrary data to be passed from the custom loader (user-land)
152
130
* to the worker.
153
- * @returns {any } The result of the loader's `initialize` hook, if provided .
131
+ * @returns {Promise<number> } The id of the registered loader .
154
132
*/
155
- addCustomLoader ( url , exports , data ) {
133
+ async addCustomLoader ( url , exports , data ) {
156
134
const {
157
135
globalPreload,
158
136
initialize,
159
137
resolve,
160
138
load,
161
139
} = pluckHooks ( exports ) ;
162
-
163
140
if ( globalPreload && ! initialize ) {
164
141
emitExperimentalWarning (
165
142
'`globalPreload` is planned for removal in favor of `initialize`. `globalPreload`' ,
166
143
) ;
167
- ArrayPrototypePush ( this . #chains. globalPreload , { __proto__ : null , fn : globalPreload , url } ) ;
168
- }
169
- if ( resolve ) {
170
- const next = this . #chains. resolve [ this . #chains. resolve . length - 1 ] ;
171
- ArrayPrototypePush ( this . #chains. resolve , { __proto__ : null , fn : resolve , url, next } ) ;
172
144
}
173
- if ( load ) {
174
- const next = this . #chains. load [ this . #chains. load . length - 1 ] ;
175
- ArrayPrototypePush ( this . #chains. load , { __proto__ : null , fn : load , url, next } ) ;
145
+ const next = this . #loaderInstances[ this . #loaderInstances. length - 1 ] ;
146
+ const instance = {
147
+ __proto__ : null ,
148
+ id : ++ this . #lastInstanceId,
149
+ url,
150
+ globalPreload,
151
+ initialize,
152
+ resolve,
153
+ load,
154
+ next,
155
+ } ;
156
+ ArrayPrototypePush ( this . #loaderInstances, instance ) ;
157
+ instance . state = await initialize ?. ( data , { __proto__ : null , id : instance . id , url } ) ;
158
+ return instance . id ;
159
+ }
160
+
161
+ removeCustomLoader ( id ) {
162
+ // This loop purposefully has `> 0` in order to prevent people from
163
+ // removing the first loader (i.e. the default one).
164
+ for ( let i = this . #loaderInstances. length - 1 ; i > 0 ; -- i ) {
165
+ if ( id === this . #loaderInstances[ i ] . id ) {
166
+ if ( i + 1 < this . #loaderInstances. length ) {
167
+ this . #loaderInstances[ i + 1 ] . next = this . #loaderInstances[ i - 1 ] ;
168
+ }
169
+ this . #loaderInstances. splice ( i , 1 ) ;
170
+ return true ;
171
+ }
176
172
}
177
- return initialize ?. ( data ) ;
173
+ return false ;
178
174
}
179
175
180
176
/**
181
177
* Initialize `globalPreload` hooks.
182
178
*/
183
179
initializeGlobalPreload ( ) {
184
180
const preloadScripts = [ ] ;
185
- for ( let i = this . #chains. globalPreload . length - 1 ; i >= 0 ; i -- ) {
181
+ for ( const loader of this . #loaderInstances) {
182
+ if ( ! loader . globalPreload ) {
183
+ continue ;
184
+ }
186
185
const { MessageChannel } = require ( 'internal/worker/io' ) ;
187
186
const channel = new MessageChannel ( ) ;
188
187
const {
@@ -193,10 +192,7 @@ class Hooks {
193
192
insidePreload . unref ( ) ;
194
193
insideLoader . unref ( ) ;
195
194
196
- const {
197
- fn : preload ,
198
- url : specifier ,
199
- } = this . #chains. globalPreload [ i ] ;
195
+ const preload = loader . globalPreload ;
200
196
201
197
const preloaded = preload ( {
202
198
port : insideLoader ,
@@ -207,8 +203,8 @@ class Hooks {
207
203
if ( typeof preloaded !== 'string' ) { // [2]
208
204
throw new ERR_INVALID_RETURN_VALUE (
209
205
'a string' ,
210
- `${ specifier } globalPreload` ,
211
- preload ,
206
+ `${ loader . url } globalPreload` ,
207
+ loader . globalPreload ,
212
208
) ;
213
209
}
214
210
@@ -240,7 +236,6 @@ class Hooks {
240
236
) {
241
237
throwIfInvalidParentURL ( parentURL ) ;
242
238
243
- const chain = this . #chains. resolve ;
244
239
const context = {
245
240
conditions : getDefaultConditions ( ) ,
246
241
importAssertions,
@@ -272,7 +267,11 @@ class Hooks {
272
267
}
273
268
} ;
274
269
275
- const nextResolve = nextHookFactory ( chain [ chain . length - 1 ] , meta , { validateArgs, validateOutput } ) ;
270
+ const nextResolve = nextHookFactory (
271
+ this . #loaderInstances[ this . #loaderInstances. length - 1 ] ,
272
+ meta ,
273
+ { validateArgs, validateOutput } ,
274
+ ) ;
276
275
277
276
const resolution = await nextResolve ( originalSpecifier , context ) ;
278
277
const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
@@ -364,7 +363,6 @@ class Hooks {
364
363
* @returns {Promise<{ format: ModuleFormat, source: ModuleSource }> }
365
364
*/
366
365
async load ( url , context = { } ) {
367
- const chain = this . #chains. load ;
368
366
const meta = {
369
367
chainFinished : null ,
370
368
context,
@@ -410,7 +408,11 @@ class Hooks {
410
408
}
411
409
} ;
412
410
413
- const nextLoad = nextHookFactory ( chain [ chain . length - 1 ] , meta , { validateArgs, validateOutput } ) ;
411
+ const nextLoad = nextHookFactory (
412
+ this . #loaderInstances[ this . #loaderInstances. length - 1 ] ,
413
+ meta ,
414
+ { validateArgs, validateOutput } ,
415
+ ) ;
414
416
415
417
const loaded = await nextLoad ( url , context ) ;
416
418
const { hookErrIdentifier } = meta ; // Retrieve the value after all settled
@@ -789,11 +791,13 @@ function pluckHooks({
789
791
function nextHookFactory ( current , meta , { validateArgs, validateOutput } ) {
790
792
// First, prepare the current
791
793
const { hookName } = meta ;
792
- const {
793
- fn : hook ,
794
- url : hookFilePath ,
795
- next,
796
- } = current ;
794
+
795
+ const { next, state, url : hookFilePath } = current ;
796
+ const hook = current [ hookName ] ;
797
+
798
+ if ( ! hook ) {
799
+ return nextHookFactory ( next , meta , { validateArgs, validateOutput } ) ;
800
+ }
797
801
798
802
// ex 'nextResolve'
799
803
const nextHookName = `next${
@@ -828,8 +832,9 @@ function nextHookFactory(current, meta, { validateArgs, validateOutput }) {
828
832
if ( context ) { // `context` has already been validated, so no fancy check needed.
829
833
ObjectAssign ( meta . context , context ) ;
830
834
}
831
-
832
- const output = await hook ( arg0 , meta . context , nextNextHook ) ;
835
+ const output = hook . length === 4 ?
836
+ await hook ( arg0 , meta . context , state , nextNextHook ) :
837
+ await hook ( arg0 , meta . context , nextNextHook ) ;
833
838
validateOutput ( outputErrIdentifier , output ) ;
834
839
835
840
if ( output ?. shortCircuit === true ) { meta . shortCircuited = true ; }
0 commit comments