3 Punkte von GN⁺ 2024-08-17 | 1 Kommentare | Auf WhatsApp teilen

Beispiele für Vanilla JSX

Was wäre, wenn JSX DOM-Elemente zurückgibt?

  • Die Funktion ClickMe erstellt einen Button und zeigt die Anzahl der Klicks an
  • Bei jedem Klick auf den Button wird der Text aktualisiert
export default function ClickMe() {
  let i = 0;
  const el = Click me as HTMLButtonElement;
  el.onclick = (e) => {
    el.textContent = `Clicked ${++i} times`;
  };
  return el;
}

Wiederverwendbarkeit

  • Die Komponente ClickMe kann mehrfach verwendet werden, wobei jeweils ein eigener Zustand erhalten bleibt
import ClickMe from "./sample1.js";
export default () =>

;

Erstellen eines interaktiven DOM-Baums

  • Mit den Klassen TodoInput und TodoList lässt sich eine To-do-Liste verwalten
  • Einträge können hinzugefügt und per Klick wieder entfernt werden
function TodoInput(attrs: { add: (v: string) => void }) {
  const input =  as HTMLInputElement;
  input.placeholder = 'Add todo item...';
  input.onkeydown = (e) => {
    if (e.key === 'Enter') {
      attrs.add(input.value);
      input.value = '';
    }
  };
  return input;
}

class TodoList {
  ul =  as HTMLUListElement;
  add(v: string) {
    const item =
- {v}
 as HTMLLIElement;
    item.onclick = () => item.remove();
    this.ul.append(item);
  }
}

export default () => {
  const list = new TodoList();
  list.add('foo');
  list.add('bar');
  return
     list.add(v)} />
    {list.ul}
  ;
};

Verarbeitung großer Datenmengen

  • Die Funktion FindNames verarbeitet große Datenmengen, filtert sie und zeigt die Ergebnisse an
  • Je nach Eingabewert werden passende Einträge in Echtzeit aktualisiert
import { data } from "../fetch-dataset.js";
export default function FindNames() {
  const status =  as HTMLParagraphElement;
  const results =  as HTMLUListElement;
  const input =  as HTMLInputElement;
  updateMatches();

  function updateMatches() {
    const matched = (data.entries().filter(([k]) => k.match(input.value)).toArray());
    const matches = (Iterator.from(matched).map(match => ).take(30));
    results.replaceChildren(...matches);
    status.textContent = `${matched.length} / ${data.size}`;
  }

  return
    {input}
    {status}
    {results}

;
}

function Item(attrs: { match: [string, number], regex: string }) {
  const [name, count] = attrs.match;
  const total = ({count});
  return
-
     {total}

;
}

function highlight(str: string, regex: string) {
  if (!regex) return str;
  const r = new RegExp(`(${regex})`, 'gi');
  return str.replace(r, '$1');
}

Einführung in imlib

  • imlib ist eine Bibliothek, die für immaculatalibrary.com entwickelt wurde
  • Sie wird zum Aufbau von minigamemaker.com und der Website verwendet, die Sie gerade lesen
  • Sie wurde entwickelt, weil bestehende Ansätze für Zustand nicht ausreichten, und ist die bevorzugte Methode zum Erstellen von Apps

GN⁺-Zusammenfassung

  • Dieser Artikel erklärt, wie sich mit JSX DOM-Elemente direkt erzeugen und interaktiv nutzen lassen
  • Er zeigt, wie sich auch ohne klassisches Virtual DOM große Datenmengen effizient verarbeiten lassen
  • Die Bibliothek imlib ermöglicht eine einfache und intuitive App-Entwicklung
  • Andere Projekte mit ähnlicher Funktionalität sind unter anderem React und Vue.js

