@@ -10,7 +10,8 @@ const ModuleRequest = require('internal/loader/ModuleRequest');
10
10
const errors = require ( 'internal/errors' ) ;
11
11
const debug = require ( 'util' ) . debuglog ( 'esm' ) ;
12
12
13
- function getBase ( ) {
13
+ // Returns a file URL for the current working directory.
14
+ function getURLStringForCwd ( ) {
14
15
try {
15
16
return getURLFromFilePath ( `${ process . cwd ( ) } /` ) . href ;
16
17
} catch ( e ) {
@@ -23,22 +24,44 @@ function getBase() {
23
24
}
24
25
}
25
26
27
+ /* A Loader instance is used as the main entry point for loading ES modules.
28
+ * Currently, this is a singleton -- there is only one used for loading
29
+ * the main module and everything in its dependency graph. */
26
30
class Loader {
27
- constructor ( base = getBase ( ) ) {
28
- this . moduleMap = new ModuleMap ( ) ;
31
+ constructor ( base = getURLStringForCwd ( ) ) {
29
32
if ( typeof base !== 'string' ) {
30
33
throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' , 'base' , 'string' ) ;
31
34
}
35
+
36
+ this . moduleMap = new ModuleMap ( ) ;
32
37
this . base = base ;
33
- this . resolver = ModuleRequest . resolve . bind ( null ) ;
38
+ // The resolver has the signature
39
+ // (specifier : string, parentURL : string, defaultResolve)
40
+ // -> Promise<{ url : string,
41
+ // format: anything in Loader.validFormats }>
42
+ // where defaultResolve is ModuleRequest.resolve (having the same
43
+ // signature itself).
44
+ // If `.format` on the returned value is 'dynamic', .dynamicInstantiate
45
+ // will be used as described below.
46
+ this . resolver = ModuleRequest . resolve ;
47
+ // This hook is only called when resolve(...).format is 'dynamic' and has
48
+ // the signature
49
+ // (url : string) -> Promise<{ exports: { ... }, execute: function }>
50
+ // Where `exports` is an object whose property names define the exported
51
+ // names of the generated module. `execute` is a function that receives
52
+ // an object with the same keys as `exports`, whose values are get/set
53
+ // functions for the actual exported values.
34
54
this . dynamicInstantiate = undefined ;
35
55
}
36
56
37
57
hook ( { resolve = ModuleRequest . resolve , dynamicInstantiate } ) {
58
+ // Use .bind() to avoid giving access to the Loader instance when it is
59
+ // called as this.resolver(...);
38
60
this . resolver = resolve . bind ( null ) ;
39
61
this . dynamicInstantiate = dynamicInstantiate ;
40
62
}
41
63
64
+ // Typechecking wrapper around .resolver().
42
65
async resolve ( specifier , parentURL = this . base ) {
43
66
if ( typeof parentURL !== 'string' ) {
44
67
throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' ,
@@ -48,10 +71,11 @@ class Loader {
48
71
const { url, format } = await this . resolver ( specifier , parentURL ,
49
72
ModuleRequest . resolve ) ;
50
73
51
- if ( typeof format !== 'string' ) {
74
+ if ( ! Loader . validFormats . includes ( format ) ) {
52
75
throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' , 'format' ,
53
- [ 'esm' , 'cjs' , 'builtin' , 'addon' , 'json' ] ) ;
76
+ Loader . validFormats ) ;
54
77
}
78
+
55
79
if ( typeof url !== 'string' ) {
56
80
throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' , 'url' , 'string' ) ;
57
81
}
@@ -72,14 +96,20 @@ class Loader {
72
96
return { url, format } ;
73
97
}
74
98
99
+ // May create a new ModuleJob instance if one did not already exist.
75
100
async getModuleJob ( specifier , parentURL = this . base ) {
76
101
const { url, format } = await this . resolve ( specifier , parentURL ) ;
77
102
let job = this . moduleMap . get ( url ) ;
78
103
if ( job === undefined ) {
79
104
let loaderInstance ;
80
105
if ( format === 'dynamic' ) {
106
+ const { dynamicInstantiate } = this ;
107
+ if ( typeof dynamicInstantiate !== 'function' ) {
108
+ throw new errors . Error ( 'ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK' ) ;
109
+ }
110
+
81
111
loaderInstance = async ( url ) => {
82
- const { exports, execute } = await this . dynamicInstantiate ( url ) ;
112
+ const { exports, execute } = await dynamicInstantiate ( url ) ;
83
113
return createDynamicModule ( exports , url , ( reflect ) => {
84
114
debug ( `Loading custom loader ${ url } ` ) ;
85
115
execute ( reflect . exports ) ;
@@ -100,5 +130,6 @@ class Loader {
100
130
return module . namespace ( ) ;
101
131
}
102
132
}
133
+ Loader . validFormats = [ 'esm' , 'cjs' , 'builtin' , 'addon' , 'json' , 'dynamic' ] ;
103
134
Object . setPrototypeOf ( Loader . prototype , null ) ;
104
135
module . exports = Loader ;
0 commit comments