@@ -6,11 +6,13 @@ require('internal/modules/cjs/loader');
66const {
77 Array,
88 ArrayIsArray,
9+ ArrayPrototypeIncludes,
910 ArrayPrototypeJoin,
1011 ArrayPrototypePush,
1112 FunctionPrototypeBind,
1213 FunctionPrototypeCall,
1314 ObjectCreate,
15+ ObjectFreeze,
1416 ObjectSetPrototypeOf,
1517 PromiseAll,
1618 RegExpPrototypeExec,
@@ -20,11 +22,14 @@ const {
2022} = primordials ;
2123
2224const {
25+ ERR_FAILED_IMPORT_ASSERTION ,
2326 ERR_INVALID_ARG_TYPE ,
2427 ERR_INVALID_ARG_VALUE ,
28+ ERR_INVALID_IMPORT_ASSERTION ,
2529 ERR_INVALID_MODULE_SPECIFIER ,
2630 ERR_INVALID_RETURN_PROPERTY_VALUE ,
2731 ERR_INVALID_RETURN_VALUE ,
32+ ERR_MISSING_IMPORT_ASSERTION ,
2833 ERR_UNKNOWN_MODULE_FORMAT
2934} = require ( 'internal/errors' ) . codes ;
3035const { pathToFileURL, isURLInstance } = require ( 'internal/url' ) ;
@@ -44,6 +49,10 @@ const { translators } = require(
4449 'internal/modules/esm/translators' ) ;
4550const { getOptionValue } = require ( 'internal/options' ) ;
4651
52+ const importAssertionTypeCache = new SafeWeakMap ( ) ;
53+ const finalFormatCache = new SafeWeakMap ( ) ;
54+ const supportedTypes = ObjectFreeze ( [ undefined , 'json' ] ) ;
55+
4756/**
4857 * An ESMLoader instance is used as the main entry point for loading ES modules.
4958 * Currently, this is a singleton -- there is only one used for loading
@@ -202,33 +211,74 @@ class ESMLoader {
202211 const { ModuleWrap, callbackMap } = internalBinding ( 'module_wrap' ) ;
203212 const module = new ModuleWrap ( url , undefined , source , 0 , 0 ) ;
204213 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 ) ;
207216 }
208217 } ) ;
209218
210219 return module ;
211220 } ;
212221 const job = new ModuleJob ( this , url , evalInstance , false , false ) ;
213222 this . moduleMap . set ( url , job ) ;
223+ finalFormatCache . set ( job , 'module' ) ;
214224 const { module } = await job . run ( ) ;
215225
216226 return {
217227 namespace : module . getNamespace ( ) ,
218228 } ;
219229 }
220230
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+
222236 const { format, url } = await this . resolve ( specifier , parentURL ) ;
223237 let job = this . moduleMap . get ( url ) ;
224238 // CommonJS will set functions for lazy job evaluation.
225239 if ( typeof job === 'function' ) this . moduleMap . set ( url , job = job ( ) ) ;
226240
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+ }
228268
229269 const moduleProvider = async ( url , isMain ) => {
230270 const { format : finalFormat , source } = await this . load ( url , { format } ) ;
231271
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+
232282 const translator = translators . get ( finalFormat ) ;
233283
234284 if ( ! translator ) throw new ERR_UNKNOWN_MODULE_FORMAT ( finalFormat ) ;
@@ -249,6 +299,7 @@ class ESMLoader {
249299 inspectBrk
250300 ) ;
251301
302+ importAssertionTypeCache . set ( job , importAssertions . type ) ;
252303 this . moduleMap . set ( url , job ) ;
253304
254305 return job ;
@@ -262,18 +313,19 @@ class ESMLoader {
262313 * loader module.
263314 *
264315 * @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)
267319 */
268- async import ( specifiers , parentURL ) {
320+ async import ( specifiers , parentURL , importAssertions ) {
269321 const wasArr = ArrayIsArray ( specifiers ) ;
270322 if ( ! wasArr ) specifiers = [ specifiers ] ;
271323
272324 const count = specifiers . length ;
273325 const jobs = new Array ( count ) ;
274326
275327 for ( let i = 0 ; i < count ; i ++ ) {
276- jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL )
328+ jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL , importAssertions )
277329 . then ( ( job ) => job . run ( ) )
278330 . then ( ( { module } ) => module . getNamespace ( ) ) ;
279331 }
0 commit comments