1 Kommentare

 
GN⁺ 2024-08-17
Meinungen auf Hacker News
  • Wenn tatsächliche DOM-Nodes zurückgegeben werden, werden eine der großen Stärken von JSX und Nicht-JSX-Bibliotheken wie Lit – APIs im Immediate-Mode-Stil und das Modell UI=f(state) – weitgehend entkräftet.
    Man muss nicht das echte DOM zurückgeben, sondern eine Beschreibung des DOM, damit man das Template mit neuem State wiederholt auswerten und das DOM, in das dieses Template gerendert wird, effizient aktualisieren kann.
    Die Beispiele aktualisieren mit imperativen DOM-APIs wie input.onkeydown = ... oder this.ul.append(item), aber solchen Code zu vermeiden und Elemente im Template zu iterieren, ist vermutlich der Hauptvorteil eines virtuellen DOM.
    Das Problem beim virtuellen DOM ist allerdings, dass das Diffing langsam ist; das lässt sich mit Templates lösen, die – wie bei Lit – statische und dynamische Teile trennen.

    • Aus meiner Erfahrung mit komplexen Web-UIs halte ich den Performance-Vorteil eines virtuellen DOM in hinreichend komplexen Apps für nahezu eine Illusion.
      Eher hat es den gegenteiligen Effekt: Man denkt, „ich kann große UIs beliebig neu rendern, das virtuelle DOM wird das schon erledigen“, während die tatsächliche Performance schlecht ist und die App-Struktur bereits so verhärtet ist, dass sie sich ohne Neuschreiben kaum noch reparieren lässt.
      Ich habe denselben Verlauf in vier Teams gesehen und als Architekt auf Basis der Probleme, die man mit React bekommen würde, eine performancebewusste Vanilla-Struktur empfohlen, aber das wurde mit „es gibt viele Wege, das zu vermeiden“ abgetan.
      Am Ende müssen viele das offenbar selbst schmerzhaft lernen.
    • Das hängt vom Nutzer ab. Aus einer anderen Perspektive ist JSX nur eine weitere DSL für das Aufrufmuster der Funktion createElement, und die beschriebenen Vorteile kommen aus Framework und Runtime.
      Kürzer gesagt: JSX ist eine alternative Syntax für Funktionsaufrufe, die nützliche Einsatzbereiche hat.
      In einer früheren Firma haben wir JSX für Testdaten-Factories verwendet: Es sieht aus wie XML, aber beim Erzeugen der Elemente konnte man über ein Builder-Pattern kontextabhängige Entscheidungen für die endgültigen Daten treffen.
      Das hatte nichts mit React oder dem DOM zu tun, und es bedeutete auch nicht, dass man es ohne JSX nicht deklarativ ausdrücken könnte.
    • Was man „will“, hängt davon ab, wer gemeint ist. Ich persönlich bevorzuge es, DOM-Nodes direkt an Ort und Stelle zu aktualisieren, ganz ohne Reconciliation-Mechanismen.
    • Ich verstehe den Traum, „keine imperativen Updates mehr schreiben zu müssen“. Ich habe solche Frameworks etwa zehn Jahre lang genutzt und bin damit gut vertraut.
      Persönlich aktualisiere ich das DOM lieber imperativ. Man kann sehr fein steuern, was passiert, und es sehr effizient strukturieren.
      Außerdem kann man App-Funktionen leichter hinzufügen, ändern, entfernen und anpassen, ohne sich an die starke Methodik anderer Leute anpassen zu müssen.
    • Ich stimme zu, dass die Rückgabe echter DOM-Nodes die Vorteile von JSX und Nicht-JSX-Bibliotheken wie Lit – APIs im Immediate-Mode-Stil und das Modell UI=f(state) – schwächt, aber das fühlt sich wie eine der leaky abstractions schlechthin in der gesamten Informatik an.
      Es gibt einen Grund, warum rund um React ein ganzes Ökosystem entstanden ist, das erklärt, wie man mehrfaches Rendern vermeidet oder unnötige Refreshes verhindert.
  • Ich habe dieses Projekt gebaut, weil mich der Zustand der Static-Site-Generatoren in den letzten zehn Jahren frustriert hat.
    Da ich hauptsächlich statische Websites erstelle, wollte ich ein einfaches, intuitives Tool, und JSX schien gut zu passen; die übermäßige Komplexität von JSX-Frameworks wie React hatte ich aber satt.
    Also habe ich einen Static-Site-Generator gebaut, der JSX als Strings rendert, und es war naheliegend, das auf den Browser auszuweiten, sodass JSX zu DOM-Elementen gerendert wird.
    In manchen Fällen, besonders bei Layouts, passt das auch gut für gemeinsam genutzte Komponenten. Insgesamt bin ich mit dem Ergebnis zufrieden, auch wenn Teile ziemlich hacky sind und die IDE-Unterstützung besser sein könnte.
    Zusätzlich passt dieser Ansatz auch gut zu SEO. Das war ein Problem, das meiner Meinung nach in anderen JSX-Frameworks nicht wirklich sauber gelöst ist.

    • Ich frage mich, ob du dir Astro angesehen hast. Es wirkt wie der perfekte Static-Site-Generator, aber vielleicht gab es andere Schmerzpunkte, die VanillaJSX löst.
    • Gute Idee. Auf der Demo-Seite liefert die Todo-App einen 404.
      In der Dokumentation wäre es wohl besser, „static site generator“ auszuschreiben, statt „SSG“ zu verwenden. Obwohl ich schon statische Website-Generatoren benutzt habe, wusste ich nicht, was SSG bedeutet, und musste eine KI fragen.
    • JSX für die Serverseite von Django wäre ziemlich cool.
    • Ich frage mich, ob du dir lit-html angesehen hast.
      Ich kam von Vue und war überrascht: lit-html erledigt mit einfachen Vanilla-JS-Templates vieles von dem, was Vue-Templates tun, einschließlich Event-Binding.
      Mit der VSCode-Lit-Erweiterung bekommt man sogar innerhalb der Templates Syntax-Highlighting und vollständige Typprüfung.
      Ich bin darauf gestoßen, nachdem Marc Grabanski getwittert hatte, dass er lit-html zusammen mit VanillaJS statt Lit verwendet; nach eigenen Experimenten funktionierte es sehr gut. Es scheint dem, was du gerade lösen willst, ziemlich ähnlich zu sein.
      Mit dem Template-Paket lit-html kann man das meiste aus dem Template-Kapitel umsetzen: https://lit.dev/docs/templates/overview/
      Das geht auch ohne die Komponentenabstraktion von lit-element: https://lit.dev/docs/libraries/standalone-templates/#renderi...
  • Der Grund, warum solche „Was wäre, wenn“-Gedanken Spaß machen, ist, dass man die Ursprünge von JSX bis zu Facebooks XHP zurückverfolgen kann und XHP explizit von E4X inspiriert war, einem frühen JS-Standard, der ähnlich aussah und funktionierte wie die hier beschriebene Library.
    [1] https://engineering.fb.com/2010/02/09/developer-tools/xhp-a-...
    [2] https://en.m.wikipedia.org/wiki/ECMAScript_for_XML

    • E4X hatte den unglücklichen Nachteil, echte DOM-Instanzen zurückzugeben, sodass man sie imperativ aktualisieren musste.
      Das ist auch der Grund, warum JSX über E4X hinausging, und danach gab es keine ernsthaften Vorschläge mehr für HTML-Templates in JS.
      Trotzdem dürfte es möglich sein, die allgemeine Idee modern aufzubereiten und wiederzubeleben: https://github.com/WICG/webcomponents/issues/1069
    • Kurioserweise ist E4X der Grund, warum JavaScript for(of) statt for each bekommen hat.
      Der Grund, warum wir kein for (:) bekommen haben, ist noch absurder: Es hätte mit :type-Annotationen kollidieren können, von denen einige TC39-Mitglieder glaubten, sie würden irgendwann auf magische Weise in die Sprache kommen.
  • Ich habe einmal eine UI-Library gebaut, die auf der Idee basiert, dass JSX-Template-Ausdrücke echte DOM-Nodes erzeugen.
    Sie bindet Modellobjekte an Eigenschaften und beseitigt so auch einen Teil des imperativen Event-Handler-Boilerplates. Wie man sich denken kann, halte ich das für eine gute Idee.
    https://github.com/tomtheisen/mutraction
    Man kann etwa mit track({ clicks: 0 }) ein Modell erstellen, bei einem Klick model.clicks erhöhen und den Wert direkt im DOM widerspiegeln lassen.

  • Hier ist auch VanJS erwähnenswert: https://vanjs.org/
    Auch andere JSX-Libraries wie Solid.JS geben DOM-Nodes zurück, und ich finde es gut, dass diese Idee an Fahrt gewinnt.
    Je näher wir an der Plattform arbeiten, die wir verwenden, desto besser, denke ich. Abstraktionsschichten können zwar nützlich sein, aber in der Praxis habe ich bisher keinen Bedarf gefunden, die Plattform zu verstecken.
    Riesige Projekte wie Facebook könnten davon profitieren, aber in dieser Größenordnung habe ich nicht gearbeitet.

    • Ist SolidJS beim Vergleich der Bundle-Größe nicht ziemlich bedeutungslos?
  • Die Beispiele sind cool, aber wichtig scheint mir, dass sie keine Komponenten-Props zeigen, die sich im Lauf der Zeit ändern können.
    Diese Fähigkeit scheint gar nicht modelliert zu sein. Wenn man sie nicht braucht, ist das ein cleverer Ansatz, aber ich sehe nicht recht, wie er auf komplexere Apps skaliert.

    • Die hier und im gesamten Browser-Code verwendete Technik ist exakt dieselbe, die intern in VS Code genutzt wird, und sie skaliert ziemlich gut.
      Der Unterschied besteht lediglich darin, dass 10 Zeilen Code zum Erstellen und Konfigurieren von DOM-Elementen auf die übliche Weise knapper geschrieben werden.
      Tatsächlich ist der wirklich interessante Teil dieses Frameworks fast alles andere: dass auf der Seite des Static-Site-Generators JSX als String zurückgegeben wird, dass rohe Source-Verzeichnisse eingelesen und zur SSG-Zeit string|Buffer manipuliert werden, sowie ein selbst gebautes, sehr effizientes und schnelles Modulsystem auf Basis von chokidar und swc.
      Der Teil, JSX als DOM zu verwenden, ist visuell am auffälligsten, in Wirklichkeit aber eher ein Partytrick.
      Zum Beispiel ist der Source von vanillajsx.com sehr knapp, klar und kurz. Ich habe ihn heute komplett ohne Abhängigkeiten außer imlib geschrieben, und die JSX-as-DOM-Demo ist der am wenigsten innovative Teil: https://github.com/sdegutis/vanillajsx.com/tree/main/site
    • Um diesen Ansatz auf echte Größe zu bringen und sicher zu sein, dass alles wie erwartet zusammenspielt, braucht man eine ordentliche Reaktivitätsschicht.
      Wenn man diese Art mag, bei der JSX konkrete DOM-Elemente zurückgibt, funktioniert Solid genau so und ergänzt das um eine vernünftige Reaktivitätsschicht.
    • Vielleicht übersehe ich etwas, aber ich verstehe nicht, wie dieser Ansatz die Verwendung von setTimeout oder setInterval verhindert.
      Ich stimme allerdings zu, dass solche Projekte für kleine Use Cases oft gut funktionieren, in „Real-World“-Szenarien aber schnell auseinanderfallen.
    • Ich will diese Library weder verteidigen noch habe ich vor, sie zu verwenden, aber wenn man wie bei React/Vue erreichen will, dass Änderungen an Props ein Re-Rendering auslösen, könnte man hier wohl JS Proxy verwenden.
      Die Implementierung dürfte nicht allzu schwierig sein.
  • Das ist Vanilla TSX sehr ähnlich: https://github.com/wisercoder/uibuilder
    Ein Beispiel für eine mit Vanilla TSX geschriebene App gibt es hier: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...

  • Das erinnert an ActionScript 3, das XML im Kern der Sprache hatte.
    Es war eine Sprache, die Spaß gemacht hat, aber sie ist bekanntlich daran gescheitert, ES4 zu werden. Am Ende hat es also über 10 Jahre gedauert, bis wir mit TypeScript und JSX an einem ähnlichen Punkt angekommen sind.

    • https://en.wikipedia.org/wiki/ECMAScript_for_XML — Das gab es auch in Firefox, aber die breite Masse wollte es nicht, und schließlich wurde es entfernt.
      In Firefox 17, sechs Monate vor dem ersten React-Release, wurde es für Webseiten deaktiviert.
    • Ich kann mich nicht erinnern, dass man XML auf diese Weise inline erzeugen konnte. Vielleicht wäre es möglich gewesen, wenn es eine serverseitige Flex-Funktion gewesen wäre.
  • Ich frage mich, ob es noch jemanden gibt, bei dem das letzte Beispiel in Firefox nicht funktioniert. In Edge ging es, in Firefox schlug es fehl.
    Uncaught (in promise) TypeError: Map.groupBy(...).entries().map is not a function

  • Ich verstehe nicht so recht, warum JSX attraktiver sein soll als etwas wie unten:
    h("div", {}, [ h("p", {}, "this is easy"), ...list.map((l) => h("li", {}, l)) ])
    Damit bekommt man Schleifen, Variablen-Interpolation usw. automatisch, ohne einen Compiler und neue Syntax zu bauen. Wäre schön, wenn das jemand erklären könnte.

    • Ich glaube, viele finden die JSX-Version deutlich lesbarer und vertrauter. Natürlich ist das Geschmackssache.
      Fairerweise nutzt man auch in JSX ganz normale Schleifen und Interpolation. {} akzeptiert Vanilla-JS-Ausdrücke.
      Es braucht lediglich einen Kompilierschritt, der Element-Tags in createElement-Aufrufe auflöst.
    • Persönlich verstehe ich Leute nicht so ganz, die sich den geposteten Code ansehen und finden, er sei so gut wie JSX.
      Menschen verarbeiten Informationen unterschiedlich, aber dieses Beispiel hat so viel Interpunktion, dass es sehr schwer ist, es schnell zu erfassen.
      Wenn man die Syntax nicht im Kopf selbst zerlegt, sieht man die erzeugte DOM-Struktur nicht; und die DOM-Struktur auf einen Blick zu verstehen ist viel wichtiger als die Frage, ob ein Compiler nötig ist.
      Zur Einordnung: Ich bin selbst neurodivergent und hoffe, diese Erklärung hilft.
    • Wenn man diesem Gedankengang immer weiter in den Kaninchenbau folgt, landet man am Ende bei Common Lisp.
    • Den Gedanken hatte ich auch schon. Ein Vorteil ist wohl, dass es wie HTML aussieht.
      Es ähnelt dem, was man in den Browser-Entwicklertools sieht, daher lässt es sich leichter vergleichen und debuggen.
    • Für eine einzelne Datei oder eine einfache App ist so ein Stil auch in Ordnung.
      Aber wenn eine komplexe App Hunderte von Komponenten hat und diese anfangen, sich gegenseitig zu importieren und zu nutzen, wirkt die Schreibweise mit JSX-Tags wie eine einfache und leicht verständliche Syntax.
      Die Gegenreaktion auf React kommt letztlich oft daher, dass es für einfache Dinge überdimensioniert ist — und das stimmt auch.
      Aber je nach Person unterscheiden sich die Apps, die Komplexität und die Anforderungen; deshalb lässt sich nicht einfach sagen, eine Seite sei attraktiver. Es sind unterschiedliche Werkzeuge für unterschiedliche Probleme.