- Регистрация
- 1 Мар 2015
- Сообщения
- 1,481
- Баллы
- 155
Introduction
A demo of a real-time, collaborative multi-page web app built with Phoenix LiveView.
It is designed for offline-first ready; it is packaged as a PWA and uses CRDTs or local state and reactive components.
Offline first solutions naturally offloads most of the reactive UI logic to JavaScript.
When online, we use LiveView "hooks", while when offline, we render the reactive components.
It uses Vite as the bundler.
GitHub:
We can check it deployed: or
Context
We want to experiment PWA webapps using Phoenix LiveView.
We building a simple two pages webapp:
Tech overview
LiveStock page
You have both CRDT-based synchronization (for convergence) and server-enforced business rules (for consistency).
We thus have two Layers of Authority:
When the client code is updated, we get notified:
Once we accept the update, the new code is active:
Build tool: Vite
Since we need to setup a Service Worker, we used Vite and the plugin VitePWA.
We let Vite bundle all the code and can remove safely esbuild and tailwindcss. They are now part of Vite.
Store managers
For the LiveStock page, we used Yjs client-side and server-side.
For the LiveFlight page, we use Valtio as we didn't design the state of this page to survive a network disruption.
A demo of a real-time, collaborative multi-page web app built with Phoenix LiveView.
It is designed for offline-first ready; it is packaged as a PWA and uses CRDTs or local state and reactive components.
Offline first solutions naturally offloads most of the reactive UI logic to JavaScript.
When online, we use LiveView "hooks", while when offline, we render the reactive components.
It uses Vite as the bundler.
GitHub:
We can check it deployed: or
Context
We want to experiment PWA webapps using Phoenix LiveView.
We building a simple two pages webapp:
- LiveStock. On the first page, we mimic a shopping cart where users can pick items until stock is depleted, at which point the stock is replenished. Every user will see and can interact with this counter
- LiveFlight. On the second page, we propose an interactive map with a form with two inputs where two users can edit collaboratively a form to display markers on the map and then draw a great circle between the two points.
Tech overview
| Component | Role |
|---|---|
| Vite | Build and bundling framework |
| SQLite | Embedded persistent storage of latest Yjs document |
| Phoenix LiveView | UI rendering, incuding hooks |
| PubSub / Phoenix.Channel | Broadcast/notifies other clients of updates / conveys CRDTs binaries on a separate websocket (from te LiveSocket) |
| Yjs / Y.Map | Holds the CRDT state client-side (shared) |
| Valtio | Holds local ephemeral state |
| y-indexeddb | Persists state locally for offline mode |
| SolidJS | renders reactive UI using signals, driven by Yjs observers |
| Hooks | Injects communication primitives and controls JavaScript code |
| Service Worker / Cache API | Enable offline UI rendering and navigation by caching HTML pages and static assets |
| Leaflet | Map rendering |
| MapTiler | enable vector tiles |
| WebAssembly container | high-performance calculations for map "great-circle" routes use Zig code compiled to WASM |
You have both CRDT-based synchronization (for convergence) and server-enforced business rules (for consistency).
We thus have two Layers of Authority:
CRDT Sync Layer (Collaborative):
Clients and server synchronize using Yjs CRDTs to merge concurrent edits deterministically.
Clients can modify their local Y-Doc freely (offline or online).
Business Rules Layer (Authoritative):
The server is authoritative. It validates updates upon the business logic (e.g., stock validation), and broadcasts the canonical state to all clients.
Clients propose changes, but the server decides the final state (e.g., enforcing stock limits).
Offline capabilities:
Edits are saved to y-indexeddb and sent later.
Synchronization Flow:
Client sends all pending Yjs updates on (re)connection.
The client updates his local Y-Doc with the server responses.
Y-Doc mutations trigger UI rendering, and reciprocally, UI modifications update the Y-Doc and propagate mutations to the server.
Server Processing:
Merges updates into the SQLite3-stored Y-Doc (using y_ex).
Applies business rules (e.g., "stock cannot be negative").Broadcasts the approved state.
Clients reconcile local state with the server's authoritative version
Data Transport:
- for the LiveStock page: Use a Phoenix.Channel to transmit the Y-Doc state as binary. This minimises the bandwidth usage and decouples CRDT synchronisation from the LiveSocket. The implementation heavily inspired by the repo made by the author of y_ex.
- for the LiveFlight: use LiveSocket as we broadcast a limited amount of data.
Component Rendering Strategy:
- online: use LiveView hooks
- offline: hydrate the cached HTML documents with reactive JavaScript components
When the client code is updated, we get notified:
Once we accept the update, the new code is active:
Build tool: Vite
Since we need to setup a Service Worker, we used Vite and the plugin VitePWA.
We let Vite bundle all the code and can remove safely esbuild and tailwindcss. They are now part of Vite.
Store managers
For the LiveStock page, we used Yjs client-side and server-side.
For the LiveFlight page, we use Valtio as we didn't design the state of this page to survive a network disruption.