This subtree builds the loro-crdt JS/WASM package. It contains the Rust
#[wasm_bindgen] bindings in src/lib.rs, the JS/TS wrapper in index.ts,
Rollup/build scripts, package exports, and WASM-specific tests.
Run commands from the repository root unless noted.
- Build/test release package:
pnpm release-wasm. - Local dev package build:
pnpm -C crates/loro-wasm build-dev. - Package-local release build:
pnpm -C crates/loro-wasm build-release. - Package-local tests after an existing build:
pnpm -C crates/loro-wasm test. - Fast bundler smoke tests after entrypoint/export changes:
pnpm test-bundlers. - Browser runtime smoke tests for packaging changes:
pnpm --dir examples/bundler-smoke-tests run test:browser.
pnpm release-wasm runs version sync, installs this package's dependencies, and
builds the release artifacts. Use it for final validation when changing
src/lib.rs, index.ts, package exports, Rollup config, or build scripts.
Subscription callbacks (subscribe*, container subscribe, and related APIs)
do not call user JS immediately. Rust queues JS calls into a global pending queue
and schedules a microtask check. If the microtask runs before
callPendingEvents() flushes the queue, the package logs:
[LORO_INTERNAL_ERROR] Event not called
Any WASM-exposed API that can enqueue subscription events must flush pending
events before returning control to JS. This is intentionally implemented as a
small JS-side allowlist in index.ts rather than wrapping every method.
When adding or changing a #[wasm_bindgen] API in src/lib.rs, check whether it
can:
- mutate document or container state,
- trigger an implicit commit or barrier (
commit,with_barrier,implicit_commit_then_stop), - emit events,
- apply diffs (
revertTo,applyDiff), or - change ephemeral store state that has JS subscribers.
If yes, add the JS method name to the relevant installed decorateMethods(...)
allowlist near the bottom of index.ts. Today those wrappers cover
LoroDoc.prototype, EphemeralStoreWasm.prototype, and UndoManager.prototype;
add another prototype only when the wrapper is wired there. Pure read/query APIs
should not be decorated.
A quick behavioral check is to run with an active doc.subscribe(...) or
container subscribe(...) and confirm the mutation does not produce the internal
error above. Keep or add a regression test when the issue is observable from JS.
Never let Rust panics escape to JS: on wasm32-unknown-unknown a panic traps as
an unreadable RuntimeError: unreachable executed and leaves the instance corrupt.
Validate arguments and return JsResult errors instead of unwrap/expect/
unreachable!() on any path reachable from JS input. handler_to_js_value
returns JsResult for this reason: unknown containers (written by newer Loro
versions) must surface a readable error, matching getContainerById.
When a panic or OOM does trap, the full message and stack are stored on
globalThis.__LORO_WASM_LAST_PANIC__ and printed to console.error. Mechanism
and pitfalls (the __wbindgen_start glue invariant, why OOM uses a global
allocator wrapper, stack-frame limits): see
context/wasm-error-reporting.md.
Container id wrapper identity, lazy-cache lifetime, and the focused benchmark
are documented in
context/wasm-container-id-cache.md.
- Preserve the public
loro-crdtAPI names and package export paths used by tests and docs. - Do not hand-edit generated package output. Regenerate with
build-dev,build-release, orpnpm release-wasm. - Package entrypoint changes must consider
bundler,browser,nodejs,web, andbase64outputs. - Vite and Webpack can emit the
.wasmasset fromnew URL(...); plain esbuild and Rollup need either thebase64entry or an explicit asset copy. Keep the bundler smoke tests aligned with these expectations. - If package output or published behavior changes, add a changeset.