moxzi
Docs / Browser / Installing moxzi-web

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#

SpecifierContentsWhen you need it
moxzi-webMoxzi, ActorError, idFromName, fetchMopsClosureAlways
moxzi-web/runtimeThe wasm-bindgen glue (moxzi_web.js + moxzi_web_bg.wasm)Page mode only — pass it as glue
moxzi-web/storageindexedDbStore, memoryStorePersistence across reloads
moxzi-web/workerThe worker entry pointOnly to ship it yourself as a raw asset
moxzi-web/devtoolsattachDevtoolsThe 🔎 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:

ArrangementWhat you doWhat the bundler must do
Bundled worker (default)Nothing — import Moxzi and start with worker: trueRecognise the literal new Worker(new URL(…, import.meta.url)) and emit the worker as its own chunk
Raw assetCopy moxzi-web/worker and the glue into your static output, pass workerUrl to Moxzi.startNothing; 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#

Next#

On this pageInstallPackage exportsThe two ways to startThe bundler ruleNo-bundler pagesHeaders for HTTPS outcallsBuilding it from this repoThings you cannot doNext