@@ -140,168 +140,182 @@ let internalBinding;
140140 } ;
141141}
142142
143- // Think of this as module.exports in this file even though it is not
144- // written in CommonJS style.
145- const loaderExports = {
146- internalBinding,
147- NativeModule,
148- require : nativeModuleRequire
149- } ;
150-
151143const loaderId = 'internal/bootstrap/loaders' ;
152-
153- // Set up NativeModule.
154- function NativeModule ( id ) {
155- this . filename = `${ id } .js` ;
156- this . id = id ;
157- this . exports = { } ;
158- this . module = undefined ;
159- this . exportKeys = undefined ;
160- this . loaded = false ;
161- this . loading = false ;
162- this . canBeRequiredByUsers = ! id . startsWith ( 'internal/' ) ;
163- }
164-
165- // To be called during pre-execution when --expose-internals is on.
166- // Enables the user-land module loader to access internal modules.
167- NativeModule . exposeInternals = function ( ) {
168- for ( const [ id , mod ] of NativeModule . map ) {
169- // Do not expose this to user land even with --expose-internals.
170- if ( id !== loaderId ) {
171- mod . canBeRequiredByUsers = true ;
172- }
173- }
174- } ;
175-
176144const {
177145 moduleIds,
178146 compileFunction
179147} = internalBinding ( 'native_module' ) ;
180148
181- NativeModule . map = new Map ( ) ;
182- for ( let i = 0 ; i < moduleIds . length ; ++ i ) {
183- const id = moduleIds [ i ] ;
184- const mod = new NativeModule ( id ) ;
185- NativeModule . map . set ( id , mod ) ;
186- }
149+ const getOwn = ( target , property , receiver ) => {
150+ return ObjectPrototypeHasOwnProperty ( target , property ) ?
151+ ReflectGet ( target , property , receiver ) :
152+ undefined ;
153+ } ;
187154
188- function nativeModuleRequire ( id ) {
189- if ( id === loaderId ) {
190- return loaderExports ;
155+ /**
156+ * An internal abstraction for the built-in JavaScript modules of Node.js.
157+ * Be careful not to expose this to user land unless --expose-internals is
158+ * used, in which case there is no compatibility guarantee about this class.
159+ */
160+ class NativeModule {
161+ /**
162+ * A map from the module IDs to the module instances.
163+ * @type {Map<string, NativeModule> }
164+ */
165+ static map = new Map ( moduleIds . map ( ( id ) => [ id , new NativeModule ( id ) ] ) ) ;
166+
167+ constructor ( id ) {
168+ this . filename = `${ id } .js` ;
169+ this . id = id ;
170+ this . canBeRequiredByUsers = ! id . startsWith ( 'internal/' ) ;
171+
172+ // The CJS exports object of the module.
173+ this . exports = { } ;
174+ // States used to work around circular dependencies.
175+ this . loaded = false ;
176+ this . loading = false ;
177+
178+ // The following properties are used by the ESM implementation and only
179+ // initialized when the native module is loaded by users.
180+ /**
181+ * The C++ ModuleWrap binding used to interface with the ESM implementation.
182+ * @type {ModuleWrap|undefined }
183+ */
184+ this . module = undefined ;
185+ /**
186+ * Exported names for the ESM imports.
187+ * @type {string[]|undefined }
188+ */
189+ this . exportKeys = undefined ;
191190 }
192191
193- const mod = NativeModule . map . get ( id ) ;
194- // Can't load the internal errors module from here, have to use a raw error.
195- // eslint-disable-next-line no-restricted-syntax
196- if ( ! mod ) throw new TypeError ( `Missing internal module '${ id } '` ) ;
197- return mod . compile ( ) ;
198- }
192+ // To be called during pre-execution when --expose-internals is on.
193+ // Enables the user-land module loader to access internal modules.
194+ static exposeInternals ( ) {
195+ for ( const [ id , mod ] of NativeModule . map ) {
196+ // Do not expose this to user land even with --expose-internals.
197+ if ( id !== loaderId ) {
198+ mod . canBeRequiredByUsers = true ;
199+ }
200+ }
201+ }
199202
200- NativeModule . exists = function ( id ) {
201- return NativeModule . map . has ( id ) ;
202- } ;
203+ static exists ( id ) {
204+ return NativeModule . map . has ( id ) ;
205+ }
203206
204- NativeModule . canBeRequiredByUsers = function ( id ) {
205- const mod = NativeModule . map . get ( id ) ;
206- return mod && mod . canBeRequiredByUsers ;
207- } ;
207+ static canBeRequiredByUsers ( id ) {
208+ const mod = NativeModule . map . get ( id ) ;
209+ return mod && mod . canBeRequiredByUsers ;
210+ }
208211
209- // Allow internal modules from dependencies to require
210- // other modules from dependencies by providing fallbacks.
211- function requireWithFallbackInDeps ( request ) {
212- if ( ! NativeModule . map . has ( request ) ) {
213- request = `internal/deps/${ request } ` ;
212+ // Used by user-land module loaders to compile and load builtins.
213+ compileForPublicLoader ( needToSyncExports ) {
214+ if ( ! this . canBeRequiredByUsers ) {
215+ // No code because this is an assertion against bugs
216+ // eslint-disable-next-line no-restricted-syntax
217+ throw new Error ( `Should not compile ${ this . id } for public use` ) ;
218+ }
219+ this . compileForInternalLoader ( ) ;
220+ if ( needToSyncExports ) {
221+ if ( ! this . exportKeys ) {
222+ // When using --expose-internals, we do not want to reflect the named
223+ // exports from core modules as this can trigger unnecessary getters.
224+ const internal = this . id . startsWith ( 'internal/' ) ;
225+ this . exportKeys = internal ? [ ] : ObjectKeys ( this . exports ) ;
226+ }
227+ this . getESMFacade ( ) ;
228+ this . syncExports ( ) ;
229+ }
230+ return this . exports ;
214231 }
215- return nativeModuleRequire ( request ) ;
216- }
217232
218- // This is exposed for public loaders
219- NativeModule . prototype . compileForPublicLoader = function ( needToSyncExports ) {
220- if ( ! this . canBeRequiredByUsers ) {
221- // No code because this is an assertion against bugs
222- // eslint-disable-next-line no-restricted-syntax
223- throw new Error ( `Should not compile ${ this . id } for public use` ) ;
233+ getESMFacade ( ) {
234+ if ( this . module ) return this . module ;
235+ const { ModuleWrap } = internalBinding ( 'module_wrap' ) ;
236+ const url = `node:${ this . id } ` ;
237+ const nativeModule = this ;
238+ this . module = new ModuleWrap (
239+ url , undefined , [ ...this . exportKeys , 'default' ] ,
240+ function ( ) {
241+ nativeModule . syncExports ( ) ;
242+ this . setExport ( 'default' , nativeModule . exports ) ;
243+ } ) ;
244+ // Ensure immediate sync execution to capture exports now
245+ this . module . instantiate ( ) ;
246+ this . module . evaluate ( - 1 , false ) ;
247+ return this . module ;
224248 }
225- this . compile ( ) ;
226- if ( needToSyncExports ) {
227- if ( ! this . exportKeys ) {
228- // When using --expose-internals, we do not want to reflect the named
229- // exports from core modules as this can trigger unnecessary getters.
230- const internal = this . id . startsWith ( 'internal/' ) ;
231- this . exportKeys = internal ? [ ] : ObjectKeys ( this . exports ) ;
249+
250+ // Provide named exports for all builtin libraries so that the libraries
251+ // may be imported in a nicer way for ESM users. The default export is left
252+ // as the entire namespace (module.exports) and updates when this function is
253+ // called so that APMs and other behavior are supported.
254+ syncExports ( ) {
255+ const names = this . exportKeys ;
256+ if ( this . module ) {
257+ for ( let i = 0 ; i < names . length ; i ++ ) {
258+ const exportName = names [ i ] ;
259+ if ( exportName === 'default' ) continue ;
260+ this . module . setExport ( exportName ,
261+ getOwn ( this . exports , exportName , this . exports ) ) ;
262+ }
232263 }
233- this . getESMFacade ( ) ;
234- this . syncExports ( ) ;
235264 }
236- return this . exports ;
237- } ;
238265
239- const getOwn = ( target , property , receiver ) => {
240- return ObjectPrototypeHasOwnProperty ( target , property ) ?
241- ReflectGet ( target , property , receiver ) :
242- undefined ;
243- } ;
266+ compileForInternalLoader ( ) {
267+ if ( this . loaded || this . loading ) {
268+ return this . exports ;
269+ }
244270
245- NativeModule . prototype . getURL = function ( ) {
246- return `node:${ this . id } ` ;
247- } ;
271+ const id = this . id ;
272+ this . loading = true ;
248273
249- NativeModule . prototype . getESMFacade = function ( ) {
250- if ( this . module ) return this . module ;
251- const { ModuleWrap } = internalBinding ( 'module_wrap' ) ;
252- const url = this . getURL ( ) ;
253- const nativeModule = this ;
254- this . module = new ModuleWrap (
255- url , undefined , [ ...this . exportKeys , 'default' ] ,
256- function ( ) {
257- nativeModule . syncExports ( ) ;
258- this . setExport ( 'default' , nativeModule . exports ) ;
259- } ) ;
260- // Ensure immediate sync execution to capture exports now
261- this . module . instantiate ( ) ;
262- this . module . evaluate ( - 1 , false ) ;
263- return this . module ;
264- } ;
274+ try {
275+ const requireFn = this . id . startsWith ( 'internal/deps/' ) ?
276+ requireWithFallbackInDeps : nativeModuleRequire ;
265277
266- // Provide named exports for all builtin libraries so that the libraries
267- // may be imported in a nicer way for ESM users. The default export is left
268- // as the entire namespace (module.exports) and updates when this function is
269- // called so that APMs and other behavior are supported.
270- NativeModule . prototype . syncExports = function ( ) {
271- const names = this . exportKeys ;
272- if ( this . module ) {
273- for ( let i = 0 ; i < names . length ; i ++ ) {
274- const exportName = names [ i ] ;
275- if ( exportName === 'default' ) continue ;
276- this . module . setExport ( exportName ,
277- getOwn ( this . exports , exportName , this . exports ) ) ;
278+ const fn = compileFunction ( id ) ;
279+ fn ( this . exports , requireFn , this , process , internalBinding , primordials ) ;
280+
281+ this . loaded = true ;
282+ } finally {
283+ this . loading = false ;
278284 }
279- }
280- } ;
281285
282- NativeModule . prototype . compile = function ( ) {
283- if ( this . loaded || this . loading ) {
286+ moduleLoadList . push ( `NativeModule ${ id } ` ) ;
284287 return this . exports ;
285288 }
289+ }
286290
287- const id = this . id ;
288- this . loading = true ;
291+ // Think of this as module.exports in this file even though it is not
292+ // written in CommonJS style.
293+ const loaderExports = {
294+ internalBinding,
295+ NativeModule,
296+ require : nativeModuleRequire
297+ } ;
289298
290- try {
291- const requireFn = this . id . startsWith ( 'internal/deps/' ) ?
292- requireWithFallbackInDeps : nativeModuleRequire ;
299+ function nativeModuleRequire ( id ) {
300+ if ( id === loaderId ) {
301+ return loaderExports ;
302+ }
293303
294- const fn = compileFunction ( id ) ;
295- fn ( this . exports , requireFn , this , process , internalBinding , primordials ) ;
304+ const mod = NativeModule . map . get ( id ) ;
305+ // Can't load the internal errors module from here, have to use a raw error.
306+ // eslint-disable-next-line no-restricted-syntax
307+ if ( ! mod ) throw new TypeError ( `Missing internal module '${ id } '` ) ;
308+ return mod . compileForInternalLoader ( ) ;
309+ }
296310
297- this . loaded = true ;
298- } finally {
299- this . loading = false ;
311+ // Allow internal modules from dependencies to require
312+ // other modules from dependencies by providing fallbacks.
313+ function requireWithFallbackInDeps ( request ) {
314+ if ( ! NativeModule . map . has ( request ) ) {
315+ request = `internal/deps/${ request } ` ;
300316 }
317+ return nativeModuleRequire ( request ) ;
318+ }
301319
302- moduleLoadList . push ( `NativeModule ${ id } ` ) ;
303- return this . exports ;
304- } ;
305-
306- // This will be passed to internal/bootstrap/node.js.
320+ // Pass the exports back to C++ land for C++ internals to use.
307321return loaderExports ;
0 commit comments