4 Punkte von GN⁺ 2024-11-04 | 1 Kommentare | Auf WhatsApp teilen
  • Cash ist eine sehr kleine jQuery-Alternative, die eine jQuery-ähnliche Syntax zur DOM-Manipulation in modernen Browsern ab IE11+ bietet
  • Durch Nutzung moderner Browserfunktionen reduziert sie die Codebasis und macht vertraute chaining-fähige Methoden mit deutlich kleinerer Dateigröße verfügbar
  • Sie zielt nicht auf 100%ige Funktionsgleichheit mit jQuery ab, deckt aber die meisten alltäglichen Use Cases ab; die implementierten APIs sind weitgehend mit jQuery kompatibel
  • Die Größe beträgt minified & gzipped 6 KB und ist damit 76,6 % kleiner als jQuery Slim 3.4.1 mit 24,4 KB; mit Partial Builds lässt sie sich weiter reduzieren
  • Unterstützt werden eine TypeScript-Codebasis, aus dem Code generierte TypeScript-Typen, namespaced Events sowie Partial Builds, bei denen einzelne Methoden ausgeschlossen werden können

Welches Problem Cash löst

  • Cash ist eine jQuery-Alternative für moderne Browser und bietet für die DOM-Manipulation den jQuery-ähnlichen $()-Selektor sowie chaining-fähige Collection-Methoden
  • Unterstützt werden Browser ab IE11+
  • Ziel ist nicht, sämtliche jQuery-Funktionen identisch nachzubilden; die von Cash implementierten Funktionen sind jedoch größtenteils auf Kompatibilität mit der jQuery-API ausgelegt
  • Nutzer, die von jQuery migrieren, können den migration guide lesen

Vergleich von Größe und Funktionen

  • Beim Vergleich der Dateigröße ist Cash kleiner als Zepto 1.2.0 und jQuery Slim 3.4.1
    • Unminified: 36,5 KB
    • Minified: 16 KB
    • Minified & Gzipped: 6 KB
  • jQuery Slim 3.4.1 kommt minified & gzipped auf 24,4 KB; Cash reduziert die Größe im Vergleich dazu um 76,6 %
  • Wenn ein noch kleineres Bundle benötigt wird, können partial builds verwendet werden
  • Im Funktionsvergleich bietet Cash Unterstützung für moderne Browser, aktive Wartung, namespaced Events, eine TypeScript-Codebasis und aus dem Code generierte TypeScript-Typen
  • Bei Partial Builds können in Cash einzelne Methoden ausgeschlossen werden, während Zepto und jQuery Slim als Ausschluss ganzer Module ausgewiesen sind

Verwendung

  • Cash kann über jsDelivr geladen und direkt im Browser verwendet werden
<script src="https://cdn.jsdelivr.net/npm/cash-dom/…;
<script>
  $(function () {
    $('html').addClass ( 'dom-loaded' );
    $('<footer>Appended with Cash</footer>').appendTo ( document.body );
  });
</script>
  • Das npm-Paket ist unter dem Namen cash-dom verfügbar
npm install --save cash-dom
import $ from "cash-dom";

$(function () {
  $('html').addClass ( 'dom-loaded' );
  $('<footer>Appended with Cash</footer>').appendTo ( document.body );
});

API-Struktur

  • $() ist die zentrale Selektormethode von Cash und gibt eine manipulierbare Collection von Nodes zurück
  • Wird eine Funktion übergeben, wird diese ausgeführt, sobald das DOM bereit ist
  • $() kann einen Selektor, einen DOM-Node, eine NodeList, einen HTML-String, eine Cash-Collection oder einen Document-Ready-Callback entgegennehmen
  • Cash bietet im Wesentlichen drei API-Bereiche
    • Query-Selektoren
    • Collection-Methoden
    • Library-Methoden des globalen $-Objekts

Collection-Methoden

  • Collection-Methoden werden aufgerufen, nachdem mit $() eine Collection erstellt wurde, etwa $(element).addClass(className)
  • Die angebotenen Kategorien gliedern sich in Attribute, Collections, CSS, Daten, Dimensionen, Effekte, Events, Formulare, DOM-Manipulation, Offsets und Traversierung
  • Zu den wichtigsten Methoden gehören
    • Klassen/Attribute: addClass, removeClass, toggleClass, attr, prop, removeAttr
    • Collection-Verarbeitung: add, each, eq, filter, first, get, map, slice
    • DOM-Manipulation: append, prepend, before, after, html, text, clone, remove, replaceWith, wrap
    • Events: on, off, one, ready, trigger
    • Traversierung: find, children, closest, parent, parents, siblings, next, prev
  • Einige extra methods werden bereitgestellt, sind aber standardmäßig deaktiviert
  • $.fn ist das Haupt-Prototypobjekt der Collection; darüber lassen sich wie bei Plugins eigene Methoden zu allen Collections hinzufügen

Globale Cash-Methoden

  • Das globale $-Objekt enthält Typprüfungs- und Utility-Methoden
  • Die Typprüfungsmethoden umfassen $.isArray, $.isFunction, $.isNumeric, $.isPlainObject und $.isWindow
  • Zu den Utilities gehören $.guid, $.each, $.extend, $.parseHTML und $.unique
  • $.extend erweitert ein Zielobjekt um die Eigenschaften von Quellobjekten und unterstützt auch Deep Extend
  • $.parseHTML gibt aus einem HTML-String eine Collection zurück, und $.unique gibt ein neues Array ohne Duplikate zurück

Erweiterung und Beiträge

  • Cash kann mit eigenen Methoden erweitert werden; die Vorgehensweise ist unter extending Cash beschrieben
  • Probleme oder Feature Requests können als GitHub-Issue eröffnet werden
  • Der Pull-Request-Workflow besteht aus Repository klonen, Abhängigkeiten installieren, automatische Neukompilierung mit npm run dev, Tests mit npm run test ausführen und bei Bedarf das README aktualisieren
  • Die Lizenz ist MIT

1 Kommentare

 
GN⁺ 2024-11-04
Hacker-News-Meinungen
  • Da Browser heutzutage besser geworden sind, reichen zur Vereinfachung von DOM-Manipulationen oft schon die folgenden zweizeiligen Aliasse
    dqs = document.querySelector.bind(document);
    dqsA = document.querySelectorAll.bind(document);
    So kann man statt document.querySelector('#country') einfach dqs('#country') verwenden und statt document.querySelectorAll('.city') dqsA('.city')
    Für den Rest kann man problemlos einfach native Browser-Funktionen nutzen; meistens importiert man sie aus einem Modul, etwa mit import { dqs, dqsA } from '/lib/js/dqs.js';
    https://github.com/no-gravity/dqs.js

    • Die zwei Zeilen selbst, in denen querySelector/querySelectorAll gebunden werden, wirken nützlich und vernünftig, aber sie per import hereinzuholen ist übertrieben
      Es sind nur zwei einfache Zeilen; es gibt keinen Grund, daraus eine Abhängigkeit zu machen — man kann sie einfach kopieren und einfügen
    • Nicht jede Utility-Funktion ist ein Einzeiler
      Zum Beispiel sind $.fn.one() oder $.fn.on() für mehrere Events mit jQuery/Cash einfacher zu verwenden, und wenn man sich die interne Implementierung ansieht, steckt ziemlich viel Arbeit darin: https://github.com/fabiospampinato/cash/blob/master/src/even...
    • querySelectorAll() liefert keine Live-Collection, daher wird es oft direkt so in ein Array umgewandelt
      dqsA = s => Array.from(document.querySelectorAll(s));
      Dadurch kann man auf dem Ergebnis sofort Array-Methoden wie .map() oder .filter() verwenden, wodurch es sich ähnlich wie jQuery anfühlt
    • Ich habe kürzlich versucht, eine React-Abhängigkeit zu entfernen, aber bei der Event-Verarbeitung waren die Unterschiede zwischen Browsern immer noch ziemlich groß
      Zum Beispiel wurde in Safari während der Tests das select-Event eines bestimmten Elements überhaupt nicht ausgelöst, und in manchen Browsern gab es kein Event, wenn nur der Caret bewegt wurde
      Selbst wenn man React-Funktionen wie Komponenten, State oder Props nicht braucht, reichen native Browser-Funktionen nicht unbedingt aus; der Wert von React DOM, Browser-Unterschiede zu überdecken, wurde deutlich
    • Sieht bling.js ähnlich: https://gist.github.com/paulirish/12fb951a8b893a454b32
  • Wenn man fast alle Polyfills entfernt hat, ist der verbleibende dauerhafte Vorteil von jQuery aus meiner Sicht die automatische Listenverarbeitung
    Die Fähigkeit, die Auswahl aller Buttons in einem Formular mit einem einzigen Aufruf aufzuheben, ist anderswo immer noch schwer zu erreichen, und bei Parent-Queries ist es ähnlich
    Das größte Implementierungsproblem ist allerdings, dass es still fehlschlägt, wenn die Liste leer ist
    Weil ich schon zu viele solcher Bugs behoben habe, die entstanden sind, nachdem der DOM-Baum später aus Layout-Gründen refaktoriert wurde, würde ich, wenn ich jQuery heute neu bauen würde, standardmäßig bei einer leeren Menge einen Fehler ausgeben und nur dann per Chain-Aufruf oder Flag still fehlschlagen, wenn es wirklich egal ist
    Früher habe ich ein paar Stunden damit verbracht zu prüfen, ob man Sizzle herauslösen und es in diese Richtung ändern könnte, bin aber nicht weitergegangen
    Letztlich hängt jQuery auch mit der alten Debatte Library versus Framework zusammen, und nach langer Arbeit mit Single-Page-Apps in riesigen Frameworks fühlt es sich so an, als käme das Tal der Enttäuschung wieder näher

    • Dass man sich nicht darum kümmern muss, ob ein Selektor tatsächlich zu Elementen passt, ist für viele der Reiz von jQuery
      Wenn man anweist, alle .foo zu verstecken, werden sie versteckt, falls es sie gibt, und wenn nicht, passiert nichts — ein Fire-and-forget-Ansatz, ähnlich wie bei CSS
      Wenn man .foo { color: red; } schreibt und es im Dokument kein .foo gibt, hat das außer einem kleinen Overhead keine Nebenwirkungen
    • In modernen Browsern kann man auch alle Elemente eines Query-Ergebnisses ziemlich sauber in einer Zeile ändern
      document.querySelectorAll('input[type=checkbox]').forEach((i) => i.checked = false);
      Das nutzt eine iterierbare NodeList und Iterator-Hilfsfunktionen
      Viele Parent-Queries lassen sich mit element.closest() erledigen
    • Ich stimme völlig zu, dass der Standard streng sein sollte
      Bei jQuery-basiertem Code müsste jeder Entwickler alle Selektoren des Projekts kennen und sie aktualisieren, wenn sich das DOM ändert — was natürlich unmöglich ist
      Es scheint möglich, die jQuery-Initialisierungsfunktion durch eine eigene Implementierung zu ersetzen und so eine Längenprüfung zu erzwingen
  • In einer Situation, in der Mainstream-Websites buchstäblich Megabytes an JavaScript ausliefern, verstehe ich nicht, warum man eine ganze Bibliothek mit weniger Funktionen neu schreiben sollte, nur um 50 KB zu sparen

    • Unabhängig von diesem Paket selbst irritiert mich diese Logik jedes Mal, wenn ich sie in jQuery-Diskussionen sehe
      Viele Artikel sprechen sich wegen Paketgröße und Bandbreitenbeschränkungen gegen jQuery aus und verteidigen gleichzeitig SPA-Frameworks, die viel mehr Bandbreite verbrauchen
      Das ist völlig unsinniges Cargo-Cult-Denken
    • Diese Logik klingt ähnlich wie „Warum zum Mittagessen einen Salat machen, wenn große Fast-Food-Ketten Mahlzeiten mit 1600 Kalorien verkaufen?“ oder „Warum Hypothekenzinsen vergleichen, wenn die Staatsverschuldung fast 35 Billionen Dollar beträgt?“
      In allen drei Fällen ist die Antwort dieselbe: Das Große bin nicht ich, und ich mache etwas anderes, Kleineres
      Eine weitere Antwort ist: Wenn etwas zu groß ist, klingt kleiner werden nach einer Lösung
      Letztlich scheint die eigentliche Frage zu sein, warum Entwickler Zeit damit verbringen, eine Bibliothek neu zu schreiben, aber so überraschend ist das nicht
      Ein erheblicher Teil des Programmierens besteht darin, bereits Vorhandenes neu zu schreiben — weil es beruflich nötig ist, weil man ein etwas anderes Verhalten oder andere Performance-Eigenschaften braucht oder weil man einfach lernen möchte, wie es funktioniert
    • Statt „Warum überhaupt?“ zu fragen, kann die Akzeptanz kleiner Abhängigkeiten dazu führen, dass auch Abhängigkeiten kleiner werden
    • Mainstream-Websites sind eher Müll zur Auslieferung von Werbung und sollten nicht der Maßstab für unsere Arbeit sein
    • Es gibt immer noch Leute, die versuchen, insgesamt unter 50 KB JavaScript auszuliefern
  • Wenn du nach einer jQuery-Alternative suchst: Ich habe zu lange auf jQuery 4.0 gewartet und am Ende etwas Eigenes gebaut, das jQuery ähnelt, aber ein paar wichtige Unterschiede hat.
    Animationen, Tweens und Timelines nutzen reines CSS statt des Custom-Systems von jQuery, behandeln einzelne Elemente und Listen transparent und sind eher auf Inline-Nutzung ausgerichtet.

    • Die Dokumentation widerspricht sich
      Bei me() steht, dass es ein einzelnes Element zurückgibt, entweder das erste Element oder null, und bei any() steht, dass es ein Array oder ein leeres Array zurückgibt.
      Die Beispiele darunter wie any('button')?.forEach(...) und any('button')?.map(...) deuten aber an, dass auch null möglich sein könnte.
      Ich bin verwirrt, ob any() wie oben beschrieben immer ein Array zurückgibt oder ob wie in den Beispielen unten auch null möglich ist.
    • Gut
      Besonders interessiert mich Lokalität des Verhaltens.
      Mich würde interessieren, welche Erfahrungen es mit currentScript.parentElement gibt.
      Als ich letzten Monat kurz recherchiert habe, hatte ich den Eindruck, dass es in Randfällen vermutlich nicht zuverlässig ist, aber ich weiß nicht mehr genau, wann das war.
      Ich bin nicht tief eingestiegen, und es freut mich, dass es zum Laufen gebracht wurde.
      Unter der Annahme, dass es nicht async oder module ist, müsste currentScript.parentElement doch auch dann in allen Browsern weiterhin funktionieren, wenn drei aufeinanderfolgende Skripte geladen werden, oder?
      SvelteKit hatte diese Diskussion ebenfalls und implementierte am Ende zufällige IDs, um das Zielelement anzugeben: https://github.com/sveltejs/kit/issues/2221
  • Beim Lesen des Migration Guides habe ich ein paar Dinge gelernt, die jQuery kann, Cash aber nicht, die ich nicht kannte und irgendwann einmal ausprobieren könnte.
    https://github.com/fabiospampinato/cash/blob/master/docs/mig...

  • Als Erweiterungsziel wäre es schön, mit TypeScript-Template-String-Magie den Elementtyp exakt abzuleiten.
    Zum Beispiel könnte statisch erkannt werden, dass $('div#name') ein HTMLDivElement ist.

    • Das Paket heißt typed-query-selector.
      Ein echtes Nutzungsbeispiel gibt es hier: https://github.com/GoogleChrome/lighthouse/blob/main/types/i...
    • Elixir und einige andere Sprachen können so etwas mit Pattern Matching und dem Typsystem hinbekommen, aber viele Sprachen nicht.
      Ich weiß nicht, ob das in TypeScript möglich ist, und sehe auch nicht richtig, wie es gehen könnte.
  • Ich habe gehört, jQuery 4 sei die jQuery-Alternative für moderne Browser.

  • In einer Browser-Erweiterung, an der ich arbeite, habe ich das anfangs verwendet, bin aber am Ende zu einer JSX-Bibliothek gewechselt.
    Sobald man über den Bereich „einfache App“ hinauskommt, wird jQuery schnell zu Code, der schwer nachzuvollziehen ist; das empfinde ich auch als jemand, der selbst eine von jQuery inspirierte Bibliothek gebaut hat.
    Am Ende muss man das passende Werkzeug für die Aufgabe verwenden.
    [1]: https://github.com/aleclarson/dough
    Wenn man jQuery in mittelgroßen bis großen Apps gut beherrschen kann, ist das in Ordnung, aber es ist nicht mein Geschmack.

  • Früher, als ich JS reduzieren wollte, habe ich https://github.com/filamentgroup/shoestring verwendet.
    Der Hauptgrund war, dass es Custom Builds bot, in die man nur das packt, was man wirklich braucht.
    Cash scheint etwas Ähnliches zu haben, aber es ist in der Dokumentation etwas versteckter: https://github.com/fabiospampinato/cash/blob/master/docs/par...
    Wenn ich es verwenden würde, würde ich das wohl zuerst ausprobieren.
    Trotzdem denke ich immer noch, dass es heutzutage die bessere Wahl ist, direkt das zu nutzen, was der Browser bereitstellt.
    Das ist tatsächlich ziemlich gut, und jQuery ist nicht mehr unbedingt nötig.
    Besonders wenn man bedenkt, dass selbst eine kleine jQuery-Alternative 6 kB groß ist, während Preact, eine React-ähnliche Bibliothek, nur halb so groß ist.

  • Ich bin mir nicht sicher, ob es viel mehr hilft, als bestehenden Web-APIs Aliasse zu geben.