Skip to content

Commit

Permalink
Support Wasm files that import JS resources (#13608)
Browse files Browse the repository at this point in the history
  • Loading branch information
kachkaev committed Jan 1, 2023
1 parent 8f997e0 commit 819090a
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@
- `[jest-environment-node]` fix non-configurable globals ([#13687](https://github.com/facebook/jest/pull/13687))
- `[@jest/expect-utils]` `toMatchObject` should handle `Symbol` properties ([#13639](https://github.com/facebook/jest/pull/13639))
- `[jest-resolve]` Add global paths to `require.resolve.paths` ([#13633](https://github.com/facebook/jest/pull/13633))
- `[jest-runtime]` Support Wasm files that import JS resources ([#13608](https://github.com/facebook/jest/pull/13608))
- `[jest-snapshot]` Make sure to import `babel` outside of the sandbox ([#13694](https://github.com/facebook/jest/pull/13694))

### Chore & Maintenance
Expand Down
2 changes: 1 addition & 1 deletion e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap
Expand Up @@ -10,7 +10,7 @@ Ran all test suites matching /native-esm-deep-cjs-reexport.test.js/i."
exports[`runs WebAssembly (Wasm) test with native ESM 1`] = `
"Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites matching /native-esm-wasm.test.js/i."
Expand Down
10 changes: 10 additions & 0 deletions e2e/native-esm/__tests__/native-esm-wasm.test.js
Expand Up @@ -6,6 +6,7 @@
*/

import {readFileSync} from 'node:fs';
import {jest} from '@jest/globals';
// file origin: https://github.com/mdn/webassembly-examples/blob/2f2163287f86fe29deb162335bccca7d5d95ca4f/understanding-text-format/add.wasm
// source code: https://github.com/mdn/webassembly-examples/blob/2f2163287f86fe29deb162335bccca7d5d95ca4f/understanding-text-format/add.was
import {add} from '../add.wasm';
Expand Down Expand Up @@ -54,3 +55,12 @@ test('imports from "data:application/wasm" URI with invalid encoding fail', asyn
import('data:application/wasm;charset=utf-8,oops'),
).rejects.toThrow('Invalid data URI encoding: charset=utf-8');
});

test('supports wasm files that import js resources (wasm-bindgen)', async () => {
globalThis.alert = jest.fn();

const {greet} = await import('../wasm-bindgen/index.js');
greet('World');

expect(globalThis.alert).toHaveBeenCalledWith('Hello, World!');
});
12 changes: 12 additions & 0 deletions e2e/native-esm/wasm-bindgen/index.js
@@ -0,0 +1,12 @@
/**
* 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.
*/

// folder source: https://github.com/rustwasm/wasm-bindgen/tree/4f865308afbe8d2463968457711ad356bae63b71/examples/hello_world
// docs: https://rustwasm.github.io/docs/wasm-bindgen/examples/hello-world.html

import * as wasm from './index_bg.wasm';
export * from './index_bg.js';
141 changes: 141 additions & 0 deletions e2e/native-esm/wasm-bindgen/index_bg.js
@@ -0,0 +1,141 @@
/**
* 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 * as wasm from './index_bg.wasm';

const lTextDecoder =
typeof TextDecoder === 'undefined'
? (0, module.require)('util').TextDecoder
: TextDecoder;

const cachedTextDecoder = new lTextDecoder('utf-8', {
fatal: true,
ignoreBOM: true,
});

cachedTextDecoder.decode();

let cachedUint8Memory0 = new Uint8Array();

function getUint8Memory0() {
if (cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}

function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}

function logError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
const error = (function () {
try {
return e instanceof Error
? `${e.message}\n\nStack:\n${e.stack}`
: e.toString();
} catch (_) {
return '<failed to stringify thrown value>';
}
})();
console.error(
'wasm-bindgen: imported JS function that was not marked as `catch` threw an error:',
error,
);
throw e;
}
}

let WASM_VECTOR_LEN = 0;

const lTextEncoder =
typeof TextEncoder === 'undefined'
? (0, module.require)('util').TextEncoder
: TextEncoder;

const cachedTextEncoder = new lTextEncoder('utf-8');

const encodeString =
typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length,
};
};

function passStringToWasm0(arg, malloc, realloc) {
if (typeof arg !== 'string') throw new Error('expected a string argument');

if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0()
.subarray(ptr, ptr + buf.length)
.set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}

let len = arg.length;
let ptr = malloc(len);

const mem = getUint8Memory0();

let offset = 0;

for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7f) break;
mem[ptr + offset] = code;
}

if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, (len = offset + arg.length * 3));
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
if (ret.read !== arg.length) throw new Error('failed to pass whole string');
offset += ret.written;
}

WASM_VECTOR_LEN = offset;
return ptr;
}
/**
* @param {string} name
*/
export function greet(name) {
const ptr0 = passStringToWasm0(
name,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
wasm.greet(ptr0, len0);
}

export function __wbg_alert_9ea5a791b0d4c7a3() {
return logError((arg0, arg1) => {
// eslint-disable-next-line no-undef
alert(getStringFromWasm0(arg0, arg1));
}, arguments);
}

export function __wbindgen_throw(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
}
Binary file added e2e/native-esm/wasm-bindgen/index_bg.wasm
Binary file not shown.
8 changes: 6 additions & 2 deletions packages/jest-runtime/src/index.ts
Expand Up @@ -1715,9 +1715,13 @@ export default class Runtime {
const moduleLookup: Record<string, VMModule> = {};
for (const {module} of imports) {
if (moduleLookup[module] === undefined) {
moduleLookup[module] = await this.loadEsmModule(
await this.resolveModule(module, identifier, context),
const resolvedModule = await this.resolveModule(
module,
identifier,
context,
);

moduleLookup[module] = await this.linkAndEvaluateModule(resolvedModule);
}
}

Expand Down

0 comments on commit 819090a

Please sign in to comment.