Manche Leute schreiben JavaScript lieber mit React. Für andere ist es Vue oder jQuery. Wieder andere bevorzugen ihre eigenen Werkzeuge oder ein leeres Dokument. Einige Setups sind minimalistisch, andere ermöglichen es, Dinge schnell zu erledigen, und wieder andere sind unglaublich leistungsfähig und erlauben den Aufbau komplexer und wartbarer Anwendungen. Jedes Setup hat Vor- und Nachteile, aber die positiven Aspekte überwiegen normalerweise die negativen, wenn es um beliebte Frameworks geht, die von einer aktiven Community verifiziert und geprüft wurden.
React und Vue sind leistungsstarke JavaScript-Frameworks. Natürlich sind sie das – deshalb liegen beide in der allgemeinen Nutzung so hoch im Trend. Aber was macht diese und andere Frameworks so leistungsfähig? Ist es die Geschwindigkeit? Die Portabilität auf andere Plattformen wie native Desktops und Mobilgeräte? Die Unterstützung der riesigen Community?
Der Erfolg eines Entwicklungsteams beginnt mit einer Übereinkunft. Einer Übereinkunft darüber, wie die Dinge gemacht werden. Ohne eine solche Übereinkunft würde der Code schnell unübersichtlich werden und die Software selbst bei relativ kleinen Projekten schnell unhaltbar werden. Daher liegt viel (wenn nicht die meiste) der Stärke eines Frameworks in dieser Übereinkunft. Die Fähigkeit, Konventionen und gemeinsame Muster zu definieren, an die sich jeder hält. Diese Idee ist auf jedes Framework anwendbar, ob JavaScript oder nicht. Diese "Regeln" sind für die Entwicklung unerlässlich und bringen Teams jeder Größe Wartbarkeit, Wiederverwendbarkeit von Code und die Möglichkeit, Arbeit mit einer Community in einer Form zu teilen, die jeder versteht. Deshalb können wir im Web nach einer Komponente/einem Plugin suchen, das unser Problem löst, anstatt etwas selbst zu schreiben. Wir verlassen uns auf Open-Source-Alternativen, egal welches Framework wir verwenden.
Leider haben Frameworks auch Nachteile. Wie Adrian Holovaty (einer der Django-Erfinder) in seinem Vortrag erklärt, neigen Frameworks dazu, für einen bestimmten Anwendungsfall sperrig und mit Funktionen überladen zu werden. Es kann sich um die Aufnahme einer wirklich guten Idee handeln. Es kann auch der Wunsch sein, eine Einheitslösung für jedermanns Bedürfnisse anzubieten. Es kann auch darum gehen, beliebt zu bleiben. In jedem Fall gibt es Nachteile von Frameworks angesichts all der Vorteile, die sie auch haben.

