Migrating from Native Builds#

If you already build native wheels with python -m build and want to add Pyodide/WebAssembly support, this guide shows what to change and what to watch out for.

The short version#

For many packages, the migration is just:

pip install pyodide-build
pyodide build .

Your existing pyproject.toml, setup.py, CMakeLists.txt, or meson.build works as-is — pyodide-build handles the cross-compilation transparently. If the build succeeds, you’re done.

This page covers what to do when it doesn’t.

What works differently in WebAssembly#

WebAssembly (via Emscripten) is a different platform with different capabilities than Linux, macOS, or Windows. Some things your code may rely on are not available:

No threading#

pthread, std::thread, Python’s threading, and multiprocessing are not available. Code that uses them will fail at runtime if not handled properly.

You need to guard threaded code paths like this:

import sys

if sys.platform != "emscripten":
    import threading
    # threaded implementation
else:
    # single-threaded fallback

No networking#

socket, http.client, urllib.request (with network I/O), and similar modules don’t work. Network access in Pyodide goes through the browser’s fetch API.

Fix — guard network code or provide alternative implementations:

import sys

if sys.platform == "emscripten":
    from pyodide.http import pyfetch
    # use pyfetch for HTTP
else:
    import urllib.request
    # standard networking

Note

Third party networking libraries such as requests, aiohttp, urllib3, httpx has a Pyodide-specific code path that uses pyodide.http.pyfetch or similar alternatives.

Therefore, if you are using these libraries, your code should work for common use cases.

No subprocesses#

subprocess, os.system, os.popen, and related calls don’t work in WebAssembly.

Fix — skip or mock subprocess-dependent functionality on Emscripten.

32-bit integers#

WebAssembly is a 32-bit platform. Code that assumes 64-bit pointer sizes or uses pointer-to-int casts may behave differently.

Detecting Pyodide at build time#

pyodide-build sets the PYODIDE environment variable during the build. Use it to conditionally adjust your build configuration:

# setup.py or build script
import os

if os.environ.get("PYODIDE"):
    # WebAssembly-specific build adjustments
    ...

For C/C++ code, use the Emscripten preprocessor macro:

#ifdef __EMSCRIPTEN__
// WebAssembly-specific code
#else
// Native code
#endif

Detecting Pyodide at runtime#

import sys

if sys.platform == "emscripten":
    # Running on Pyodide
    ...

Common migration patterns#

Skipping unsupported tests#

Mark tests that require threads, networking, subprocesses, or platform-specific behavior:

import sys
import pytest

@pytest.mark.skipif(sys.platform == "emscripten", reason="No threads on Emscripten")
def test_concurrent_access():
    ...

@pytest.mark.skipif(sys.platform == "emscripten", reason="No sockets on Emscripten")
def test_http_client():
    ...

Adding Pyodide to your CI#

Once your build works locally, add it to CI alongside your native builds:

What’s next?#