Skip to content

Commit 819090a

Browse files
authored
Support Wasm files that import JS resources (#13608)
1 parent 8f997e0 commit 819090a

File tree

7 files changed

+171
-3
lines changed

7 files changed

+171
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `[jest-environment-node]` fix non-configurable globals ([#13687](https://github.com/facebook/jest/pull/13687))
1414
- `[@jest/expect-utils]` `toMatchObject` should handle `Symbol` properties ([#13639](https://github.com/facebook/jest/pull/13639))
1515
- `[jest-resolve]` Add global paths to `require.resolve.paths` ([#13633](https://github.com/facebook/jest/pull/13633))
16+
- `[jest-runtime]` Support Wasm files that import JS resources ([#13608](https://github.com/facebook/jest/pull/13608))
1617
- `[jest-snapshot]` Make sure to import `babel` outside of the sandbox ([#13694](https://github.com/facebook/jest/pull/13694))
1718

1819
### Chore & Maintenance

e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Ran all test suites matching /native-esm-deep-cjs-reexport.test.js/i."
1010
1111
exports[`runs WebAssembly (Wasm) test with native ESM 1`] = `
1212
"Test Suites: 1 passed, 1 total
13-
Tests: 5 passed, 5 total
13+
Tests: 6 passed, 6 total
1414
Snapshots: 0 total
1515
Time: <<REPLACED>>
1616
Ran all test suites matching /native-esm-wasm.test.js/i."

e2e/native-esm/__tests__/native-esm-wasm.test.js

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
import {readFileSync} from 'node:fs';
9+
import {jest} from '@jest/globals';
910
// file origin: https://github.com/mdn/webassembly-examples/blob/2f2163287f86fe29deb162335bccca7d5d95ca4f/understanding-text-format/add.wasm
1011
// source code: https://github.com/mdn/webassembly-examples/blob/2f2163287f86fe29deb162335bccca7d5d95ca4f/understanding-text-format/add.was
1112
import {add} from '../add.wasm';
@@ -54,3 +55,12 @@ test('imports from "data:application/wasm" URI with invalid encoding fail', asyn
5455
import('data:application/wasm;charset=utf-8,oops'),
5556
).rejects.toThrow('Invalid data URI encoding: charset=utf-8');
5657
});
58+
59+
test('supports wasm files that import js resources (wasm-bindgen)', async () => {
60+
globalThis.alert = jest.fn();
61+
62+
const {greet} = await import('../wasm-bindgen/index.js');
63+
greet('World');
64+
65+
expect(globalThis.alert).toHaveBeenCalledWith('Hello, World!');
66+
});

e2e/native-esm/wasm-bindgen/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
// folder source: https://github.com/rustwasm/wasm-bindgen/tree/4f865308afbe8d2463968457711ad356bae63b71/examples/hello_world
9+
// docs: https://rustwasm.github.io/docs/wasm-bindgen/examples/hello-world.html
10+
11+
import * as wasm from './index_bg.wasm';
12+
export * from './index_bg.js';
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import * as wasm from './index_bg.wasm';
9+
10+
const lTextDecoder =
11+
typeof TextDecoder === 'undefined'
12+
? (0, module.require)('util').TextDecoder
13+
: TextDecoder;
14+
15+
const cachedTextDecoder = new lTextDecoder('utf-8', {
16+
fatal: true,
17+
ignoreBOM: true,
18+
});
19+
20+
cachedTextDecoder.decode();
21+
22+
let cachedUint8Memory0 = new Uint8Array();
23+
24+
function getUint8Memory0() {
25+
if (cachedUint8Memory0.byteLength === 0) {
26+
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
27+
}
28+
return cachedUint8Memory0;
29+
}
30+
31+
function getStringFromWasm0(ptr, len) {
32+
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
33+
}
34+
35+
function logError(f, args) {
36+
try {
37+
return f.apply(this, args);
38+
} catch (e) {
39+
const error = (function () {
40+
try {
41+
return e instanceof Error
42+
? `${e.message}\n\nStack:\n${e.stack}`
43+
: e.toString();
44+
} catch (_) {
45+
return '<failed to stringify thrown value>';
46+
}
47+
})();
48+
console.error(
49+
'wasm-bindgen: imported JS function that was not marked as `catch` threw an error:',
50+
error,
51+
);
52+
throw e;
53+
}
54+
}
55+
56+
let WASM_VECTOR_LEN = 0;
57+
58+
const lTextEncoder =
59+
typeof TextEncoder === 'undefined'
60+
? (0, module.require)('util').TextEncoder
61+
: TextEncoder;
62+
63+
const cachedTextEncoder = new lTextEncoder('utf-8');
64+
65+
const encodeString =
66+
typeof cachedTextEncoder.encodeInto === 'function'
67+
? function (arg, view) {
68+
return cachedTextEncoder.encodeInto(arg, view);
69+
}
70+
: function (arg, view) {
71+
const buf = cachedTextEncoder.encode(arg);
72+
view.set(buf);
73+
return {
74+
read: arg.length,
75+
written: buf.length,
76+
};
77+
};
78+
79+
function passStringToWasm0(arg, malloc, realloc) {
80+
if (typeof arg !== 'string') throw new Error('expected a string argument');
81+
82+
if (realloc === undefined) {
83+
const buf = cachedTextEncoder.encode(arg);
84+
const ptr = malloc(buf.length);
85+
getUint8Memory0()
86+
.subarray(ptr, ptr + buf.length)
87+
.set(buf);
88+
WASM_VECTOR_LEN = buf.length;
89+
return ptr;
90+
}
91+
92+
let len = arg.length;
93+
let ptr = malloc(len);
94+
95+
const mem = getUint8Memory0();
96+
97+
let offset = 0;
98+
99+
for (; offset < len; offset++) {
100+
const code = arg.charCodeAt(offset);
101+
if (code > 0x7f) break;
102+
mem[ptr + offset] = code;
103+
}
104+
105+
if (offset !== len) {
106+
if (offset !== 0) {
107+
arg = arg.slice(offset);
108+
}
109+
ptr = realloc(ptr, len, (len = offset + arg.length * 3));
110+
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
111+
const ret = encodeString(arg, view);
112+
if (ret.read !== arg.length) throw new Error('failed to pass whole string');
113+
offset += ret.written;
114+
}
115+
116+
WASM_VECTOR_LEN = offset;
117+
return ptr;
118+
}
119+
/**
120+
* @param {string} name
121+
*/
122+
export function greet(name) {
123+
const ptr0 = passStringToWasm0(
124+
name,
125+
wasm.__wbindgen_malloc,
126+
wasm.__wbindgen_realloc,
127+
);
128+
const len0 = WASM_VECTOR_LEN;
129+
wasm.greet(ptr0, len0);
130+
}
131+
132+
export function __wbg_alert_9ea5a791b0d4c7a3() {
133+
return logError((arg0, arg1) => {
134+
// eslint-disable-next-line no-undef
135+
alert(getStringFromWasm0(arg0, arg1));
136+
}, arguments);
137+
}
138+
139+
export function __wbindgen_throw(arg0, arg1) {
140+
throw new Error(getStringFromWasm0(arg0, arg1));
141+
}
58.4 KB
Binary file not shown.

packages/jest-runtime/src/index.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1715,9 +1715,13 @@ export default class Runtime {
17151715
const moduleLookup: Record<string, VMModule> = {};
17161716
for (const {module} of imports) {
17171717
if (moduleLookup[module] === undefined) {
1718-
moduleLookup[module] = await this.loadEsmModule(
1719-
await this.resolveModule(module, identifier, context),
1718+
const resolvedModule = await this.resolveModule(
1719+
module,
1720+
identifier,
1721+
context,
17201722
);
1723+
1724+
moduleLookup[module] = await this.linkAndEvaluateModule(resolvedModule);
17211725
}
17221726
}
17231727

0 commit comments

Comments
 (0)