circuitpython/ports/webassembly
Damien George ffa98cb014 webassembly/proxy_js: Reuse JsProxy ref if object matches.
This reduces memory use by reusing objects, and improves identity/equality
relationships of JavaScript objects on the Python side.

In 77bd8fe5b8 PyProxy's were reused when the
same Python object was proxied across to JavaScript.  This commit does the
same thing but for JsProxy's going from JS to Python.  If an existing
JsProxy reference exists for the JS object about to be proxied across, then
it's reused.

This helps reduce the number of alive objects (memory use), and, more
importantly, improves equality relationships of JavaScript objects on the
Python side.  Eg we now get, on the Python side:

    import js

    print(js.Object == js.Object)

that prints True.  Previously it was False.

Note that this change does not make identity work with `is`, for example
`js.Object is js.Object` is actually False.  With more work that could be
made True but for now we leave that as-is.

The behaviour with this commit matches Pyodide semantics.

Signed-off-by: Damien George <damien@micropython.org>
2025-07-31 11:40:50 +10:00
..
asyncio webassembly/asyncio: Schedule run loop when tasks are pushed to queue. 2024-06-20 00:11:54 +10:00
variants webassembly: Add JavaScript-based asyncio support. 2024-04-24 16:24:00 +10:00
api.js webassembly/api: Allow specifying the pystack size. 2024-06-20 00:26:08 +10:00
lexer_dedent.c webassembly: Add JavaScript proxying, and js and jsffi modules. 2024-03-22 13:37:47 +11:00
lexer_dedent.h webassembly: Add JavaScript proxying, and js and jsffi modules. 2024-03-22 13:37:47 +11:00
library.h webassembly: Enable time localtime, gmtime, time, time_ns. 2024-03-22 13:05:54 +11:00
library.js webassembly/library: Fix formatting and style for Biome. 2024-03-22 14:31:25 +11:00
main.c all: Rename the "NORETURN" macro to "MP_NORETURN". 2025-05-13 10:36:47 +10:00
Makefile tests/run-tests.py: Change --target/--device options to --test-instance. 2024-11-04 12:47:47 +11:00
modjs.c webassembly/objjsproxy: Fix binding of self to JavaScript methods. 2025-07-24 14:38:08 +10:00
modjsffi.c webassembly/asyncio: Support top-level await of asyncio Task and Event. 2024-06-18 22:23:16 +10:00
modtime.c webassembly: Enable time localtime, gmtime, time, time_ns. 2024-03-22 13:05:54 +11:00
mpconfigport.h webassembly/objjsproxy: Implement equality for JsProxy objects. 2025-07-31 11:40:03 +10:00
mphalport.c ports: Include py/mphal.h instead of mphalport.h. 2024-10-09 14:39:34 +11:00
mphalport.h webassembly: Enable time localtime, gmtime, time, time_ns. 2024-03-22 13:05:54 +11:00
node_run.sh javascript: Rename this port to 'webassembly'. 2022-08-22 12:03:39 +01:00
objjsproxy.c webassembly/proxy_js: Reuse JsProxy ref if object matches. 2025-07-31 11:40:50 +10:00
objpyproxy.js webassembly/objpyproxy: Avoid throwing on implicit symbols access. 2025-07-17 14:37:53 +10:00
proxy_c.c webassembly/proxy_c: Provide constants for fixed JsProxy refs. 2025-07-24 14:37:30 +10:00
proxy_c.h webassembly/objjsproxy: Fix binding of self to JavaScript methods. 2025-07-24 14:38:08 +10:00
proxy_js.js webassembly/proxy_js: Reuse JsProxy ref if object matches. 2025-07-31 11:40:50 +10:00
qstrdefsport.h webassembly/asyncio: Support top-level await of asyncio Task and Event. 2024-06-18 22:23:16 +10:00
README.md webassembly/proxy_c: Return undefined if dict lookup failed on JS side. 2024-05-16 12:49:42 +10:00

MicroPython WebAssembly

MicroPython for WebAssembly.

Dependencies

