Fresh — современный фреймворк для разработки веб-приложений на JavaScript и Deno, ориентированный на высокую производительность и минимальное использование клиентского JavaScript. Одной из продвинутых задач в Fresh является реализация collaborative editing — функционала, позволяющего нескольким пользователям одновременно редактировать один и тот же документ или данные в реальном времени.
Совместное редактирование в Fresh строится на сочетании серверного рендеринга, WebSocket соединений и оптимистичного обновления состояния. Основные компоненты:
Fresh использует стандарт Deno для работы с WebSocket, что обеспечивает высокую производительность и простоту интеграции.
WebSocket в Fresh подключается через deno.land/std/ws
или встроенные API Deno. Пример базовой конфигурации:
import { serve } from "https://deno.land/std@0.203.0/http/server.ts";
import { acceptWebSocket, isWebSocketCloseEvent } from "https://deno.land/std@0.203.0/ws/mod.ts";
const clients = new Map();
serve(async (req) => {
const { socket, response } = Deno.upgradeWebSocket(req);
socket.ono pen = () => clients.set(socket, {});
socket.onmess age = (event) => {
const data = JSON.parse(event.data);
broadcast(data, socket);
};
socket.oncl ose = () => clients.delete(socket);
return response;
});
function broadcast(data, origin) {
for (const client of clients.keys()) {
if (client !== origin) {
client.send(JSON.stringify(data));
}
}
}
В этом примере сервер принимает подключения WebSocket и рассылает изменения всем подключенным клиентам, кроме источника изменений.
Для обеспечения консистентности данных между клиентами используют CRDT (Conflict-free Replicated Data Types) или Operational Transformation (OT). В Fresh можно интегрировать сторонние библиотеки, например, Yjs или Automerge, для работы с этими структурами.
Пример интеграции Yjs с Fresh:
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('ws://localhost:1234', 'my-document', ydoc);
const ytext = ydoc.getText('editor');
ytext.observe(event => {
console.log('Document changed:', ytext.toString());
});
Такой подход позволяет автоматически синхронизировать изменения между всеми подключенными клиентами и корректно разрешать конфликты.
Fresh использует islands architecture, где отдельные интерактивные компоненты могут быть подключены к статическому HTML. Для совместного редактирования это означает, что редактор можно реализовать как island:
/** @jsx h */
import { h } from "preact";
import { useEffect, useState } from "preact/hooks";
export default function CollaborativeEditor({ ytext }) {
const [content, setContent] = useState(ytext.toString());
useEffect(() => {
const observer = () => setContent(ytext.toString());
ytext.observe(observer);
return () => ytext.unobserve(observer);
}, []);
const handleInput = (e) => {
ytext.delete(0, ytext.length);
ytext.insert(0, e.target.value);
};
return (
<textarea value={content} onIn put={handleInput}></textarea>
);
}
Каждое изменение пользователя обновляет состояние Yjs, которое в реальном времени синхронизируется через WebSocket с сервером и другими клиентами.
Для улучшения UX часто используют optimistic UI updates: изменения отображаются локально до подтверждения сервера. В сочетании с CRDT это позволяет почти мгновенно видеть результат своих действий и действий других участников без заметных задержек.
Использование Fresh для collaborative editing обеспечивает минимальный клиентский JavaScript, быстрый серверный рендеринг и простую интеграцию с современными CRDT-библиотеками, создавая удобные и высокопроизводительные приложения для совместной работы.