Collaborative editing

Fresh — современный фреймворк для разработки веб-приложений на JavaScript и Deno, ориентированный на высокую производительность и минимальное использование клиентского JavaScript. Одной из продвинутых задач в Fresh является реализация collaborative editing — функционала, позволяющего нескольким пользователям одновременно редактировать один и тот же документ или данные в реальном времени.

Архитектура совместного редактирования

Совместное редактирование в Fresh строится на сочетании серверного рендеринга, WebSocket соединений и оптимистичного обновления состояния. Основные компоненты:

  • Серверная логика — обработка входящих изменений от клиентов и синхронизация состояния.
  • WebSocket-сервер — канал для мгновенной передачи изменений между всеми участниками.
  • Клиентский компонент — интерфейс для редактирования и локального отображения изменений, реагирующий на обновления сервера.

Fresh использует стандарт Deno для работы с WebSocket, что обеспечивает высокую производительность и простоту интеграции.

Настройка WebSocket в Fresh

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

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 это позволяет почти мгновенно видеть результат своих действий и действий других участников без заметных задержек.

Масштабирование и производительность

  • Разделение каналов WebSocket по документам снижает нагрузку на сервер.
  • Сжатие сообщений и отправка только диффов (изменений) уменьшает трафик.
  • Периодическая сериализация состояния документа позволяет новым клиентам быстро подтягивать актуальные данные.

Использование Fresh для collaborative editing обеспечивает минимальный клиентский JavaScript, быстрый серверный рендеринг и простую интеграцию с современными CRDT-библиотеками, создавая удобные и высокопроизводительные приложения для совместной работы.