In der serverseitig gerenderten Welt, in der wir mit Inhalten arbeiten, die vom Server an den Browser geliefert werden, ist diese "Framework-Mission" voller Fähigkeiten, Ideen und Lösungen, die Leute mit Kollegen, Freunden und anderen teilen. Sie neigen dazu, konsistente Entwicklungsregeln zu übernehmen und diese innerhalb ihrer Teams oder Unternehmen durchzusetzen, anstatt sich auf ein vordefiniertes Framework zu verlassen. Lange Zeit war es jQuery, das es den Leuten ermöglichte, Code im globalen Maßstab mit seinen Plugins zu teilen und wiederzuverwenden – aber diese Ära schwindet, da neue Frameworks in den Vordergrund treten.
Werfen wir einen Blick auf einige Kernpunkte, die jedes Framework – ob benutzerdefiniert oder nicht – als wesentlich für die Förderung einer wartbaren und wiederverwendbaren Codebasis betrachten sollte.
Namenskonventionen
Manche würden sagen, dass das Benennen der schwierigste Teil der Entwicklung ist. Ob es um die Benennung von JavaScript-Variablen, HTML-Klassen oder andere Dinge geht, eine Namenskonvention hat einen großen Einfluss darauf, wie wir zusammenarbeiten, da sie unserem Code Kontext und Bedeutung verleiht. Ein gutes Beispiel für eine Namenskonvention ist BEM, eine CSS-Methodik, die von den Leuten bei Yandex entwickelt und von vielen, vielen Frontend-Entwicklern übernommen wurde. Eine solche Standardisierung und Konsistenz kann unsere Zielsetzung unterstützen, nämlich eine Standardkonvention für die Benennung von JavaScript-Selektoren zu haben.
Diese Situation mag Ihnen vertraut vorkommen: Sie kehren nach einigen Monaten zu einem Projekt zurück und stellen fest, dass auf der Seite ein bestimmtes Verhalten auftritt – vielleicht etwas, das Sie ändern oder beheben möchten. Das Auffinden der Quelle dieses Verhaltens kann jedoch schwierig sein. Ist es das id-Attribut, nach dem ich suchen sollte? Ist es eine der Klassen? Vielleicht das Datenattribut für dieses bestimmte Element?
Ich denke, Sie verstehen, worauf ich hinauswill. Was hilft, ist eine Art Konvention, um den Prozess zu vereinfachen und sicherzustellen, dass jeder mit denselben Namen arbeitet. Und es spielt keine Rolle, ob wir Klassen (z. B. js-[name]) oder Datenattribute (z. B. data-name="[name]") verwenden. Wichtig ist nur, dass die Namen dem gleichen Stil entsprechen. Sicherlich wird jedes Framework, auf das Sie stoßen werden, auch eine Art Namenskonvention erzwingen, sei es React, Vue, Bootstrap, Foundation usw.
Scope
Ein Entwickler wird wahrscheinlich irgendwann mit dem JavaScript-Scope zu kämpfen haben, aber hier konzentrieren wir uns speziell auf den DOM-Scope. Egal, was Sie mit JavaScript tun, Sie verwenden es, um ein DOM-Element zu beeinflussen. Die Begrenzung von Code-Teilen auf ein DOM-Element fördert die Wartbarkeit und macht den Code modularer. Komponenten in React und Vue funktionieren auf ähnliche Weise, obwohl ihr "Komponenten"-Konzept anders ist als das des Standard-DOMs. Dennoch werden durch diese Idee die Codes auf ein DOM-Element beschränkt, das von diesen Komponenten gerendert wird.
Während wir beim Thema DOM-Manipulation sind, ist das Referenzieren von Elementen innerhalb des Root-Elements der Komponente sehr hilfreich, da es den ständigen Bedarf an Elementauswahl vermeidet. React und Vue tun dies standardmäßig auf sehr gute Weise.
Lebenszyklus
Früher war der Seitenlebenszyklus sehr geradlinig: Der Browser lud die Seite und Der Browser verließ die Seite. Das Erstellen oder Entfernen von Event-Listenern oder das Ausführen von Code zur richtigen Zeit war viel einfacher, da man einfach alles benötigte beim Laden erstellte und selten darauf achtete, den Code zu deaktivieren, da der Browser dies standardmäßig für einen erledigte.