Building the webassembly port bears the same requirements as the standard MicroPython ports with the addition of Emscripten, and optionally terser for the minified file.

The output includes micropython.mjs (a JavaScript wrapper for the MicroPython runtime) and micropython.wasm (actual MicroPython compiled to WASM).

Build instructions

In order to build micropython.mjs, run:

$ make

To generate the minified file micropython.min.mjs, run:

$ make min

Running with Node.js

Access the repl with:

$ make repl

This is the same as running:

$ node build-standard/micropython.mjs

The initial MicroPython GC heap size may be modified using:

$ node build-standard/micropython.mjs -X heapsize=64k

Where stack size may be represented in bytes, or have a k or m suffix.

MicroPython scripts may be executed using:

$ node build-standard/micropython.mjs hello.py

Alternatively micropython.mjs may by accessed by other JavaScript programs in node using the require command and the general API outlined below. For example:

const mp_mjs = await import("micropython.mjs");
const mp = await mp_mjs.loadMicroPython();

mp.runPython("print('hello world')");

Or without await notation:

import("micropython.mjs").then((mp_mjs) => {
    mp_mjs.loadMicroPython().then((mp) => {
        mp.runPython("print('hello world')");
    });
});

Running with HTML

The following code demonstrates the simplest way to load micropython.mjs in a browser, create an interpreter context, and run some Python code:

<!doctype html>
<html>
  <head>
    <script src="build-standard/micropython.mjs" type="module"></script>
  </head>
  <body>
    <script type="module">
      const mp = await loadMicroPython();
      mp.runPython("print('hello world')");
    </script>
  </body>
</html>

The output in the above example will go to the JavaScript console. It's possible to instead capture the output and print it somewhere else, for example in an HTML element. The following example shows how to do this, and also demonstrates the use of top-level await and the js module:

<!doctype html>
<html>
  <head>
    <script src="build-standard/micropython.mjs" type="module"></script>
  </head>
  <body>
    <pre id="micropython-stdout"></pre>
    <script type="module">
      const stdoutWriter = (line) => {
        document.getElementById("micropython-stdout").innerText += line + "\n";
      };
      const mp = await loadMicroPython({stdout:stdoutWriter});
      await mp.runPythonAsync(`
        import js
        url = "https://api.github.com/users/micropython"
        print(f"fetching {url}...")
        res = await js.fetch(url)
        json = await res.json()
        for i in dir(json):
          print(f"{i}: {json[i]}")
      `);
    </script>
  </body>
</html>

MicroPython code execution will suspend the browser so be sure to atomize usage within this environment. Unfortunately interrupts have not been implemented for the browser.

Testing

Run the test suite using:

$ make test

API

The following functions have been exposed to JavaScript through the interpreter context, created and returned by loadMicroPython().

  • PyProxy: the type of the object that proxies Python objects.

  • FS: the Emscripten filesystem object.

  • globals: an object exposing the globals from the Python __main__ module, with methods get(key), set(key, value) and delete(key).

  • registerJsModule(name, module): register a JavaScript object as importable from Python with the given name.

  • pyimport: import a Python module and return it.

  • runPython(code): execute Python code and return the result.

  • runPythonAsync(code): execute Python code and return the result, allowing for top-level await expressions (this call must be await'ed on the JavaScript side).

  • replInit(): initialise the REPL.

  • replProcessChar(chr): process an incoming character at the REPL.

  • replProcessCharWithAsyncify(chr): process an incoming character at the REPL, for use when ASYNCIFY is enabled.

Type conversions

Read-only objects (booleanns, numbers, strings, etc) are converted when passed between Python and JavaScript. The conversions are:

  • JavaScript null converts to/from Python None.
  • JavaScript undefined converts to/from Python js.undefined.

The conversion between null and None matches the behaviour of the Python json module.

Proxying

A Python dict instance is proxied such that:

for (const key in dict) {
    print(key, dict[key]);
}

works as expected on the JavaScript side and iterates through the keys of the Python dict. Furthermore, when JavaScript accesses a key that does not exist in the Python dict, the JavaScript code receives undefined instead of a KeyError exception being raised.