Blog

Ausprobieren von LiveViewJS

Albert Brand
Arjan Molenaar

Albert Brand, Arjan Molenaar

Aktualisiert Oktober 16, 2025
11 Minuten

Vor kurzem sind wir über ein serverseitig orientiertes Web-Framework namens LiveViewJS gestolpert . Es ist inspiriert von Phoenix LiveView, einem bekannten, in Elixir geschriebenen Web-Framework. Wir(Arjan Molenaar und Albert Brand) beschlossen, es an einem unserer Innovationsnachmittage auszuprobieren.

TL;DR: Es ist noch nicht bereit für den produktiven Einsatz, aber es ist vielversprechend. Im Bereich der serverseitigen Frameworks sind viele neue Paradigmen aufgetaucht. Dies ist ein weiteres Framework, das den Schwerpunkt von SPAs auf serverseitiges Rendering verlagert.

Was also ist LiveView? Und wie verhält es sich zu LiveViewJS?

Um aus der ursprünglichen LiveView-Seite zu zitieren GitHub-Seite:

"Phoenix LiveView ermöglicht reichhaltige Benutzererlebnisse in Echtzeit mit Server-gerendertem HTML.

Um noch einmal die Seite zu zitieren, auf der beschrieben wird, wie das funktioniert:

  • LiveView-Seiten sind serverzentriert. Das LiveView-Framework synchronisiert die Clients bei Änderungen auf dem Server.
  • LiveView sendet eine Seite an den Client als 'klassisch' gerendertem HTML. Das bedeutet eine schnelle, aussagekräftige Darstellung und gute Crawling-Unterstützung für Suchmaschinen.
  • Der Client stellt eine Verbindung mit dem Server her, so dass LiveView auf Benutzerereignisse reagieren kann. Die Seite wird aktualisiert mit minimalen HTML-Unterschieden.

LiveViewJS ist eine Neuimplementierung des Phoenix LiveView-Servers in NodeJS und Deno. Damit wird eine kampferprobte Architektur einem neuen Publikum zugänglich gemacht. Wo immer möglich, werden die gleichen Definitionen und Protokolle verwendet. LiveViewJS verwendet sogar dasselbe JavaScript-Bündel wie Phoenix LiveView auf dem Client. Das bedeutet, dass die beiden Frameworks weitgehend identisch sind.

Zeigen Sie mir ein Beispiel

Sicher! Hier ist ein einfaches Beispiel, das von der LiveViewJS inspiriert ist Einführung Dokumente:

import { createLiveView, html } from "liveviewjs";

export const counterLiveView = createLiveView<
  { count: number }, // define the context (read: state) type
  { type: "increment" } // define event types
>({
  // called on initialisation
  mount: (socket) => {
    socket.assign({ count: 0 }); // initialise context
  },
  // called on user event
  handleEvent: (event, socket) => {
    const { count } = socket.context; // destructure context
    switch (event.type) {
      case "increment":
        socket.assign({ count: count + 1 }); // update context
        break;
    }
  },
  // called on context change
  render: (context) => {
    const { count } = context; // destructure context
    // return rendered context
    return html`
      <div>
        <h1>Count is: ${count}</h1>
        <button phx-click="increment">+</button>
      </div>
    `;
  },
});

Ausführlichere Erläuterungen zu den verwendeten APIs finden Sie hier. Wenn Sie dieses Beispiel ausführen, wird ein Zähler angezeigt, der sich erhöht, sobald Sie auf die Plus-Schaltfläche klicken. Aber anstatt einen expliziten REST-Endpunkt zu haben, um den serverseitigen Zustand zu ändern, behandelt LiveViewJS Ereignisse und Aktualisierungen des DOM über die Websocket-Kommunikation.

Websocket-Verbindung LiveView-Nachrichten
Websocket-Verbindung LiveView-Nachrichten in Chrome DevTools

Wie Sie in der obigen Abbildung sehen können, enthält die Websocket-Nachricht eine Antwort, die ein "1" für dynamische Position "2". Der LiveView-Clientcode kann diese Informationen verwenden, um den richtigen Teil des Dokumentbaums zu aktualisieren. In komplexeren Anwendungsfällen können auch komplette HTML-Fragmente an den Client gesendet werden.

Es gibt viele weitere Möglichkeiten außerhalb dieses einfachen Beispiels. Siehe die Anatomie eines LiveViews für weitere Informationen.

Das scheint ziemlich komplex zu sein!

