Run wasm built with wasm-pack with pythonmonkey

I have been trying to run wasm code built with wasm-pack with pythonmonkey,  a Mozilla SpiderMonkey JavaScript engine embedded into the Python Runtime.

Consider a lib.rs as follow:

1use wasm_bindgen::prelude::*;  
2  
3#[wasm_bindgen]
4pub fn add(a: i32, b: i32) -> i32 {  
5  return a + b; 
6}

With wasm-pack, we can build the src code to wasm

1wasm-pack build --target nodejs --no-typescript

We will get a .wasm file and a .js wrapper file. The case here is that we can not instantiate the .wasm directly, which will get us errors like No module named '__wbindgen_placeholder__', or import object field '__wbindgen_init_externref_table' is not a Function. We should instead import the .js wrapper file, and use exported interfaces.

But import the .js wrapper in pythonmonkey would be a problem, the generated .js wrapper contains the following code:

1const path = require('path').join(__dirname, '<your_module_name_bg>.wasm');  
2const bytes = require('fs').readFileSync(path);

But pythonmonkey doesn't provide those libs. So we have to monkey patch those modules and methods. For example, a fs module with readFileSync method in fs.py:

1def readFileSync(path):
2    with open(path, 'rb') as fd:  
3        return bytearray(fd.read())  
4  
5exports = {
6  'readFileSync': readFileSync  
7}

We can require fs.py in pythonmonkey by require('./fs.py'). And one more step, we have to hack require.cache to make sure we can require('fs') in the future code. So we we can write a load function for this:

 1import os  
 2import pythonmonkey as pm  
 3  
 4require = pm.createRequire(os.path.join(os.getcwd(),
 5										f'{__name__}__virtual__'))  
 6pm.globalThis['require'] = require
 7  
 8def load(module, path=None):
 9    if path is None:
10        path = f'./{module}.py'
11    pm.eval(f'''
12require('{path}')
13require.cache['{module}'] = require.cache[require.resolve('{path}')]  
14''')

Finally, we can run our add(1, 1) wasm code in pythonmonkey:

1load('fs')  
2load('path')
3
4pm.eval('''  
5const wasm = require('./pkg/wasm_add.js');  
6console.log(wasm.add(1, 1));

But if your code is more complex than a simple add function, especially if you expose more interfaces and more types, the generated .js wrapper file would be much more complex. More modules and functions need to be patched. One more thing, pythonmonkey doesn't support object destruction(const { a } = {a: 1, b: 2}) currently(version 1.1.0), so the generated .js wrapper need to be edited.