Skip to content

Consider using -sSINGLE_FILE=1 to simplify the WASM build plumbing #285

Description

@tameware

Per Cursor:

Here’s what adopting -sSINGLE_FILE=1 would look like in this repo — a sketch, not a patch.

Build

Add the flag next to the other MVP linkopts, and drop the separate .wasm from declared outputs (or keep it knowing Bazel may emit an empty placeholder):

# web/BUILD.bazel
WASM_MVP_LINKOPTS = WASM_LINKOPTS + [
    "-sMODULARIZE=1",
    "-sEXPORT_NAME=createDdsModule",
    "-sSINGLE_FILE=1",  # wasm base64-inlined into the .js
    # ...existing EXPORTED_* / ENVIRONMENT...
]

wasm_cc_binary(
    name = "dds_mvp_wasm",
    cc_target = ":dds_mvp_wasm_cc",
    threads = "emscripten",
    outputs = [
        "dds_mvp_wasm.js",
        # no separate .wasm when SINGLE_FILE works as expected
    ],
)

Emscripten then emits one .js with the wasm embedded (base64 data URL). Your custom gen_wasm_bin_js.py becomes redundant for the same job.

Load path (the real cleanup)

Today:

dds_mvp_wasm_bin.js  →  ddsMvpWasmBytes()
dds_mvp_wasm.js      →  createDdsModule({ wasmBinary: ... })

With SINGLE_FILE:

<!-- dds_mvp.html: drop bin.js -->
<script src="dds_mvp_wasm.js"></script>
<script src="dds_mvp.js"></script>
// dds_mvp.js — createDdsModule() with no wasmBinary
ddsModulePromise = createDdsModule({ /* defaults load inlined wasm */ })

Remove the ddsMvpWasmBytes guard.

Scripts / staging

Piece Change
update_wasm.sh Copy only .js; drop gen_wasm_bin_js.py and .wasm copy; keep surgical patch_mvp_wasm.py
clean_wasm.sh / .gitignore Stop tracking dds_mvp_wasm.wasm / *_bin.js
mvp_site.py Stage + patch .js only; delete bin.js generation
verify_wasm_js.py Either decode the inlined base64 from the .js, or drop / rewrite verification
dds_mvp_wasm_node.mjs createDdsModule() with no wasmBinary / no WASM_FILE arg
System / e2e tests Drop ddsMvpWasmBytes checks and wasm path wiring

patch_mvp_wasm.py stays — it already assumes SINGLE_FILE and only rewrites the one isFileURI match (global replace would corrupt the embedded bytes).

What does not go away

  • Pthreads / COOP+COEP — still required; serve_mvp.py unchanged.
  • MODULARIZE + createDdsModule — still the app contract.
  • Worker bootstrap — with -pthread, Emscripten may still emit a separate worker helper; don’t assume a single file covers every artifact.
  • Bazel quirkwasm_cc_binary historically materializes empty siblings for undeclared optional outputs; validate that dropping "….wasm" from outputs doesn’t break the rule, or leave a dummy and ignore it.

Size / DX tradeoff

You trade three artifacts + a Python embed step for one larger .js (wasm × ~4/3 from base64). Cache invalidation becomes coarser (any C++ change busts the whole glue file). Functionally it’s the same idea you already have with dds_mvp_wasm_bin.js, just done by the linker.

Net: mostly deleting the bin.js pipeline and simplifying load/tests; the build flag itself is a one-liner. Worth doing only if you want less custom plumbing — not for new capability you don’t already have via base64 embed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions