@@ -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,8 +22,10 @@ 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 ,
@@ -44,6 +48,10 @@ const { translators } = require(
4448 'internal/modules/esm/translators' ) ;
4549const { getOptionValue } = require ( 'internal/options' ) ;
4650
51+ const importAssertionTypeCache = new SafeWeakMap ( ) ;
52+ const finalFormatCache = new SafeWeakMap ( ) ;
53+ const supportedTypes = ObjectFreeze ( [ undefined , 'json' ] ) ;
54+
4755/**
4856 * An ESMLoader instance is used as the main entry point for loading ES modules.
4957 * Currently, this is a singleton -- there is only one used for loading
@@ -202,33 +210,66 @@ class ESMLoader {
202210 const { ModuleWrap, callbackMap } = internalBinding ( 'module_wrap' ) ;
203211 const module = new ModuleWrap ( url , undefined , source , 0 , 0 ) ;
204212 callbackMap . set ( module , {
205- importModuleDynamically : ( specifier , { url } ) => {
206- return this . import ( specifier , url ) ;
213+ importModuleDynamically : ( specifier , { url } , import_assertions ) => {
214+ return this . import ( specifier , url , import_assertions ) ;
207215 }
208216 } ) ;
209217
210218 return module ;
211219 } ;
212220 const job = new ModuleJob ( this , url , evalInstance , false , false ) ;
213221 this . moduleMap . set ( url , job ) ;
222+ finalFormatCache . set ( job , 'module' ) ;
214223 const { module } = await job . run ( ) ;
215224
216225 return {
217226 namespace : module . getNamespace ( ) ,
218227 } ;
219228 }
220229
221- async getModuleJob ( specifier , parentURL ) {
230+ async getModuleJob ( specifier , parentURL , import_assertions ) {
231+ if ( ! ArrayPrototypeIncludes ( supportedTypes , import_assertions . type ) ) {
232+ throw new ERR_INVALID_IMPORT_ASSERTION ( 'type' , import_assertions . type ) ;
233+ }
234+
222235 const { format, url } = await this . resolve ( specifier , parentURL ) ;
223236 let job = this . moduleMap . get ( url ) ;
224237 // CommonJS will set functions for lazy job evaluation.
225238 if ( typeof job === 'function' ) this . moduleMap . set ( url , job = job ( ) ) ;
226239
227- if ( job !== undefined ) return job ;
240+ if ( job != null ) {
241+ const currentImportAssertionType = importAssertionTypeCache . get ( job ) ;
242+ if ( currentImportAssertionType === import_assertions . type ) return job ;
243+
244+ try {
245+ // To avoid race conditions, wait for previous module to fulfill first.
246+ await job . modulePromise ;
247+ } catch {
248+ // If the other job failed with a different `type` assertion, we got
249+ // another chance.
250+ job = undefined ;
251+ }
252+
253+ if ( job !== undefined ) {
254+ const finalFormat = finalFormatCache . get ( job ) ;
255+ if (
256+ import_assertions . type == null ||
257+ ( import_assertions . type === 'json' && finalFormat === 'json' )
258+ ) return job ;
259+ throw new ERR_FAILED_IMPORT_ASSERTION (
260+ url , 'type' , import_assertions . type , finalFormat ) ;
261+ }
262+ }
228263
229264 const moduleProvider = async ( url , isMain ) => {
230265 const { format : finalFormat , source } = await this . load ( url , { format } ) ;
231266
267+ if ( import_assertions . type === 'json' && finalFormat !== 'json' ) {
268+ throw new ERR_FAILED_IMPORT_ASSERTION (
269+ url , 'type' , import_assertions . type , finalFormat ) ;
270+ }
271+ finalFormatCache . set ( job , finalFormat ) ;
272+
232273 const translator = translators . get ( finalFormat ) ;
233274
234275 if ( ! translator ) throw new ERR_UNKNOWN_MODULE_FORMAT ( finalFormat ) ;
@@ -249,6 +290,7 @@ class ESMLoader {
249290 inspectBrk
250291 ) ;
251292
293+ importAssertionTypeCache . set ( job , import_assertions . type ) ;
252294 this . moduleMap . set ( url , job ) ;
253295
254296 return job ;
@@ -262,18 +304,19 @@ class ESMLoader {
262304 * loader module.
263305 *
264306 * @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)
307+ * @param {string } parentURL Path of the parent importing the module
308+ * @param {Record<string, Record<string, string>> } import_assertions
309+ * @returns {Promise<object | object[]> } A list of module export(s)
267310 */
268- async import ( specifiers , parentURL ) {
311+ async import ( specifiers , parentURL , import_assertions ) {
269312 const wasArr = ArrayIsArray ( specifiers ) ;
270313 if ( ! wasArr ) specifiers = [ specifiers ] ;
271314
272315 const count = specifiers . length ;
273316 const jobs = new Array ( count ) ;
274317
275318 for ( let i = 0 ; i < count ; i ++ ) {
276- jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL )
319+ jobs [ i ] = this . getModuleJob ( specifiers [ i ] , parentURL , import_assertions )
277320 . then ( ( job ) => job . run ( ) )
278321 . then ( ( { module } ) => module . getNamespace ( ) ) ;
279322 }
0 commit comments