From 1790999e85b90b192905564feca5b5b13a45f094 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Mon, 14 Feb 2022 16:47:43 +0100 Subject: [PATCH 01/12] feat: ESM import from data URI (#12370) --- packages/jest-runtime/src/index.ts | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 3940af1822de..6ee96c397abe 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -553,6 +553,56 @@ export default class Runtime { return globals; } + if (specifier.startsWith('data:')) { + const fromCache = this._esmoduleRegistry.get(specifier); + + if (fromCache) { + return fromCache; + } + + const match = specifier.match( + /^data:text\/javascript;(charset=utf-8|base64),(.*)$/, + ); + + if (!match) { + throw new Error('Invalid data URI'); + } + + let code = match[2]; + if (match[1] === 'base64') { + code = atob(code); + } else if (match[1] === 'charset=utf-8') { + code = decodeURIComponent(code); + } + + const module = new SourceTextModule(code, { + context, + identifier: specifier, + 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: ImportMeta) { + meta.url = specifier; + }, + }); + + this._esmoduleRegistry.set(specifier, module); + return module; + } + if (specifier.startsWith('file://')) { specifier = fileURLToPath(specifier); } From 4f490884d4c0a7dbc027435bacea50c6e048c393 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 11:12:04 +0100 Subject: [PATCH 02/12] test: ESM import from data URI --- .../__snapshots__/nativeEsm.test.ts.snap | 2 +- e2e/native-esm/__tests__/native-esm.test.js | 71 +++++++++++++++---- packages/jest-resolve/src/resolver.ts | 3 + packages/jest-runtime/src/index.ts | 13 ++++ 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index 3c01bb2e69eb..b683ea29a9e4 100644 --- a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap +++ b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap @@ -2,7 +2,7 @@ exports[`on node >=12.16.0 runs test with native ESM 1`] = ` "Test Suites: 1 passed, 1 total -Tests: 21 passed, 21 total +Tests: 25 passed, 25 total Snapshots: 0 total Time: <> Ran all test suites matching /native-esm.test.js/i." diff --git a/e2e/native-esm/__tests__/native-esm.test.js b/e2e/native-esm/__tests__/native-esm.test.js index aa6c6456d7c7..d2c41e4de35d 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import dns from 'dns'; +import {jest as jestObject} from '@jest/globals' +import dns from 'dns' // the point here is that it's the node core module // eslint-disable-next-line no-restricted-imports -import {readFileSync} from 'fs'; -import {createRequire} from 'module'; -import prefixDns from 'node:dns'; -import {dirname, resolve} from 'path'; -import {fileURLToPath} from 'url'; -import {jest as jestObject} from '@jest/globals'; -import staticImportedStatefulFromCjs from '../fromCjs.mjs'; -import {double} from '../index'; -import defaultFromCjs, {half, namedFunction} from '../namedExport.cjs'; -import {bag} from '../namespaceExport.js'; +import {readFileSync} from 'fs' +import {createRequire} from 'module' +import prefixDns from 'node:dns' +import {dirname, resolve} from 'path' +import {fileURLToPath} from 'url' +import staticImportedStatefulFromCjs from '../fromCjs.mjs' +import {double} from '../index' +import defaultFromCjs, {half, namedFunction} from '../namedExport.cjs' +import {bag} from '../namespaceExport.js' /* eslint-disable import/no-duplicates */ -import staticImportedStateful from '../stateful.mjs'; -import staticImportedStatefulWithQuery from '../stateful.mjs?query=1'; -import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2'; +import staticImportedStateful from '../stateful.mjs' +import staticImportedStatefulWithQuery from '../stateful.mjs?query=1' +import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2' /* eslint-enable import/no-duplicates */ test('should have correct import.meta', () => { @@ -195,3 +195,46 @@ test('can mock module', async () => { test('supports imports using "node:" prefix', () => { expect(dns).toBe(prefixDns); }); + +test('supports imports from "data:text/javascript;charset=utf-8" URI', async () => { + const code = 'export const something = "some value"'; + const importedEncoded = await import( + `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}` + ); + expect(importedEncoded.something).toBe('some value'); +}); + +test('supports imports from "data:text/javascript;base64" URI', async () => { + const code = 'export const something = "some value"'; + const importedBase64 = await import( + `data:text/javascript;base64,${btoa(code)}` + ); + expect(importedBase64.something).toBe('some value'); +}); + +test('imports from "data:" URI is properly cached', async () => { + const code = + 'export const wrapper = {value: 123}\nexport const set = (value) => wrapper.value = value'; + const data1 = await import( + `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}` + ); + expect(data1.wrapper.value).toBe(123); + data1.set(234); + expect(data1.wrapper.value).toBe(234); + const data2 = await import(`data:text/javascript;base64,${btoa(code)}`); + expect(data2.wrapper.value).toBe(123); + const data3 = await import( + `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}` + ); + expect(data3.wrapper.value).toBe(234); +}); + +test('can mock "data:" URI module', async () => { + const code = 'export const something = "some value"'; + const dataModule = `data:text/javascript;base64,${btoa(code)}`; + jestObject.unstable_mockModule(dataModule, () => ({foo: 'bar'}), { + virtual: true, + }); + const mocked = await import(dataModule); + expect(mocked.foo).toBe('bar'); +}); diff --git a/packages/jest-resolve/src/resolver.ts b/packages/jest-resolve/src/resolver.ts index 4dacbc8fc5fd..75c4018e4088 100644 --- a/packages/jest-resolve/src/resolver.ts +++ b/packages/jest-resolve/src/resolver.ts @@ -370,6 +370,9 @@ export default class Resolver { if (this.isCoreModule(moduleName)) { return moduleName; } + if (moduleName.startsWith('data:')) { + return moduleName; + } return this._isModuleResolved(from, moduleName) ? this.getModule(moduleName) : this._getVirtualMockPath(virtualMocks, from, moduleName, options); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 6ee96c397abe..b3bcd228726b 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -554,6 +554,17 @@ export default class Runtime { } if (specifier.startsWith('data:')) { + if ( + this._shouldMock( + referencingIdentifier, + specifier, + this._explicitShouldMockModule, + {conditions: this.esmConditions}, + ) + ) { + return this.importMock(referencingIdentifier, specifier, context); + } + const fromCache = this._esmoduleRegistry.get(specifier); if (fromCache) { @@ -573,6 +584,8 @@ export default class Runtime { code = atob(code); } else if (match[1] === 'charset=utf-8') { code = decodeURIComponent(code); + } else { + throw new Error('Invalid data URI encoding'); } const module = new SourceTextModule(code, { From 84775d5b9ae59be455dcc97d95782f8ba9356939 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 12:12:50 +0100 Subject: [PATCH 03/12] test: update snapshot --- e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap index b756a376579a..d382ec9c3f30 100644 --- a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap +++ b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap @@ -41,7 +41,7 @@ exports[`moduleNameMapper wrong array configuration 1`] = ` 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:568:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:572:17) at Object.require (index.js:10:1)" `; @@ -70,6 +70,6 @@ exports[`moduleNameMapper wrong configuration 1`] = ` 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:568:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/resolver.js:572:17) at Object.require (index.js:10:1)" `; From 3986c525add258885adfe1c230888d0879bec3ce Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 14:44:05 +0100 Subject: [PATCH 04/12] fix: replace `atob` and `btoa` with Buffer --- e2e/native-esm/__tests__/native-esm.test.js | 38 ++++++++++++--------- packages/jest-runtime/src/index.ts | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/e2e/native-esm/__tests__/native-esm.test.js b/e2e/native-esm/__tests__/native-esm.test.js index d2c41e4de35d..c78664a744c2 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -5,23 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {jest as jestObject} from '@jest/globals' -import dns from 'dns' +import dns from 'dns'; // the point here is that it's the node core module // eslint-disable-next-line no-restricted-imports -import {readFileSync} from 'fs' -import {createRequire} from 'module' -import prefixDns from 'node:dns' -import {dirname, resolve} from 'path' -import {fileURLToPath} from 'url' -import staticImportedStatefulFromCjs from '../fromCjs.mjs' -import {double} from '../index' -import defaultFromCjs, {half, namedFunction} from '../namedExport.cjs' -import {bag} from '../namespaceExport.js' +import {readFileSync} from 'fs'; +import {createRequire} from 'module'; +import prefixDns from 'node:dns'; +import {dirname, resolve} from 'path'; +import {fileURLToPath} from 'url'; +import {jest as jestObject} from '@jest/globals'; +import staticImportedStatefulFromCjs from '../fromCjs.mjs'; +import {double} from '../index'; +import defaultFromCjs, {half, namedFunction} from '../namedExport.cjs'; +import {bag} from '../namespaceExport.js'; /* eslint-disable import/no-duplicates */ -import staticImportedStateful from '../stateful.mjs' -import staticImportedStatefulWithQuery from '../stateful.mjs?query=1' -import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2' +import staticImportedStateful from '../stateful.mjs'; +import staticImportedStatefulWithQuery from '../stateful.mjs?query=1'; +import staticImportedStatefulWithAnotherQuery from '../stateful.mjs?query=2'; /* eslint-enable import/no-duplicates */ test('should have correct import.meta', () => { @@ -207,7 +207,7 @@ test('supports imports from "data:text/javascript;charset=utf-8" URI', async () test('supports imports from "data:text/javascript;base64" URI', async () => { const code = 'export const something = "some value"'; const importedBase64 = await import( - `data:text/javascript;base64,${btoa(code)}` + `data:text/javascript;base64,${Buffer.from(code).toString('base64')}` ); expect(importedBase64.something).toBe('some value'); }); @@ -221,7 +221,9 @@ test('imports from "data:" URI is properly cached', async () => { expect(data1.wrapper.value).toBe(123); data1.set(234); expect(data1.wrapper.value).toBe(234); - const data2 = await import(`data:text/javascript;base64,${btoa(code)}`); + const data2 = await import( + `data:text/javascript;base64,${Buffer.from(code).toString('base64')}` + ); expect(data2.wrapper.value).toBe(123); const data3 = await import( `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}` @@ -231,7 +233,9 @@ test('imports from "data:" URI is properly cached', async () => { test('can mock "data:" URI module', async () => { const code = 'export const something = "some value"'; - const dataModule = `data:text/javascript;base64,${btoa(code)}`; + const dataModule = `data:text/javascript;base64,${Buffer.from(code).toString( + 'base64', + )}`; jestObject.unstable_mockModule(dataModule, () => ({foo: 'bar'}), { virtual: true, }); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index b3bcd228726b..a7900f54b9d5 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -581,7 +581,7 @@ export default class Runtime { let code = match[2]; if (match[1] === 'base64') { - code = atob(code); + code = Buffer.from(code, 'base64').toString(); } else if (match[1] === 'charset=utf-8') { code = decodeURIComponent(code); } else { From a588f8c873e1e7f3cb42b8f0490f342073003da0 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 14:48:36 +0100 Subject: [PATCH 05/12] refactor: named capture groups in data URI regex --- packages/jest-runtime/src/index.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index a7900f54b9d5..991f3dd68cb8 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -572,17 +572,18 @@ export default class Runtime { } const match = specifier.match( - /^data:text\/javascript;(charset=utf-8|base64),(.*)$/, + /^data:text\/javascript;(?charset=utf-8|base64),(?.*)$/, ); - if (!match) { + if (!match || !match.groups) { throw new Error('Invalid data URI'); } - let code = match[2]; - if (match[1] === 'base64') { + const encoding = match.groups.encoding; + let code = match.groups.code; + if (encoding === 'base64') { code = Buffer.from(code, 'base64').toString(); - } else if (match[1] === 'charset=utf-8') { + } else if (encoding === 'charset=utf-8') { code = decodeURIComponent(code); } else { throw new Error('Invalid data URI encoding'); From f4158e3d190514e3aed65e908f0a74fbc0d431cc Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 17:10:59 +0100 Subject: [PATCH 06/12] feat: handle json import from data URI --- .../__snapshots__/nativeEsm.test.ts.snap | 2 +- e2e/native-esm/__tests__/native-esm.test.js | 52 ++++++++++++++++++- e2e/native-esm/staticDataImport.js | 13 +++++ packages/jest-runtime/src/index.ts | 17 ++++-- 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 e2e/native-esm/staticDataImport.js diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index b683ea29a9e4..6a6886d95fd3 100644 --- a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap +++ b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap @@ -2,7 +2,7 @@ exports[`on node >=12.16.0 runs test with native ESM 1`] = ` "Test Suites: 1 passed, 1 total -Tests: 25 passed, 25 total +Tests: 32 passed, 32 total Snapshots: 0 total Time: <> Ran all test suites matching /native-esm.test.js/i." diff --git a/e2e/native-esm/__tests__/native-esm.test.js b/e2e/native-esm/__tests__/native-esm.test.js index c78664a744c2..9219a24749ff 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -196,7 +196,7 @@ test('supports imports using "node:" prefix', () => { expect(dns).toBe(prefixDns); }); -test('supports imports from "data:text/javascript;charset=utf-8" URI', async () => { +test('supports imports from "data:text/javascript" URI with charset=utf-8 encoding', async () => { const code = 'export const something = "some value"'; const importedEncoded = await import( `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}` @@ -204,7 +204,7 @@ test('supports imports from "data:text/javascript;charset=utf-8" URI', async () expect(importedEncoded.something).toBe('some value'); }); -test('supports imports from "data:text/javascript;base64" URI', async () => { +test('supports imports from "data:text/javascript" URI with base64 encoding', async () => { const code = 'export const something = "some value"'; const importedBase64 = await import( `data:text/javascript;base64,${Buffer.from(code).toString('base64')}` @@ -212,6 +212,54 @@ test('supports imports from "data:text/javascript;base64" URI', async () => { expect(importedBase64.something).toBe('some value'); }); +test('supports imports from "data:text/javascript" URI without explicit encoding', async () => { + const code = 'export const something = "some value"'; + const importedEncoded = await import( + `data:text/javascript,${encodeURIComponent(code)}` + ); + expect(importedEncoded.something).toBe('some value'); +}); + +test('imports from "data:text/javascript" URI with invalid encoding fail', async () => { + const code = 'export const something = "some value"'; + await expect( + async () => + await import( + `data:text/javascript;charset=badEncoding,${encodeURIComponent(code)}` + ), + ).rejects.toThrow('Invalid data URI'); +}); + +test('imports from "data:" URI with invalid mime type fail', async () => { + const code = 'export const something = "some value"'; + await expect( + async () => await import(`data:something/else,${encodeURIComponent(code)}`), + ).rejects.toThrow('Invalid data URI'); +}); + +test('imports from "data:text/javascript" URI with invalid data fail', async () => { + await expect( + async () => + await import('data:text/javascript;charset=utf-8,so(me)+.-gibberish'), + ).rejects.toThrow("Unexpected token '.'"); +}); + +test('imports from "data:application/wasm" URI not supported', async () => { + await expect( + async () => await import('data:application/wasm,96cafe00babe'), + ).rejects.toThrow('Unsupported MIME type'); +}); + +test('supports imports from "data:application/json" URI', async () => { + const data = await import('data:application/json,{foo: "bar"}'); + expect(data.default).toStrictEqual({foo: 'bar'}); +}); + +test('supports static "data:" URI import', async () => { + const module = await import('../staticDataImport.js'); + expect(module.value()).toStrictEqual({bar: {obj: 456}, foo: '123'}); +}); + test('imports from "data:" URI is properly cached', async () => { const code = 'export const wrapper = {value: 123}\nexport const set = (value) => wrapper.value = value'; diff --git a/e2e/native-esm/staticDataImport.js b/e2e/native-esm/staticDataImport.js new file mode 100644 index 000000000000..f69a8a4f4452 --- /dev/null +++ b/e2e/native-esm/staticDataImport.js @@ -0,0 +1,13 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import bar from 'data:application/json,{obj: 456}'; +import {foo} from 'data:text/javascript,export const foo = "123"'; + +export function value() { + return {bar, foo}; +} diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 991f3dd68cb8..743bd7ad96fd 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -572,23 +572,32 @@ export default class Runtime { } const match = specifier.match( - /^data:text\/javascript;(?charset=utf-8|base64),(?.*)$/, + /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/, ); if (!match || !match.groups) { throw new Error('Invalid data URI'); } + const mime = match.groups.mime; + if (mime === 'application/wasm') { + throw new Error('Unsupported MIME type'); + } + const encoding = match.groups.encoding; let code = match.groups.code; - if (encoding === 'base64') { - code = Buffer.from(code, 'base64').toString(); - } else if (encoding === 'charset=utf-8') { + if (!encoding || encoding === 'charset=utf-8') { code = decodeURIComponent(code); + } else if (encoding === 'base64') { + code = Buffer.from(code, 'base64').toString(); } else { throw new Error('Invalid data URI encoding'); } + if (mime === 'application/json') { + code = 'export default ' + code; + } + const module = new SourceTextModule(code, { context, identifier: specifier, From 2befb8bf03f8ef2a581cf9ad5731c9edb09f243f Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Tue, 15 Feb 2022 17:14:20 +0100 Subject: [PATCH 07/12] docs: update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 884d023a78ee..e6ecb2147327 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924)) - `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323)) - `[jest-resolver]` [**BREAKING**] Add support for `package.json` `exports` ([11961](https://github.com/facebook/jest/pull/11961)) +- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392)) - `[@jes/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384)) - `[jest-worker]` [**BREAKING**] Allow only absolute `workerPath` ([#12343](https://github.com/facebook/jest/pull/12343)) From d5bd818b0e6af9b6f89dde7b4976c7497dcfaab0 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Wed, 16 Feb 2022 08:14:13 +0100 Subject: [PATCH 08/12] fix: specify invalid encoding in error Co-authored-by: Simen Bekkhus --- packages/jest-runtime/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 743bd7ad96fd..2d0bb1344d83 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -591,7 +591,7 @@ export default class Runtime { } else if (encoding === 'base64') { code = Buffer.from(code, 'base64').toString(); } else { - throw new Error('Invalid data URI encoding'); + throw new Error(`Invalid data URI encoding: ${encoding}`); } if (mime === 'application/json') { From a354fb8ba6418e4adfdedc8ca5ba108e821cf3de Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Wed, 16 Feb 2022 08:14:44 +0100 Subject: [PATCH 09/12] fix: better error message Co-authored-by: Simen Bekkhus --- packages/jest-runtime/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 2d0bb1344d83..df116281d7f2 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -581,7 +581,7 @@ export default class Runtime { const mime = match.groups.mime; if (mime === 'application/wasm') { - throw new Error('Unsupported MIME type'); + throw new Error('WASM is currently not supported'); } const encoding = match.groups.encoding; From a6ffa893d1248e71d9d4444a059281be6a9df375 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Wed, 16 Feb 2022 08:26:06 +0100 Subject: [PATCH 10/12] refactor: move regex to constant --- packages/jest-runtime/src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index df116281d7f2..1374924a9b3f 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -63,6 +63,8 @@ export type {Context} from './types'; const esmIsAvailable = typeof SourceTextModule === 'function'; +const dataURIregex = /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/; + interface JestGlobals extends Global.TestFrameworkGlobals { expect: typeof JestGlobals.expect; } @@ -571,9 +573,7 @@ export default class Runtime { return fromCache; } - const match = specifier.match( - /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/, - ); + const match = specifier.match(dataURIregex); if (!match || !match.groups) { throw new Error('Invalid data URI'); From 67f7e6355127b979efc5c3744023e222c00106b9 Mon Sep 17 00:00:00 2001 From: Tommaso Bossi Date: Wed, 16 Feb 2022 08:36:43 +0100 Subject: [PATCH 11/12] refactor: use `SyntheticModule` for json data URI import --- e2e/native-esm/__tests__/native-esm.test.js | 8 +-- e2e/native-esm/staticDataImport.js | 2 +- packages/jest-runtime/src/index.ts | 59 ++++++++++++--------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/e2e/native-esm/__tests__/native-esm.test.js b/e2e/native-esm/__tests__/native-esm.test.js index 9219a24749ff..73a652bdac5e 100644 --- a/e2e/native-esm/__tests__/native-esm.test.js +++ b/e2e/native-esm/__tests__/native-esm.test.js @@ -247,17 +247,17 @@ test('imports from "data:text/javascript" URI with invalid data fail', async () test('imports from "data:application/wasm" URI not supported', async () => { await expect( async () => await import('data:application/wasm,96cafe00babe'), - ).rejects.toThrow('Unsupported MIME type'); + ).rejects.toThrow('WASM is currently not supported'); }); test('supports imports from "data:application/json" URI', async () => { - const data = await import('data:application/json,{foo: "bar"}'); - expect(data.default).toStrictEqual({foo: 'bar'}); + const data = await import('data:application/json,{"foo": "bar"}'); + expect(data.default).toEqual({foo: 'bar'}); }); test('supports static "data:" URI import', async () => { const module = await import('../staticDataImport.js'); - expect(module.value()).toStrictEqual({bar: {obj: 456}, foo: '123'}); + expect(module.value()).toEqual({bar: {obj: 456}, foo: '123'}); }); test('imports from "data:" URI is properly cached', async () => { diff --git a/e2e/native-esm/staticDataImport.js b/e2e/native-esm/staticDataImport.js index f69a8a4f4452..f26873dc7b48 100644 --- a/e2e/native-esm/staticDataImport.js +++ b/e2e/native-esm/staticDataImport.js @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import bar from 'data:application/json,{obj: 456}'; +import bar from 'data:application/json,{"obj": 456}'; import {foo} from 'data:text/javascript,export const foo = "123"'; export function value() { diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 1374924a9b3f..0ee7f60d0f61 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -594,33 +594,42 @@ export default class Runtime { throw new Error(`Invalid data URI encoding: ${encoding}`); } + let module; if (mime === 'application/json') { - code = 'export default ' + code; - } - - const module = new SourceTextModule(code, { - context, - identifier: specifier, - 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, - ); + module = new SyntheticModule( + ['default'], + function () { + const obj = JSON.parse(code); + // @ts-expect-error: TS doesn't know what `this` is + this.setExport('default', obj); + }, + {context, identifier: specifier}, + ); + } else { + module = new SourceTextModule(code, { + context, + identifier: specifier, + 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: ImportMeta) { - meta.url = specifier; - }, - }); + return this.linkAndEvaluateModule(module); + }, + initializeImportMeta(meta: ImportMeta) { + meta.url = specifier; + }, + }); + } this._esmoduleRegistry.set(specifier, module); return module; From 1f226d2e709ab7f11171854dc4bade878ee5e726 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 16 Feb 2022 09:54:13 +0100 Subject: [PATCH 12/12] eslint --- packages/jest-runtime/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 0ee7f60d0f61..368384b3e51c 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -63,7 +63,8 @@ export type {Context} from './types'; const esmIsAvailable = typeof SourceTextModule === 'function'; -const dataURIregex = /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/; +const dataURIregex = + /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/; interface JestGlobals extends Global.TestFrameworkGlobals { expect: typeof JestGlobals.expect;