From b015fbccbbb2a44991b70e8a393ffe34aaf00b87 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:20:37 +0800 Subject: [PATCH 1/2] docs: update Vite setup for native WebAssembly support --- pages/docs/tutorial/get_started.mdx | 86 ++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 7 deletions(-) diff --git a/pages/docs/tutorial/get_started.mdx b/pages/docs/tutorial/get_started.mdx index 6bace6b..d87effa 100644 --- a/pages/docs/tutorial/get_started.mdx +++ b/pages/docs/tutorial/get_started.mdx @@ -35,17 +35,74 @@ pnpm install loro-crdt yarn add loro-crdt ``` -If you're using `Vite`, you should add the following to your vite.config.ts: +### Vite + +#### Vite 8.1 and newer + +[Vite 8.1 introduced built-in WebAssembly ESM integration](https://vite.dev/blog/announcing-vite8-1#wasm-esm-integration-support), +so you do not need `vite-plugin-wasm`. Keep `loro-crdt` out of dependency +pre-bundling so its JavaScript bindings and WASM imports use the same module +graph during development. Add this to your `vite.config.ts`, keeping any +framework plugins you already use: + +```ts no-run +import { defineConfig } from "vite"; + +export default defineConfig({ + optimizeDeps: { + exclude: ["loro-crdt"], + }, +}); +``` + +Vite's native WASM loader uses top-level `await` (TLA). The default Vite 8.1 +browser targets support TLA, so `vite-plugin-top-level-await` is not needed +either. If you customize `build.target`, keep it compatible with TLA; changing +the target does not add TLA support to older browsers. See +[Vite's WebAssembly guide](https://vite.dev/guide/features#webassembly) and +[browser compatibility guide](https://vite.dev/guide/build#browser-compatibility). + +You can then import and use Loro normally: + +```ts twoslash +import { LoroDoc } from "loro-crdt"; + +const doc = new LoroDoc(); +doc.getText("text").insert(0, "Hello, Loro!"); +``` + +#### Older Vite versions + +For Vite versions before 8.1, install `vite-plugin-wasm`: + +```bash +npm install -D vite-plugin-wasm +``` + +For browsers with native TLA support, configure a compatible build target and +add the plugin alongside your existing framework plugins: ```ts no-run +import { defineConfig } from "vite"; import wasm from "vite-plugin-wasm"; -import topLevelAwait from "vite-plugin-top-level-await"; export default defineConfig({ - plugins: [...otherConfigures, wasm(), topLevelAwait()], + plugins: [wasm()], + optimizeDeps: { + exclude: ["loro-crdt"], + }, + build: { + target: "es2022", + }, }); ``` +`es2022` is an example target for modern browsers, not a compatibility polyfill. +For older browsers, the plugin also documents a +[`vite-plugin-top-level-await` option](https://github.com/Menci/vite-plugin-wasm#usage). +Check compatibility with your Vite version and test the production output before +using that option; it is not required for every Vite project. +
⚠️ DOMContentLoaded Timing Issue with Vite @@ -62,7 +119,10 @@ document.addEventListener("DOMContentLoaded", () => { }); ``` -**Reason:** This occurs because Vite loads ES modules asynchronously, and the WASM module initialization within `loro-crdt` also happens asynchronously. When you import at the top level but execute code inside `DOMContentLoaded`, the WASM module may not be fully initialized when the event fires, causing the application to fail silently. +**Reason:** WASM initialization can delay module evaluation until after +`DOMContentLoaded` has already fired. The listener is then registered too late +and never runs. Static imports wait for their asynchronous dependencies before +the importing module executes, so Loro is initialized when your module code runs. **Solutions:** @@ -75,19 +135,29 @@ document.addEventListener("DOMContentLoaded", () => { // Your code here... ``` -2. **Use dynamic import inside the event listener**: +2. **Check whether the DOM is already ready before registering a listener**: ```ts no_run twoslash - document.addEventListener("DOMContentLoaded", async () => { + async function start() { const { LoroDoc } = await import("loro-crdt"); const doc = new LoroDoc(); // Your code here... - }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => void start(), { + once: true, + }); + } else { + void start(); + } ``` The dynamic import ensures the module and its WASM dependencies are fully loaded before use.
+### Next.js + If you're using `Next.js`, you should add the following to your next.config.js: ```js no-run @@ -102,6 +172,8 @@ module.exports = { }; ``` +### Browser ESM + You can also use Loro directly in the browser via ESM imports. Here's a minimal example: From 639773a8326a51d1e5369a3832a1486e8b4a1dfc Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:28:20 +0800 Subject: [PATCH 2/2] docs: focus Vite guidance on verified configurations --- pages/docs/tutorial/get_started.mdx | 76 ++++++++++------------------- 1 file changed, 26 insertions(+), 50 deletions(-) diff --git a/pages/docs/tutorial/get_started.mdx b/pages/docs/tutorial/get_started.mdx index d87effa..e1da4a6 100644 --- a/pages/docs/tutorial/get_started.mdx +++ b/pages/docs/tutorial/get_started.mdx @@ -40,10 +40,18 @@ yarn add loro-crdt #### Vite 8.1 and newer [Vite 8.1 introduced built-in WebAssembly ESM integration](https://vite.dev/blog/announcing-vite8-1#wasm-esm-integration-support), -so you do not need `vite-plugin-wasm`. Keep `loro-crdt` out of dependency -pre-bundling so its JavaScript bindings and WASM imports use the same module -graph during development. Add this to your `vite.config.ts`, keeping any -framework plugins you already use: +so `vite-plugin-wasm` is no longer needed. The default browser targets also +support top-level `await` (TLA), so `vite-plugin-top-level-await` is not needed. +You can import from `loro-crdt` directly. + +If you customize `build.target`, use browser targets that support TLA. Changing +the target does not add TLA support to older browsers. See +[Vite's WebAssembly guide](https://vite.dev/guide/features#webassembly). + +**Development workaround (Vite 8.1.0 with Loro 1.16.4):** This combination can +fail during WASM initialization with `Cannot read properties of undefined +(reading 'memory')` when Loro is pre-bundled. If you encounter this error, +exclude Loro from dependency pre-bundling: ```ts no-run import { defineConfig } from "vite"; @@ -55,53 +63,32 @@ export default defineConfig({ }); ``` -Vite's native WASM loader uses top-level `await` (TLA). The default Vite 8.1 -browser targets support TLA, so `vite-plugin-top-level-await` is not needed -either. If you customize `build.target`, keep it compatible with TLA; changing -the target does not add TLA support to older browsers. See -[Vite's WebAssembly guide](https://vite.dev/guide/features#webassembly) and -[browser compatibility guide](https://vite.dev/guide/build#browser-compatibility). - -You can then import and use Loro normally: - -```ts twoslash -import { LoroDoc } from "loro-crdt"; - -const doc = new LoroDoc(); -doc.getText("text").insert(0, "Hello, Loro!"); -``` +Merge this setting into your existing config. It affects development only; +production builds still bundle Loro normally. #### Older Vite versions -For Vite versions before 8.1, install `vite-plugin-wasm`: +For Vite versions before 8.1, install the plugins: ```bash -npm install -D vite-plugin-wasm +npm install -D vite-plugin-wasm vite-plugin-top-level-await ``` -For browsers with native TLA support, configure a compatible build target and -add the plugin alongside your existing framework plugins: +Add them alongside your existing framework plugins in `vite.config.ts`: ```ts no-run import { defineConfig } from "vite"; import wasm from "vite-plugin-wasm"; +import topLevelAwait from "vite-plugin-top-level-await"; export default defineConfig({ - plugins: [wasm()], - optimizeDeps: { - exclude: ["loro-crdt"], - }, - build: { - target: "es2022", - }, + plugins: [wasm(), topLevelAwait()], }); ``` -`es2022` is an example target for modern browsers, not a compatibility polyfill. -For older browsers, the plugin also documents a -[`vite-plugin-top-level-await` option](https://github.com/Menci/vite-plugin-wasm#usage). -Check compatibility with your Vite version and test the production output before -using that option; it is not required for every Vite project. +For targets with native TLA support, the TLA plugin can be omitted; see the +[plugin's usage guide](https://github.com/Menci/vite-plugin-wasm#usage) for target +configuration and compatibility details.
⚠️ DOMContentLoaded Timing Issue with Vite @@ -119,10 +106,7 @@ document.addEventListener("DOMContentLoaded", () => { }); ``` -**Reason:** WASM initialization can delay module evaluation until after -`DOMContentLoaded` has already fired. The listener is then registered too late -and never runs. Static imports wait for their asynchronous dependencies before -the importing module executes, so Loro is initialized when your module code runs. +**Reason:** This occurs because Vite loads ES modules asynchronously, and the WASM module initialization within `loro-crdt` also happens asynchronously. When you import at the top level but execute code inside `DOMContentLoaded`, the WASM module may not be fully initialized when the event fires, causing the application to fail silently. **Solutions:** @@ -135,21 +119,13 @@ the importing module executes, so Loro is initialized when your module code runs // Your code here... ``` -2. **Check whether the DOM is already ready before registering a listener**: +2. **Use dynamic import inside the event listener**: ```ts no_run twoslash - async function start() { + document.addEventListener("DOMContentLoaded", async () => { const { LoroDoc } = await import("loro-crdt"); const doc = new LoroDoc(); // Your code here... - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", () => void start(), { - once: true, - }); - } else { - void start(); - } + }); ``` The dynamic import ensures the module and its WASM dependencies are fully loaded before use.