Heutzutage ist der Lebenszyklus dank State Management und der Art und Weise, wie wir Änderungen direkt am DOM vornehmen oder Seiteninhalte per JavaScript laden, oft viel dynamischer. Unglücklicherweise bringt dies normalerweise einige Probleme mit sich, bei denen Sie Teile Ihres Codes zu bestimmten Zeiten erneut ausführen müssen.
Ich kann nicht zählen, wie oft ich mich mit Code herumschlagen musste, damit meine Event-Handler nach dem Umschreiben eines Teils des DOM korrekt ausgeführt wurden. Manchmal ist es möglich, dies mit Event-Delegation zu lösen. Andernfalls werden Ihre Code-Handler gar nicht oder mehrmals ausgeführt, weil sie plötzlich doppelt angehängt werden.
Ein weiterer Anwendungsfall wäre die Notwendigkeit, Instanzen von Bibliotheken nach DOM-Änderungen zu erstellen, wie z. B. "Fake Selects".
In jedem Fall ist der Lebenszyklus wichtig, und die Begrenzung Ihres Codes auf ein DOM-Element hilft auch hier, da Sie wissen, dass der auf dieses Element angewendete Code erneut ausgeführt werden muss, falls das Element neu gerendert wird.
Frameworks tun dasselbe mit Funktionen wie componentDidMount oder componentDidUpdate, die Ihnen eine einfache Möglichkeit bieten, Code genau dann auszuführen, wenn Sie ihn benötigen.
Wiederverwendbarkeit
Code von anderswo zu kopieren und wiederzuverwenden ist sehr üblich. Wer hat nicht einen Ausschnitt aus einem früheren Projekt oder eine Antwort von StackOverflow verwendet, richtig? Leute haben sogar Dienste wie npm erfunden, die für einen Zweck gedacht sind: um Code einfach zu teilen und wiederzuverwenden. Es ist nicht nötig, das Rad neu zu erfinden, und das Teilen von Code mit anderen ist bequem, praktisch und oft effizienter als etwas von Grund auf neu zu entwickeln.
Komponenten, sei es React, Vue oder ein anderer Baustein eines Frameworks, fördern die Wiederverwendbarkeit, indem sie einen Wrapper um einen Codeabschnitt legen, der einem bestimmten Zweck dient. Ein standardisierter Wrapper mit einem erzwungenen Format. Ich würde sagen, das ist eher ein Nebeneffekt der Existenz einer Basis, auf der wir aufbauen können, als eine gezielte Anstrengung zur Wiederverwendbarkeit, da jede Basis immer ein Standardformat von Code benötigt, das sie ausführen kann, so wie Programmiersprachen eine Syntax haben, die wir befolgen müssen… aber es ist definitiv ein sehr bequemer und nützlicher Nebeneffekt. Durch die Kombination dieser gewonnenen Wiederverwendbarkeit mit Paketmanagern wie Yarn und Bundlern wie webpack können wir plötzlich Code, der von vielen Entwicklern auf der ganzen Welt geschrieben wurde, mit fast keiner Anstrengung in unserer App zum Laufen bringen.
Auch wenn nicht jeder Code Open Source und teilbar ist, hilft eine gemeinsame Struktur auch kleineren Teams, sodass jeder den größten Teil des Codes, der im Team geschrieben wurde, verstehen kann, ohne den Autor konsultieren zu müssen.
DOM-Manipulation unter Berücksichtigung der Performance
Das Lesen und Schreiben in den DOM ist kostspielig. Entwickler von Frontend-Frameworks berücksichtigen dies und versuchen, so viel wie möglich mit Zuständen, virtuellem DOM und anderen Methoden zu optimieren, um Änderungen am DOM dann vorzunehmen, wenn es nötig ist und wo es nötig ist… und das sollten wir auch tun. Während wir uns mehr mit serverseitig gerendertem HTML beschäftigen, landet der meiste Code schließlich beim Lesen oder Schreiben in den DOM. Schließlich ist das der Zweck von JavaScript.
Während die meisten DOM-Änderungen minimal sind, können sie auch recht häufig auftreten. Ein schönes Beispiel für häufiges Lesen/Schreiben ist die Ausführung eines Skripts beim Scrollen der Seite. Idealerweise möchten wir das Hinzufügen von Klassen, Attributen oder das Ändern von Elementinhalten direkt im Handler vermeiden, auch wenn wir dieselben Klassen, Attribute oder Inhalte schreiben, da unsere Änderungen immer noch vom Browser verarbeitet werden und selbst dann teuer sind, wenn sich für den Benutzer nichts ändert.
Größe
Zu guter Letzt: Größe. Die Größe ist entscheidend oder sollte es zumindest für ein Framework sein. Der in diesem Artikel behandelte Code soll als Basis für viele Projekte dienen. Er sollte so klein wie möglich sein und unnötigen Code vermeiden, der manuell für einmalige Anwendungsfälle hinzugefügt werden könnte. Idealerweise sollte er modular sein, damit wir Teile davon à la carte entnehmen können, basierend auf den spezifischen Anforderungen des jeweiligen Projekts.

Während die Größe selbst geschriebenen Codes normalerweise angemessen ist, enden viele Projekte mit mindestens einigen Abhängigkeiten/Bibliotheken, um Lücken zu füllen, und diese können die Dinge sehr schnell sehr sperrig machen. Nehmen wir an, wir haben einen Karussell auf einer Top-Level-Seite einer Website und einige Diagramme irgendwo tiefer. Wir können uns an vorhandene Bibliotheken wenden, die diese Dinge für uns erledigen, wie z. B. slick carousel (10 KB minifiziert/gepackt, ohne jQuery) und highcharts (73 KB minifiziert/gepackt). Das sind über 80 KB Code, und ich wette, nicht jedes Byte wird für unser Projekt benötigt. Wie Addy Osmani erklärt, ist JavaScript teuer und seine Größe ist nicht dasselbe wie andere Assets auf einer Seite. Es lohnt sich, das im Hinterkopf zu behalten, wenn Sie das nächste Mal zu einer Bibliothek greifen, obwohl dies Sie nicht davon abhalten sollte, dies zu tun, wenn die Vorteile die Nachteile überwiegen.
Ein Blick auf eine minimale Lösung, die wir Gia nennen
Werfen wir einen Blick auf etwas, woran ich eine Weile mit meinem Team bei Giant gearbeitet und es verwendet habe. Wir mögen JavaScript und möchten direkt mit JavaScript arbeiten, nicht mit einem Framework. Gleichzeitig benötigen wir die Wartbarkeit und Wiederverwendbarkeit, die Frameworks bieten. Wir haben versucht, einige Konzepte beliebter Frameworks zu übernehmen und sie auf eine serverseitig gerenderte Website anzuwenden, auf der die Optionen für ein JavaScript-Setup so vielfältig und doch so begrenzt sind.
Wir werden einige grundlegende Funktionen unseres Setups durchgehen und uns darauf konzentrieren, wie es die bisher behandelten Punkte löst. Beachten Sie, dass viele der Lösungen für unsere Bedürfnisse direkt von großen Frameworks inspiriert sind. Wenn Sie also Ähnlichkeiten feststellen, wissen Sie, dass dies kein Zufall ist. Wir nennen es Gia (verstehen Sie es, wie kurz für Giant?!) und Sie können es von npm beziehen und den Quellcode auf GitHub finden.
Gia ist, wie viele Frameworks, um Komponenten aufgebaut, die Ihnen einen grundlegenden Baustein und einige Vorteile bieten, auf die wir gleich eingehen werden. Verwechseln Sie Gia-Komponenten jedoch nicht mit den Komponentenkonzepten von React und Vue, bei denen Ihr gesamtes HTML ein Produkt der Komponente ist. Das ist anders.
In Gia ist eine Komponente ein Wrapper für Ihren eigenen Code, der im Scope eines DOM-Elements ausgeführt wird, und die Instanz einer Komponente wird innerhalb des Elements selbst gespeichert. Dadurch wird eine Instanz automatisch aus dem Speicher vom Garbage Collector entfernt, falls das Element aus dem DOM entfernt wird. Der Quellcode der Komponente ist hier zu finden und ist recht einfach.
Abgesehen von einer Komponente enthält Gia mehrere Helfer zum Anwenden, Zerstören und Arbeiten mit der Komponenteninstanz oder zur Kommunikation zwischen Komponenten (mit einem standardmäßigen Eventbus für den Komfort).
Beginnen wir mit einem grundlegenden Setup. Gia verwendet ein Attribut zur Auswahl von Elementen, zusammen mit dem Namen der auszuführenden Komponente (d. h. g-component="[Komponentenname]"). Dies erzwingt eine konsistente Namenskonvention. Das Ausführen der Funktion loadComponents erstellt die Instanzen, die mit dem Attribut g-component definiert sind.
Siehe den Pen Grundlegende Komponente von Georgy Marchuk (@gmrchk) auf CodePen.
Gia-Komponenten ermöglichen uns auch, Elemente innerhalb des Root-Elements mit dem Attribut g-ref einfach auszuwählen. Alles, was getan werden muss, ist, die erwarteten Refs zu definieren und anzugeben, ob wir mit einem einzelnen Element oder einer Reihe von ihnen arbeiten. Die Referenz ist dann im this.ref-Objekt innerhalb der Komponente zugänglich.
Siehe den Pen Komponente mit Ref-Elementen von Georgy Marchuk (@gmrchk) auf CodePen.
Als Bonus können Standardoptionen in der Komponente definiert werden, die automatisch durch Optionen im Attribut g-options überschrieben werden.
Siehe den Pen Komponente mit Optionen von Georgy Marchuk (@gmrchk) auf CodePen.
Die Komponente enthält Methoden, die zu verschiedenen Zeiten ausgeführt werden, um das Lebenszyklusproblem zu lösen. Hier ist ein Beispiel, das zeigt, wie eine Komponente initialisiert oder entfernt werden kann
Siehe den Pen Komponenten laden/entfernen von Georgy Marchuk (@gmrchk) auf CodePen.
Beachten Sie, wie die Funktion loadComponents dieselbe Komponente nicht anwendet, wenn sie bereits existiert.
Es ist nicht notwendig, Listener, die an das Root-Element einer Komponente oder Elemente darin angehängt sind, vor dem erneuten Rendern zu entfernen, da die Elemente sowieso aus dem DOM entfernt würden. Es können jedoch einige Listener erstellt werden, die an globale Objekte (z. B. window) angehängt sind, wie z. B. die für die scroll-Behandlung verwendeten. In diesem Fall ist es notwendig, den Listener manuell zu entfernen, bevor die Komponenteninstanz zerstört wird, um Speicherlecks zu vermeiden.
Das Konzept einer Komponente, die auf ein DOM-Element beschränkt ist, ähnelt in seiner Natur den Komponenten von React und Vue, mit dem wichtigen Unterschied, dass die DOM-Struktur außerhalb der Komponente liegt. Folglich müssen wir sicherstellen, dass sie zur Komponente passt. Die Definition der Ref-Elemente hilft sicherlich, da Gia-Komponenten Sie informiert, wenn die erforderlichen Refs nicht vorhanden sind. Das macht die Komponente wiederverwendbar. Das Folgende ist ein Beispiel für die Implementierung eines einfachen Karussells, das leicht wiederverwendet oder geteilt werden könnte
Siehe den Pen Grundlegende Karussellkomponente von Georgy Marchuk (@gmrchk) auf CodePen.
Während wir über Wiederverwendbarkeit sprechen, ist es wichtig zu erwähnen, dass Komponenten nicht in ihrem bestehenden Zustand wiederverwendet werden müssen. Mit anderen Worten, wir können sie erweitern, um neue Komponenten wie jede andere JavaScript-Klasse zu erstellen. Das bedeutet, wir können eine allgemeine Komponente vorbereiten und darauf aufbauen.
Als Beispiel scheint eine Komponente, die uns den Abstand zwischen dem Cursor und der Mitte eines Elements gibt, etwas zu sein, das eines Tages nützlich sein könnte. Eine solche Komponente finden Sie hier. Nachdem dies bereit ist, ist es lächerlich einfach, darauf aufzubauen und mit den bereitgestellten Zahlen zu arbeiten, wie das nächste Beispiel in der Renderfunktion zeigt, obwohl wir über die Nützlichkeit dieses speziellen Beispiels streiten könnten.
Siehe den Pen ZMXMJo von Georgy Marchuk (@gmrchk) auf CodePen.
Versuchen wir, uns optimierte DOM-Manipulationen anzusehen. Das Erkennen, ob eine DOM-Änderung stattfinden soll, kann manuell gespeichert oder überprüft werden, ohne direkt auf den DOM zuzugreifen, aber das bedeutet viel Arbeit, die wir vielleicht vermeiden möchten.
Hier hat sich Gia wirklich von React inspirieren lassen, mit einfacher, abgespeckter Komponenten-Zustandsverwaltung. Der Zustand einer Komponente wird ähnlich wie Zustände in React gesetzt, mithilfe einer setState-Funktion.
Das gesagt, gibt es keine Renderung in unserer Komponente. Der Inhalt wird vom Server gerendert, daher müssen wir die Änderungen im Zustand woanders nutzen. Zustandsänderungen werden ausgewertet und tatsächliche Änderungen werden an die stateChange-Methode einer Komponente gesendet. Idealerweise würde jede Interaktion zwischen der Komponente und dem DOM in dieser Funktion behandelt. Wenn sich ein Teil des Zustands nicht ändert, ist er nicht im Objekt stateChanges enthalten, das an eine Funktion übergeben wird, und wird daher nicht verarbeitet – der DOM wird nicht berührt, ohne dass es wirklich notwendig ist.
Schauen Sie sich das folgende Beispiel mit einer Komponente an, die im Viewport sichtbare Abschnitte anzeigt
Siehe den Pen Abschnitte im Viewport Komponente von Georgy Marchuk (@gmrchk) auf CodePen.
Beachten Sie, wie das Schreiben in den DOM (visualisiert durch das Blinken) nur für Elemente erfolgt, bei denen sich der Zustand tatsächlich ändert.
Jetzt kommen wir zu meinem Lieblingsteil! Gia ist wirklich minimalistisch. Das gesamte Bundle, das den gesamten Code enthält, einschließlich aller Helfer, ist ganze 2,68 KB (minifiziert/gepackt) groß. Ganz zu schweigen davon, dass Sie wahrscheinlich nicht alle Teile von Gia benötigen und mit einem Bundler sogar noch weniger importieren würden.

Wie bereits erwähnt, kann die Größe des Codes durch die Einbindung von Drittanbieterabhängigkeiten schnell ansteigen. Deshalb unterstützt Gia auch Code Splitting, bei dem Sie eine Abhängigkeit für eine Komponente definieren können, die erst beim ersten Initialisieren der Komponente geladen wird, ohne zusätzliche Einrichtung für den Bundler. So müssen sperrige Bibliotheken, die irgendwo tief in Ihrer Website oder Anwendung verwendet werden, diese nicht verlangsamen.
Falls Sie eines Tages beschließen, dass Sie wirklich alle Vorteile großer Frameworks in Ihrem Code nutzen möchten, gibt es nichts Einfacheres, als sie wie jede andere Abhängigkeit für die Komponente zu laden.
class SampleComponent extends Component {
async require() {
this.vue = await import('vue');
}
mount() {
new this.vue({
el: this.element,
});
}
}
Fazit
Ich denke, der Hauptpunkt dieses Artikels ist, dass Sie kein Framework benötigen, um wartbaren und wiederverwendbaren Code zu schreiben. Das Befolgen und Erzwingen einiger Konzepte (die Frameworks ebenfalls verwenden) kann Ihnen allein schon viel bringen. Obwohl Gia minimalistisch ist und nicht viele der robusten Funktionen großer Anbieter wie React und Vue bietet, hilft es uns dennoch, diese saubere Struktur zu erhalten, die langfristig so wichtig ist. Es enthält noch einige interessanteste Dinge, die es hier nicht geschafft haben. Wenn es Ihnen bisher gefällt, schauen Sie es sich an!
Es gibt unzählige Anwendungsfälle und unterschiedliche Bedürfnisse erfordern unterschiedliche Ansätze. Verschiedene Frameworks können viel Arbeit für Sie erledigen; in anderen Fällen können sie einschränkend sein.
Was ist Ihr minimales Setup und wie berücksichtigen Sie die hier besprochenen Punkte? Bevorzugen Sie ein Framework oder eine Nicht-Framework-Umgebung? Verwenden Sie Frameworks in Kombination mit statischen Website-Generatoren wie Gatsby? Lassen Sie es mich wissen!
Wow, sieht vielversprechend aus. Ich werde es so schnell wie möglich ausprobieren! Danke für die Arbeit und die Informationen
Haben Sie sich t3js angesehen und wenn ja – was können Sie dazu sagen (abgesehen davon, dass es bezüglich eines Modulsystems etwas veraltet ist)? Ich habe kürzlich einige Optionen für genau dieses Szenario (serverseitig gerenderte Websites) recherchiert, und t3js war das Einzige, was ich gefunden habe. Sehr interessiert daran, gia weiter zu untersuchen
Ich höre ehrlich gesagt zum ersten Mal von t3js. Abgesehen davon, dass es veraltet ist, wie Sie sagen, sieht es ziemlich gut aus. Soweit ich das verstanden habe, hat es ziemlich ähnliche Ziele, aber im Vergleich zu Gia würde ich sagen, es erzwingt den Schreibstil/die Struktur viel stärker. Gia ist eher ein kleines Stück JavaScript, das Ihnen einen Vorsprung verschafft und Sie zur Modularität drängt, aber immer noch nur ein kleines Stück JS, mit dem Sie arbeiten und das Sie beliebig ändern können.
Wir prüfen auch, ob wir weitere Funktionalitäten für größere Aufgaben hinzufügen, die mehr Kontrolle und Trennung von Code in mehrere zusammenarbeitende Komponenten erfordern, um ihm bei Bedarf mehr "App"-Fähigkeiten zu verleihen.
Toller Beitrag, Georgy!
Ich möchte mehr über die Gesamtstruktur der Website mit Gia erfahren – zum Beispiel, ob Seiten Komponenten erweitern, verschachtelte Komponenten, Gia und Swup Demo.