Installing moxzi-webalpha
How do I add it to an app, and what do bundlers require?
moxzi-web is an ESM package containing the runtime wasm, its wasm-bindgen glue, the client library, a worker entry point and an optional devtools panel. The constraint to read before anything else: the worker is detected by bundlers syntactically, so the one call that constructs it must survive your build untouched, or worker mode fails silently at runtime.
Install#
npm install moxzi-web @dfinity/candid @dfinity/principal
@dfinity/candid and @dfinity/principal are peerDependencies (>=2.0.0) and are used unmodified — the argument bytes your page sends are the bytes an agent would send.
Package exports#
| Specifier | Contents | When you need it |
|---|---|---|
moxzi-web | Moxzi, ActorError, idFromName, fetchMopsClosure | Always |
moxzi-web/runtime | The wasm-bindgen glue (moxzi_web.js + moxzi_web_bg.wasm) | Page mode only — pass it as glue |
moxzi-web/storage | indexedDbStore, memoryStore | Persistence across reloads |
moxzi-web/worker | The worker entry point | Only to ship it yourself as a raw asset |
moxzi-web/devtools | attachDevtools | The 🔎 inspector panel |
The two ways to start#
import * as glue from 'moxzi-web/runtime';
import { Moxzi } from 'moxzi-web';
import { indexedDbStore } from 'moxzi-web/storage';
import { idlFactory } from './declarations/greeter/greeter.did.js'; // what dfx generate wrote
const moxzi = await Moxzi.start({ glue, store: await indexedDbStore('my-app'), autosave: true });
const greeter = await moxzi.install({ name: 'greeter', wasm: '/greeter.wasm', idlFactory });
await greeter.greet('world'); // "Hello, world!"
Worker mode changes the start call and nothing else:
const moxzi = await Moxzi.start({ worker: true, deadlineMs: 5000, http: true });
glue is page-mode only — a worker imports its own copy of the runtime. Asking for deadlineMs or http without worker: true throws at start (and fails to type-check in TypeScript), because a page cannot interrupt a running wasm call and may not call Atomics.wait.
The bundler rule#
The library constructs its worker in exactly this form, in one expression:
new Worker(new URL('./moxzi-worker.js', import.meta.url), { type: 'module' })
Bundlers detect workers syntactically. Assign that URL to a variable first, compute it, or wrap it in a helper, and Vite's worker plugin no longer recognises it: the file is copied as a raw asset, its own import './moxzi_web.js' then 404s at runtime, and the failure arrives as a silent hang inside a worker that no page error handler can observe. This was found by building a consumer with Vite and reading the request log, which is why the npm verification step builds one.
There are therefore exactly two supported arrangements:
| Arrangement | What you do | What the bundler must do |
|---|---|---|
| Bundled worker (default) | Nothing — import Moxzi and start with worker: true | Recognise the literal new Worker(new URL(…, import.meta.url)) and emit the worker as its own chunk |
| Raw asset | Copy moxzi-web/worker and the glue into your static output, pass workerUrl to Moxzi.start | Nothing; the file is served as-is |
If you keep the default and your build rewrites that expression, the fix is not configuration — it is switching to the raw-asset arrangement and passing workerUrl explicitly.
Beyond that, one setting: build.target: 'esnext' for Vite/webpack. A target older than that errors on the worker chunk; nothing else needs configuring.
No-bundler pages#
Served as plain files, new URL('./moxzi-worker.js', import.meta.url) resolves relative to the library module, which is also correct — so the same code works without a build step. One thing does not: @dfinity/candid and @dfinity/principal publish ESM with extensionless relative specifiers, which a browser will not resolve. Every IC frontend goes through a bundler for this same reason. Pre-bundle just those two into one file:
cat > dfinity-entry.js <<'JS'
export { IDL } from '@dfinity/candid';
export { Principal } from '@dfinity/principal';
JS
npx esbuild ./dfinity-entry.js --bundle --format=esm --outfile=dfinity.js
then point an import map at it, exactly as the shipped demos do:
<script type="importmap">
{ "imports": { "@dfinity/candid": "./dfinity.js", "@dfinity/principal": "./dfinity.js" } }
</script>
That is the arrangement scripts/web_mops_gate.sh and site/demos/moxdb.html use.
Headers for HTTPS outcalls#
http: true needs SharedArrayBuffer, which needs cross-origin isolation. Serve the page with both of:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Without them Moxzi.start refuses at once with a message naming the headers, rather than letting the failure surface three frames away as "SharedArrayBuffer is not defined". Note that require-corp also constrains every other resource the page loads.
Building it from this repo#
The npm package is assembled by scripts/build_npm.sh: it builds moxzi-web for wasm32-unknown-unknown, runs wasm-bindgen --target web into moxzi/web/pkg, copies the library files (moxzi.js, moxzi-worker.js, storage.js, devtools.js) alongside, and packs into dist/.
bash scripts/build_npm.sh
It skips cleanly if the wasm-bindgen CLI is absent. For a page served straight from a checkout, bash scripts/site_serve.sh does the same build and serves it.
Things you cannot do#
- Use
@dfinity/agent'sActor. It pollsreadStatefor a certificate; there is none. The generatedidlFactoryis the supported surface — see /docs/web/candid/. - Run worker mode from a
file://page. Module workers and cross-origin isolation both require an HTTP origin. - Ask for
deadlineMsorhttpin page mode. Both throw atstart, by design.
Next#
- /docs/web/api/ — the full
Moxzisurface and the page/worker differences. - /docs/web/candid/ — encoding arguments and decoding replies without losing field names.
- /docs/web/limits/ — what a tab cannot do at all.