From ddedd372148056e65e74f305779d630998df29f0 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 27 Apr 2022 08:24:18 +0200 Subject: [PATCH] feat: add support for import assertions --- packages/jest-runtime/src/index.ts | 125 +++++++++++++++++++---------- 1 file changed, 83 insertions(+), 42 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 1dc2c9a56322..5dd40d4edc6d 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -431,7 +431,19 @@ export default class Runtime { private async loadEsmModule( modulePath: string, query = '', + importAssertions: ImportAssertions = {}, ): Promise { + if (modulePath.endsWith('.json') && importAssertions.type !== 'json') { + const error: NodeJS.ErrnoException = new Error( + `Module "${ + modulePath + (query ? `?${query}` : '') + }" needs an import assertion of type "json"`, + ); + error.code = 'ERR_IMPORT_ASSERTION_TYPE_MISSING'; + + throw error; + } + const cacheKey = modulePath + query; if (this._fileTransformsMutex.has(cacheKey)) { @@ -482,39 +494,54 @@ export default class Runtime { }); try { - const module = new SourceTextModule(transformedCode, { - context, - identifier: modulePath, - importModuleDynamically: async ( - specifier: string, - referencingModule: VMModule, - ) => { - invariant( - runtimeSupportsVmModules, - 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', - ); - const module = await this.resolveModule( - specifier, - referencingModule.identifier, - referencingModule.context, - ); - - return this.linkAndEvaluateModule(module); - }, - initializeImportMeta: (meta: JestImportMeta) => { - meta.url = pathToFileURL(modulePath).href; + let module; + if (modulePath.endsWith('.json')) { + module = new SyntheticModule( + ['default'], + function () { + const obj = JSON.parse(transformedCode); + // @ts-expect-error: TS doesn't know what `this` is + this.setExport('default', obj); + }, + {context, identifier: modulePath}, + ); + } else { + module = new SourceTextModule(transformedCode, { + context, + identifier: modulePath, + importModuleDynamically: async ( + specifier: string, + referencingModule: VMModule, + importAssertions?: ImportAssertions, + ) => { + invariant( + runtimeSupportsVmModules, + 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', + ); + const module = await this.resolveModule( + specifier, + referencingModule.identifier, + referencingModule.context, + importAssertions, + ); + + return this.linkAndEvaluateModule(module); + }, + initializeImportMeta: (meta: JestImportMeta) => { + meta.url = pathToFileURL(modulePath).href; - let jest = this.jestObjectCaches.get(modulePath); + let jest = this.jestObjectCaches.get(modulePath); - if (!jest) { - jest = this._createJestObjectFor(modulePath); + if (!jest) { + jest = this._createJestObjectFor(modulePath); - this.jestObjectCaches.set(modulePath, jest); - } + this.jestObjectCaches.set(modulePath, jest); + } - meta.jest = jest; - }, - }); + meta.jest = jest; + }, + }); + } invariant( !this._esmoduleRegistry.has(cacheKey), @@ -544,6 +571,7 @@ export default class Runtime { specifier: string, referencingIdentifier: string, context: VMContext, + importAssertions: ImportAssertions = {}, ): Promise { if (this.isTornDown) { this._logFormattedReferenceError( @@ -623,6 +651,7 @@ export default class Runtime { importModuleDynamically: async ( specifier: string, referencingModule: VMModule, + importAssertions?: ImportAssertions, ) => { invariant( runtimeSupportsVmModules, @@ -632,6 +661,7 @@ export default class Runtime { specifier, referencingModule.identifier, referencingModule.context, + importAssertions, ); return this.linkAndEvaluateModule(module); @@ -670,9 +700,11 @@ export default class Runtime { if ( this._resolver.isCoreModule(resolved) || - this.unstable_shouldLoadAsEsm(resolved) + this.unstable_shouldLoadAsEsm(resolved) || + // json files are modules when imported in modules + resolved.endsWith('.json') ) { - return this.loadEsmModule(resolved, query); + return this.loadEsmModule(resolved, query, importAssertions); } return this.loadCjsAsEsm(referencingIdentifier, resolved, context); @@ -694,12 +726,18 @@ export default class Runtime { // this method can await it this._esmModuleLinkingMap.set( module, - module.link((specifier: string, referencingModule: VMModule) => - this.resolveModule( - specifier, - referencingModule.identifier, - referencingModule.context, - ), + module.link( + ( + specifier: string, + referencingModule: VMModule, + importCallOptions?: ImportCallOptions, + ) => + this.resolveModule( + specifier, + referencingModule.identifier, + referencingModule.context, + importCallOptions?.assert, + ), ), ); } @@ -1602,7 +1640,11 @@ export default class Runtime { displayErrors: true, filename: scriptFilename, // @ts-expect-error: Experimental ESM API - importModuleDynamically: async (specifier: string) => { + importModuleDynamically: async ( + specifier: string, + _script: Script, + importAssertions?: ImportAssertions, + ) => { invariant( runtimeSupportsVmModules, 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', @@ -1616,6 +1658,7 @@ export default class Runtime { specifier, scriptFilename, context, + importAssertions, ); return this.linkAndEvaluateModule(module); @@ -1680,10 +1723,9 @@ export default class Runtime { : fileURLToPath(modulePath); if (!path.isAbsolute(filename)) { - const error = new TypeError( + const error: NodeJS.ErrnoException = new TypeError( `The argument 'filename' must be a file URL object, file URL string, or absolute path string. Received '${filename}'`, ); - // @ts-expect-error error.code = 'ERR_INVALID_ARG_TYPE'; throw error; } @@ -1716,14 +1758,13 @@ export default class Runtime { filename: string | URL, ) { if (typeof filename !== 'string') { - const error = new TypeError( + const error: NodeJS.ErrnoException = new TypeError( `The argument 'filename' must be string. Received '${filename}'.${ filename instanceof URL ? ' Use createRequire for URL filename.' : '' }`, ); - // @ts-expect-error error.code = 'ERR_INVALID_ARG_TYPE'; throw error; }