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 quirk —
wasm_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.
Per Cursor:
Here’s what adopting
-sSINGLE_FILE=1would look like in this repo — a sketch, not a patch.Build
Add the flag next to the other MVP linkopts, and drop the separate
.wasmfrom declared outputs (or keep it knowing Bazel may emit an empty placeholder):Emscripten then emits one
.jswith the wasm embedded (base64 data URL). Your customgen_wasm_bin_js.pybecomes redundant for the same job.Load path (the real cleanup)
Today:
With
SINGLE_FILE:Remove the
ddsMvpWasmBytesguard.Scripts / staging
update_wasm.sh.js; dropgen_wasm_bin_js.pyand.wasmcopy; keep surgicalpatch_mvp_wasm.pyclean_wasm.sh/.gitignoredds_mvp_wasm.wasm/*_bin.jsmvp_site.py.jsonly; delete bin.js generationverify_wasm_js.py.js, or drop / rewrite verificationdds_mvp_wasm_node.mjscreateDdsModule()with nowasmBinary/ no WASM_FILE argddsMvpWasmByteschecks and wasm path wiringpatch_mvp_wasm.pystays — it already assumes SINGLE_FILE and only rewrites the oneisFileURImatch (global replace would corrupt the embedded bytes).What does not go away
serve_mvp.pyunchanged.MODULARIZE+createDdsModule— still the app contract.-pthread, Emscripten may still emit a separate worker helper; don’t assume a single file covers every artifact.wasm_cc_binaryhistorically materializes empty siblings for undeclared optional outputs; validate that dropping"….wasm"fromoutputsdoesn’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 withdds_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.