Michael_K: Best practice für JS-Bibliotheken

Hallo,

gibt es eine (englische) Seite, die beschreibt, wie man eine JS-Bibliothek aufsetzt. Insbesondere geht es mir um die Module bzw. export-Eigenschaft.

Ich finde es aktuell sehr unglücklich, dass viele gute Node-JS Bibliotheken nicht so ohne weiteres im Browser genutzt werden können. Zumindest ist es mein Verständnis, dass die klassischen var x = require('nodename-module') nicht funktioniert bzw. wenn dann nur über hässliche Umwege. Und ziemlich unschön ist es, dass nun ES6 einen eigenen import/export Syntax eingeführt hat, der nicht kompatibel zu dem export/require von Node.js passt.

Gibt es einen Weg, eine JS Bibliothek so zu schreiben, dass diese sowohl vom Brwoser als auch bei Node eingesetzt werden kann?

Gruss Michael

  1. Ich finde es aktuell sehr unglücklich, dass viele gute Node-JS Bibliotheken nicht so ohne weiteres im Browser genutzt werden können. Zumindest ist es mein Verständnis, dass die klassischen var x = require('nodename-module') nicht funktioniert bzw. wenn dann nur über hässliche Umwege.

    Ja. Das JavaScript-Ökosystem ist in diesem Punkt sehr fragmentiert. Da es lange keine Standard-Lösung für das Problem gab, haben viele Köche ihr eigenes Süppchen kochen müssen und wir wissen ja wozu das führt.

    Und ziemlich unschön ist es, dass nun ES6 einen eigenen import/export Syntax eingeführt hat, der nicht kompatibel zu dem export/require von Node.js passt.

    EcmaScript-Module sind der Versuch das Ökosystem zu defragmentieren, indem man eine vereinheitlichte Standard-Lösung anstrebt.

    Gibt es einen Weg, eine JS Bibliothek so zu schreiben, dass diese sowohl vom Brwoser als auch bei Node eingesetzt werden kann?

    Die grundsätzliche Idee ist, dass man die Library erstmal im EcmaScript-Stil entwickeltn und dann Bindings für alle zu unterstütztenden Module-Systeme drumherum baut. In der Praxis hat man meistens sowieso schon ein Buildsytem, wie Babel oder TypeScript, im Einsatz, da kann man dann extra Build-Schritte konfigurieren, um Bindings für diverse Zielplattformen zu erzeugen.

    Hier ist das beispielhaft für TypeScript erklärt: https://www.typescriptlang.org/docs/handbook/2/modules.html#typescripts-module-output-options

    1. Hallo 1unitedpower,

      was da beschrieben wird, sind Code-Generierungen die TS erzeugen kann.

      D.h. ich schreibe den Code in TS und compiliere es dann in ein ES6-Modul, ein CJS Modul oder ein UMD-Modul.

      Ein Modul, das ich überall importieren kann, erzeugt das nicht. Oder ich verstehe dich miss.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Ein Modul, das ich überall importieren kann, erzeugt das nicht. Oder ich verstehe dich miss.

        Ich glaube du versehst mich richtig, also auf diesem Weg erzeugt man verschiedene Build-Artefakte für verschiedene Zielplattformen. Eine wirklich universelle Lösung mit nur einem einzigen Artefakt, dass alle Plattformen unterstützt gibt es AFAIK nicht. umd war ja gewissermaßen der Versuch diesen Weg zu gehen, aber der ist meiner Meinung nach auch wieder antiquitiert.

  2. Hallo Michael_K,

    Ich finde es aktuell sehr unglücklich, dass viele gute Node-JS Bibliotheken nicht so ohne weiteres im Browser genutzt werden können.

    Das hat vor allem einen Grund: AMD vs CJS. Brian Leroux schreibt hier darüber (und erklärt auch die Abkürzungen).

    Node.js hat sich der CJS Moduldefinition verschrieben und die Browser nutzen das AMD-System, das auf einer define-Funktion aufsetzt. So, wie ich das verstehe, passem diese beiden Ansätze nicht gut zusammen. Der Grund ist, dass der Ansatz mit let modul = require("./modul.js") synchron ist. Auf einem Server ist das okay, im Browser geht das einfach nicht. Ich kann ein Script nicht hängen lassen, während ein weiteres File vom Server geholt wird. Deswegen bringt eine mir bekannte AMD Bibliothek - requirejs - einen CJS Adapter mit. Aber: der ist offline, er transformiert das CJS-Modul auf Sourcecode-Ebene in ein für AMD verwendbares Format. Ich habe nicht damit experimentiert, ob man CJS-Module auch direkt mit require.js einbinden kann.

    In diesem Github-Projekt sind diverse Templates aufgeführt, wie man AMD und CJS verheiraten kann. Je nach den Erfordernissen kann man ein einfacheres oder komplexeres Template auswählen. Diese Templates ermöglichen das Schreiben vom Modulen, die von AMD-Loadern wie requirejs oder von CJS Loadern wie Node.js geladen werden können.

    ES6? Fehlanzeige. Mit ES6 Modulen geschah, was Randall Munroe in seiner Weisheit vor 10 Jahren zeichnete: Standards proliferation

    Das schlimmste an ES6 Modulen ist, dass man nicht abfragen kann, ob man exportieren darf. Abfragen kann man (via imports.meta) zwar noch, aber das export-Statement ist kein Statement. Es ist eine Deklaration. Wenn diese Deklaration in einem if oder try/catch auftaucht, bricht das Script mit einem Error ab. Wenn sie auftaucht und das Script nicht mit type="module" geladen wird, bricht das Script mit einem Error ab.

    D.h. sobald man "export" in seinem Script hat, lässt es sich nur noch als type="module" laden.

    Heißt: Entweder ES6, oder UMD. Beides in einer Datei geht nicht.

    Man kann sicherlich einen Toolstack aufbauen, der Dir aus einem Sourcefile beide Modulvarianten erzeugt. Aber zwei Varianten müssen es wohl sein. Und ich hoffe, mich widerlegt jetzt jemand 😉

    Rolf

    --
    sumpsi - posui - obstruxi
    1. ES6? Fehlanzeige. Mit ES6 Modulen geschah, was Randall Munroe in seiner Weisheit vor 10 Jahren zeichnete: Standards proliferation

      Ich bin zugegeben etwas raus aus dem aktuellen JavaScript-Geschehen, aber da möchte ich trotzdem widersprechen. EcmaScript Module genießen massiven Rückhalt aus der Community, darunter befinden sich diverse Schwergewichte. Allen voran wäre natürlich das Standard-Komittee zu nennen, das auch den Rest des Sprachstandards verwaltet. Browser-Hersteller unterstützen EcmaScript-Module inzwischen auch nativ. Microsoft bentuzt die selbe Syntax für TypeScript, das sich auch immer größerer Beliebtheit erfreut. Node.js pusht ebenfalls ES Module. Gefühlt ist das auch heute schon der Weg, der sich der größten Beliebtheit erfreut. Ich habe aber leider keine belastbaren Zahlen finden können.

      Das schlimmste an ES6 Modulen ist, dass man nicht abfragen kann, ob man exportieren darf. Abfragen kann man (via imports.meta) zwar noch, aber das export-Statement ist kein Statement. Es ist eine Deklaration. Wenn diese Deklaration in einem if oder try/catch auftaucht, bricht das Script mit einem Error ab. Wenn sie auftaucht und das Script nicht mit type="module" geladen wird, bricht das Script mit einem Error ab.

      Ob das nun Vor- oder Nachteile sind, darüber lässt sich hervorragend streiten. Der erste Teil klingt so, als wäre er durch "dynamische Imports" gelöst. Davon abgesehen sind dynamische Imports natürlich ein extrem guter Weg sich ins eigene Knie zu schießen. Der Teil über type="module" klingt für mich nach keinem größeren Problem. Die Fehlermeldung sorgt doch dafür, dass der Defekt frühzeitig auffällt und behoben werden kann.

      1. Hallo 1unitedpower,

        ah, sorry, das Posting hatte mehrere Anläufe.

        Ich wollte mich nicht gegen ES6 Module aussprechen - im Gegenteil. Es ist aber ein alles oder nichts Ansatz.

        ES6 Module haben seit Version 12 in Node.js den "experimentell" Zustand verlassen, d.h. man kann sich auf den Standpunkt stellen, dass man ein Modul nur noch als ES6 Modul bereitstellt.

        Ich habe keine Ahnung, wie groß das Legacy-Problem bei Node.js ist. In der Browserwelt schießt man damit zumindest alte Handys, die keine Updates kriegen, und den IE ab.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Ich wollte mich nicht gegen ES6 Module aussprechen - im Gegenteil. Es ist aber ein alles oder nichts Ansatz.

          Okay, dann hatte ich da missverstanden.

          Ich habe keine Ahnung, wie groß das Legacy-Problem bei Node.js ist. In der Browserwelt schießt man damit zumindest alte Handys, die keine Updates kriegen, und den IE ab.

          Eben deshalb empfahl ich ja in meiner Antwort die Verwendung eines Build-Skripts. Damit kann man dann für ältere Plattformen oder solche, die aus anderen Gründen keine native Untersützung anbieten, eine Abwärtskompatibilät herstellen. Aber gut, dass du das nochmal so im Detail aufgedröselt hast. Ich habe in meiner Argumentation ein paar gedankliche Sprünge gemacht, die es schwierig machen ihr zur folgen.

          Um es mal etwas konkreter zu machen: Ich empfehle die Verwendung von tsdx. Da kann ich mit einem Befehl tsdx build --format cjs,esm,umd die Bibliothek für mehrere Zielplattformen erstellen lassen. Das Tool ist insbesondere für die Entwicklung von TypeScript gedacht, aber da TypeScript eine Übermenge von JavaScript ist, sehe ich das eher als Bonus. Eine gute JavaScript-Library sollte die nötigen Informationen mitliefern, die eine IDE für Autovervollständigung und Fehlererkennung benötigt. TypeScript hat in diese Richtung viel zu bieten.

    2. Hallo Rolf,

      vielen Dank für die Links. Ich werde mich damit einlesen und dann wohl entscheidenmüssen.

      Gruss Michael