Ja, aber einen Client bauen/Server Seitenanwendung mit einer RESTful- oder GraphQL-Schnittstelle ist auch kein Zuckerschlecken. LiveViews bieten eine Reihe von Vorteilen:

  • LiveView ist eine generische Client/Server-Abstraktion und erfordert viel weniger Code zum Erstellen von Seiten: kein REST oder GraphQL, kein JSON (De)serialisierung, nur HTML-Vorlagen.
  • Die Originalarchitektur von Phoenix LiveView ist kampferprobt
  • Die Synchronisierung zwischen Client und Server ist 'gelöst'
  • Echtzeit ist eingebaut. Eine Nachricht an alle verbundenen Clients senden? Keine zusätzliche Einrichtung erforderlich.
  • Schnelle erste Farbe
  • Schnelle Seitenaktualisierungen
  • Keine Client-seitige Router-Komplexität: Das wird alles auf dem Server erledigt
  • Keine clientseitigen Komponentenbibliotheken zum Erstellen von Seiten erforderlich, obwohl Sie eine Seite schrittweise erweitern können, wenn Sie dies benötigen.

Was haben wir getan?

Arjan und ich besprachen zu Beginn des Innovationsnachmittags, was wir ausprobieren könnten. Arjan hatte bereits einige Erfahrung mit Elixir und Phoenix LiveView, daher war er daran interessiert, das JS-Äquivalent zu vergleichen. Ich war vor allem an der Technologieabstraktion interessiert. Außerdem hatten wir beide keine Erfahrung mit Deno, und LiveViewJS läuft auch auf Deno, also schien das eine gute Ergänzung zu sein.

Wir wollten die Beispiele zum Laufen bringen und von diesem Ausgangspunkt aus etwas aufbauen, das die Möglichkeiten der Mehrbenutzerkommunikation zeigt. Wir dachten, dass es wahrscheinlich eine Menge Beispiele dafür geben würde. Überraschenderweise gab es aber gar nicht so viele.

Das erste, was ein wenig enttäuschend war, war die Installation. Um LiveViewJS zu verwenden, ist der offizielle Weg sehen Sie sich das Repo an und bauen darauf auf. Wir hatten gehofft, dass der Installationsprozess vereinfacht werden würde, indem man einfach mit einem leeren Ordner beginnt und eine npm install liveviewjsoder führen Sie eine Befehlszeile aus, um eine Kesselstein-Einrichtung zu erstellen.

Ein weiteres Problem, das uns aufgefallen ist: Nachdem wir das Repo ausgecheckt und npm installierenwird die Ausführung der Beispiele mit deno run [...] src/example/autorun.ts. Wir haben erwartet, dass der Start von einem npm Skript. Das von uns ausgecheckte Repository dient jedoch als Arbeitsbereich, in dem die verschiedenen npm Pakete erstellt werden, was zu Konflikten mit Skripten führt, die von Entwicklern ausgeführt werden. So wird das Navigieren, Ändern und Ausführen von Code in einem Arbeitsbereich unnötig kompliziert.

Wir haben ein wenig in den Dokumenten gelesen, die so ziemlich das sind, was Sie erwarten. Dann haben wir uns angesehen alle Beispiele und fragten uns: wo ist der obligatorische Chat Serverbeispiel? Es fehlte! Dann wussten wir, was wir bauen mussten.

Aufbau einer Chat-App

Was brauchen Sie für eine Chat-App? Wir haben uns dafür entschieden, Nachrichten zusammen mit einem Autor und einem Zeitstempel zu speichern. In diesem Beispiel werden die Daten so lange im Speicher gehalten, wie der Deno-Prozess läuft. Außerdem möchten wir, dass mehrere Benutzer miteinander chatten können (duh!) also werden wir Chat-Updates an alle senden, die mit dem LiveView verbunden sind.

type Chat = {
  author: string;
  message: string;
  timestamp: Date;
};

// in memory chat
const chatMessages: Chat[] = [
  {
    author: "Chatbot",
    message: "Welcome to the chat!",
    timestamp: new Date(),
  },
];

// use global broadcastchannel implementation
const pubSub = new BroadcastChannelPubSub();

Wir erstellen die LiveView mit den Chatnachrichten als Kontext, einer senden Client-Ereignis und einem aktualisierten Info-Ereignis definiert:

export const chatLiveView = createLiveView<
  { chatMessages: Chat[] }, 
  { type: "send"; author: string; message: string },
  { type: "updated" }
>(
  {
    mount: (socket) => {
      if (socket.connected) {
        socket.subscribe("chatMessages");
      }
      socket.assign({ chatMessages });
    },
    // ...
  }
)

Wie Sie sehen können, ermöglicht das Client-Ereignis das Senden zusätzlicher Daten an den Server. In diesem Fall werden wir den Autor und die Nachricht als Formulareingabe senden. In der einbinden Funktion machen wir etwas Neues: Wir abonnieren das Thema namens chatMessages. Wenn ein Ereignis zu diesem Thema ausgelöst wird, gibt es eine spezielle Info-Methode, um es zu behandeln, die wir weiter unten zeigen.

