@@ -6,11 +6,13 @@ require('internal/modules/cjs/loader');
6
6
const {
7
7
Array,
8
8
ArrayIsArray,
9
+ ArrayPrototypeIncludes,
9
10
ArrayPrototypeJoin,
10
11
ArrayPrototypePush,
11
12
FunctionPrototypeBind,
12
13
FunctionPrototypeCall,
13
14
ObjectCreate,
15
+ ObjectFreeze,
14
16
ObjectSetPrototypeOf,
15
17
PromiseAll,
16
18
RegExpPrototypeExec,
@@ -20,11 +22,14 @@ const {
20
22
} = primordials ;
21
23
22
24
const {
25
+ ERR_FAILED_IMPORT_ASSERTION ,
23
26
ERR_INVALID_ARG_TYPE ,
24
27
ERR_INVALID_ARG_VALUE ,
28
+ ERR_INVALID_IMPORT_ASSERTION ,
25
29
ERR_INVALID_MODULE_SPECIFIER ,
26
30
ERR_INVALID_RETURN_PROPERTY_VALUE ,
27
31
ERR_INVALID_RETURN_VALUE ,
32
+ ERR_MISSING_IMPORT_ASSERTION ,
28
33
ERR_UNKNOWN_MODULE_FORMAT
29
34
} = require ( 'internal/errors' ) . codes ;
30
35
const { pathToFileURL, isURLInstance } = require ( 'internal/url' ) ;
@@ -44,6 +49,10 @@ const { translators } = require(
44
49
'internal/modules/esm/translators' ) ;
45
50
const { getOptionValue } = require ( 'internal/options' ) ;
46
51
52
+ const importAssertionTypeCache = new SafeWeakMap ( ) ;
53
+ const finalFormatCache = new SafeWeakMap ( ) ;
54
+ const supportedTypes = ObjectFreeze ( [ undefined , 'json' ] ) ;
55
+
47
56
/**
48
57
* An ESMLoader instance is used as the main entry point for loading ES modules.
49
58
* Currently, this is a singleton -- there is only one used for loading
@@ -202,33 +211,74 @@ class ESMLoader {
202
211
const { ModuleWrap, callbackMap } = internalBinding ( 'module_wrap' ) ;
203
212
const module = new ModuleWrap ( url , undefined , source , 0 , 0 ) ;
204
213
callbackMap . set ( module , {
205
- importModuleDynamically : ( specifier , { url } ) => {
206
- return this . import ( specifier , url ) ;
214
+ importModuleDynamically : ( specifier , { url } , importAssertions ) => {
215
+ return this . import ( specifier , url , importAssertions ) ;
207
216
}
208
217
} ) ;
209
218
210
219
return module ;
211
220
} ;
212
221
const job = new ModuleJob ( this , url , evalInstance , false , false ) ;
213
222
this . moduleMap . set ( url , job ) ;
223
+ finalFormatCache . set ( job , 'module' ) ;
214
224
const { module } = await job . run ( ) ;
215
225
216
226
return {
217
227
namespace : module . getNamespace ( ) ,
218
228
} ;
219
229
}
220
230
221
- async getModuleJob ( specifier , parentURL ) {
231
+ async getModuleJob ( specifier , parentURL , importAssertions ) {
232
+ if ( ! ArrayPrototypeIncludes ( supportedTypes , importAssertions . type ) ) {
233
+ throw new ERR_INVALID_IMPORT_ASSERTION ( 'type' , importAssertions . type ) ;
234
+ }
235
+
222
236
const { format, url } = await this . resolve ( specifier , parentURL ) ;
223
237
let job = this . moduleMap . get ( url ) ;
224
238
// CommonJS will set functions for lazy job evaluation.
225
239
if ( typeof job === 'function' ) this . moduleMap . set ( url , job = job ( ) ) ;
226
240
227
- if ( job !== undefined ) return job ;
241
+ if ( job != null ) {
242
+ const currentImportAssertionType = importAssertionTypeCache . get ( job ) ;
243
+ if ( currentImportAssertionType === importAssertions . type ) return job ;
244
+
245
+ try {
246
+ // To avoid race conditions, wait for previous module to fulfill first.
247
+ await job . modulePromise ;
248
+ } catch {
249
+ // If the other job failed with a different `type` assertion, we got
250
+ // another chance.
251
+ job = undefined ;
252
+ }
253
+
254
+ if ( job !== undefined ) {
255
+ const finalFormat = finalFormatCache . get ( job ) ;
256
+ if ( importAssertions . type == null && finalFormat === 'json' ) {
257
+ throw new ERR_MISSING_IMPORT_ASSERTION ( url , finalFormat ,
258
+ 'type' , 'json' ) ;
259
+ }
260
+ if (
261
+ importAssertions . type == null ||
262
+ ( importAssertions . type === 'json' && finalFormat === 'json' )
263
+ ) return job ;
264
+ throw new ERR_FAILED_IMPORT_ASSERTION (
265
+ url , 'type' , importAssertions . type , finalFormat ) ;
266
+ }
267
+ }
228
268
229
269
const moduleProvider = async ( url , isMain ) => {
230
270
const { format : finalFormat , source } = await this . load ( url , { format } ) ;
231
271
272
+ if ( importAssertions . type === 'json' && finalFormat !== 'json' ) {
273
+ throw new ERR_FAILED_IMPORT_ASSERTION (
274
+ url , 'type' , importAssertions . type , finalFormat ) ;
275
+ }
276
+ if ( importAssertions . type !== 'json' && finalFormat === 'json' ) {
277
+ throw new ERR_MISSING_IMPORT_ASSERTION ( url , finalFormat ,
278
+ 'type' , 'json' ) ;
279
+ }
280
+ finalFormatCache . set ( job , finalFormat ) ;
281
+
232
282
const translator = translators . get ( finalFormat ) ;
233
283
234
284
if ( ! translator ) throw new ERR_UNKNOWN_MODULE_FORMAT ( finalFormat ) ;
@@ -249,6 +299,7 @@ class ESMLoader {
249
299
inspectBrk
250
300
) ;
251
301
302
+ importAssertionTypeCache . set ( job , importAssertions . type ) ;
252
303
this . moduleMap . set ( url , job ) ;
253
304
254
305
return job ;
@@ -262,18 +313,19 @@ class ESMLoader {
262
313
* loader module.
263
314
*
264
315
* @param {string | string[] } specifiers Path(s) to the module
265
- * @param {string } [parentURL] Path of the parent importing the module
266
- * @returns {object | object[] } A list of module export(s)
316
+ * @param {string } parentURL Path of the parent importing the module
317
+ * @param {Record<string, Record<string, string>> } importAssertions
318
+ * @returns {Promise<object | object[]> } A list of module export(s)
267
319
*/
268
- async import ( specifiers , parentURL ) {
320
+ async import ( specifiers , parentURL , importAssertions ) {
269
321
const wasArr = ArrayIsArray ( specifiers ) ;
270
322
if ( ! wasArr ) specifiers = [ specifiers ] ;
271
323
272
324
const count = specifiers . length ;
273
325
const jobs = new Array ( count ) ;
274
326
275
327
for ( let i = 0 ; i < count ; i ++ ) {
276
- jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL )
328
+ jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL , importAssertions )
277
329
. then ( ( job ) => job . run ( ) )
278
330
. then ( ( { module } ) => module . getNamespace ( ) ) ;
279
331
}
0 commit comments