Wir definieren eine render Funktion, die die Chat-Nachrichten und das Formular zur Eingabe neuer Nachrichten anzeigt:

    render: (context, meta) => {
      const { chatMessages } = context;
      return html`
        <h1>Chat</h1>
        ${chatMessages.map(renderChatMessage)}
        <form phx-submit="send">
          <input type="hidden" name="_csrf_token" value="${meta.csrfToken}" />
          <label>
            <span>Message:</span>
            <textarea type="text" name="message" placeholder="Message"></textarea>
          </label>
          <label>
            <span>Author:</span>
            <input type="text" name="author" placeholder="Author" autocomplete="off" />
          </label>
          <button type="submit">Send</button>
        </form>
      `;
    },

Für jede Chat-Nachricht im Kontext wird eine Render-Funktion aufgerufen. Dies ist wahrscheinlich nicht der beste Weg, denn es gibt auch das Konzept der LiveComponents, die in einer LiveView leben. Wir haben jedoch keine Zeit darauf verwendet.

Für die Bearbeitung des Formulars haben wir den csrf-Schutz aus einem anderen Beispiel kopiert. Außerdem haben wir die gesamte Modellvalidierung und Fehlerbehandlung übersprungen. Die Autoren von LiveViewJS haben eine sehr eigenwillige Art für die Bearbeitung von Formularen, was wir bewundern, aber wir hatten das Gefühl, dass es für unseren Anwendungsfall zu komplex war und wir unter großem Zeitdruck standen. Uns fiel auf, dass die phx-submit="senden" die Formulareingaben als zusätzliche Daten an das Ereignis gebunden, so dass wir die handleEvent wie folgt zu implementieren:

    handleEvent: (event) => {
      switch (event.type) {
        case "send":
          chatMessages.push({
            author: event.author,
            message: event.message,
            timestamp: new Date(),
          });
          pubSub.broadcast("chatMessages", { type: "updated" });
          break;
      }
    },

Wir schieben ein neues Chat-Objekt in unser globales Array. Dann, anstatt einen neuen Kontext zuzuweisen, senden wir die aktualisiert Ereignis in der Datei chatMessages Thema. Dies wird von der handleInfo Methode.

Vielleicht fragen Sie sich: Wie ist die lokale pubSub Instanz in dieser Datei mit dem LiveView verbunden? Wir waren auch verwirrt, denn Sie übergeben diese Instanz nicht an den LiveView. Es stellte sich heraus, dass es mehr als eine BroadcastChannelPubSub Instanz (in unserem Repo, siehe src/chat/index.ts), und durch den Aufruf von socket.subscribe lassen Sie den LiveView intern den Deno BroadcastChannel Implementierung, die dieselben Themen mit allen Abonnenten teilt.

Nun, auf zum nächsten Schritt.

    handleInfo: (info, socket) => {
      switch (info.type) {
        case "updated":
          socket.assign({ chatMessages });
          break;
      }
    },

Die aktualisierte Ereignis erreicht alle LiveView Server-Instanzen, die auf die chatMessages Thema. Dann erhalten alle Clients über die Kontextaktualisierung ein aktualisiertes Rendering.

Und das war's! In nur etwa 100 Codezeilen haben wir eine synchronisierte Chat-App für mehrere Benutzer geschrieben.

Bildschirmfoto

Nächster Schritt: Eigenständig machen (sozusagen irgendwie)

An diesem Punkt wurden wir von der Arbeit mit dem LiveViewJS Repo total genervt (Platzieren Code in liveview Beispiel wie in der Dokumentation angegeben). Also dachten wir uns: Versuchen wir, dies außerhalb des Repo zu realisieren.

Wir haben die deno Ordner als separates Projekt (Offtopic: die Readme-Datei in diesem Ordner braucht etwas Liebe, sie ist ziemlich veraltet). Der Versuch, es von dort aus zu starten, schlug jedoch fehl, da es normalerweise über import_map.json mit den anderen Paketen im Arbeitsbereich. Wir haben dieses Problem durch die Installation von "liveviewjs" und span>"@liveviewjs/examples" als Abhängigkeiten (zum Glück diese werden regelmäßig veröffentlicht!) und die Änderung der import_map.json.

Dann haben wir die Beispiele aus der Auffüllung der LiveViewRouter. Wir können also wahrscheinlich die span>"@liveviewjs/examples" als Abhängigkeit, aber wir sind nicht dazu gekommen, das zu tun.

Es gibt noch viel Raum für Verbesserungen, aber dies scheint ein Schritt in die richtige Richtung zu sein, wenn die Autoren wollen, dass dies angenommen wird.

Fehlersuche > console.log

Eine Sache, die uns gestört hat, ist, dass das Debuggen der Anwendung von VSCode aus nicht sofort funktioniert. Wenn Sie die autorun.ts Skript mit dem --anhängen Flag, werden Haltepunkte nicht getroffen. Es stellt sich heraus, dass dieses Skript läuft esbuild um das clientseitige JS-Bündel vorzubereiten und erstellt dann einen neuen Deno-Prozess, um den eigentlichen Server auszuführen, ohne die --attach Flagge.

Also haben wir es leicht geändert: Wir extrahieren den ESBuild-Code in eine buildClient.ts die den Client-Build vorbereitet, und haben einen separaten Haupteinstiegspunkt, um einen Debugging-Prozess zu starten. Im Repo haben wir die Startkonfiguration für alle Interessierten aufbewahrt.

Wir haben keine Zeit! Machen Sie es mit Deno Deploy möglich

Die Zeit war fast abgelaufen, aber wir wollten noch eine letzte Sache ausprobieren. Installieren Sie unsere LiveViewJS Chat-App auf Deno Deploy! Wir haben Deno Deploy bisher noch nicht verwendet, aber wir dachten: Wie schwer kann das schon sein?

Wir haben schnell herausgefunden, dass wir wegen des Client-Build-Schrittes eine benutzerdefinierte GitHub Action-Build-Konfiguration erstellen und das Endergebnis an Deno Deploy übertragen mussten. Das ging ziemlich reibungslos und wir sind mit der Deno Deploy-Aktion sehr zufrieden.

Allerdings stürzte die App beim Starten ab:

[caption id="" align="alignnone" width="830"]Niemand mag diese Fehlermeldungen Niemand mag diese Fehlermeldungen![/caption]

Und beim Lesen der Dokumentation für Deno Deploy haben wir festgestellt, dass Deploy eine harte Einschränkung hat: Sie können nicht in das Dateisystem schreiben. Das war ziemlich unerwartet!

Während ich also herausfand, ob wir es als Docker-Container laufen lassen können, hat Arjan sich mit dem Code beschäftigt und alle key.json schreibt (wer braucht die überhaupt?). Und wow... das hat tatsächlich funktioniert! (Wir sind Ich weiß nicht, ob das Auswirkungen auf die Sicherheit hat, aber verdammt! Wir haben es geschafft ;-)

Chatten Sie weiter auf liveviewjs-deno-chat.deno.dev !

Urteil

  • Der LiveViewJS-Code ist eine nahezu vollständige Neuimplementierung von Phoenix LiveView-Schnittstellen. Wir haben die Original-LiveView-Dokumentation benutzt, um bestimmte Funktionen zu verstehen , hdie handleEvent und handleInfo Funktionen. Die aktuelle LiveViewJS Dokumentation ist etwas veraltet und unklar (aber aber es wird daran gearbeitet, dies zu beheben).
  • Die LiveView-Konzepte sind ein anderes Paradigmammit unterschiedlichen Kompromissen im Vergleich zu SPA/RESTful-Anwendungen. Es hat definitiv Vorteile, aber es ist kein Allheilmittel.
  • IWenn Sie mehrere Ereignistypen haben, können Sie in TypeScript landet man leicht bei einer großen Switch-Anweisung. In Elixir wird dies durch Musterabgleich gehandhabt, was ein eleganterer Ansatz ist. Dies ist ein grundlegenderes Problem der Portierung einer Codebasis in eine andere Programmiersprache.
  • Die LiveViewJS-Implementierung ist noch nicht produktionsreif. Es fehlen noch einige Funktionen (obwohl es ist bereits ziemlich vollständig) und wurde nicht für den Produktionseinsatz optimiert. Der Einsatz erfolgt auf eigene Gefahr.
  • Es ist ein Rahmenwerk, mehr als eine Bibliothek. Dies in eine bestehende Codebasis einzuführen, ist ziemlich komplex. Außerdem teilt es viele Funktionen mit Frontend-Bibliotheken und -Frameworks wie React und Vue, die Sie benötigen, um sie richtig zu trennen.
  • Das JavaScript LiveView-Bundle ist derzeit ~60Kb gzipped, was wir für ziemlich heftig halten, aber das Bundle ist nicht minified, also gibt es vielleicht noch etwas Raum für Optimierung.

Sie können sich den Code hier ansehen: GitHub liveviewjs-deno-chat

Verfasst von

Albert Brand

Contact

Let’s discuss how we can support your journey.