Verwenden von Scala.js mit NPM und Browserify
Veröffentlicht: 2022-03-11Wenn Sie Scala.js, den Compiler der Scala-Sprache für JavaScript, verwenden, finden Sie die standardmäßige Abhängigkeitsverwaltung von Scala.js in der modernen JavaScript-Welt möglicherweise zu einschränkend. Scala.js verwaltet Abhängigkeiten mit WebJars, während JavaScript-Entwickler Abhängigkeiten mit NPM verwalten. Da von NPM erzeugte Abhängigkeiten serverseitig sind, ist normalerweise ein zusätzlicher Schritt mit Browserify oder Webpack zum Generieren von Browsercode erforderlich.
In diesem Beitrag werde ich beschreiben, wie Scala.js mit der Fülle von JavaScript-Modulen integriert wird, die auf NPM verfügbar sind. In diesem GitHub-Repository finden Sie ein funktionierendes Beispiel für die hier beschriebenen Techniken. Anhand dieses Beispiels und durch Lesen dieses Beitrags können Sie Ihre JavaScript-Bibliotheken mit NPM sammeln, mit Browserify ein Bundle erstellen und das Ergebnis in Ihrem eigenen Scala.js-Projekt verwenden. All dies, ohne Node.js zu installieren, da alles von SBT verwaltet wird.
Verwalten von Abhängigkeiten für Scala.js
Heutzutage wird das Schreiben von Anwendungen in Sprachen, die zu JavaScript kompiliert werden, zu einer weit verbreiteten Praxis. Immer mehr Menschen wechseln zu erweiterten JavaScript-Sprachen wie CoffeeScript oder TypeScript oder zu Transpilern wie Babel, mit denen Sie heute ES6 verwenden können. Gleichzeitig wird das Google Web Toolkit (ein Compiler von Java zu JavaScript) hauptsächlich für Unternehmensanwendungen verwendet. Aus diesen Gründen halte ich als Scala-Entwickler die Verwendung von Scala.js nicht mehr für eine seltsame Wahl. Der Compiler ist schnell, der produzierte Code ist effizient und insgesamt ist es nur eine Möglichkeit, dieselbe Sprache sowohl im Frontend als auch im Backend zu verwenden.
Allerdings ist der Einsatz von Scala-Tools in der JavaScript-Welt noch nicht zu 100 Prozent selbstverständlich. Manchmal muss man die Lücke vom JavaScript-Ökosystem zum Scala-Ökosystem füllen. Scala.js hat als Sprache eine hervorragende Interoperabilität mit JavaScript. Da Scala.js ein Compiler für die Scala-Sprache zu JavaScript ist, ist es sehr einfach, Scala-Code mit vorhandenem JavaScript-Code zu verbinden. Am wichtigsten ist, dass Sie mit Scala.js typisierte Schnittstellen (oder Fassaden) erstellen können, um auf nicht typisierte JavaScript-Bibliotheken zuzugreifen (ähnlich wie bei TypeScript). Für Entwickler, die an stark typisierte Sprachen wie Java, Scala oder sogar Haskell gewöhnt sind, ist JavaScript zu locker typisiert. Wenn Sie ein solcher Entwickler sind, liegt der Hauptgrund wahrscheinlich darin, dass Sie Scala.js möglicherweise verwenden möchten, um eine (stark) typisierte Sprache zusätzlich zu einer nicht typisierten Sprache zu erhalten.
Ein Problem in der Standard-Toolchain von Scala.js, die auf SBT basiert und noch etwas offen gelassen wird, ist: Wie bindet man Abhängigkeiten, wie zusätzliche JavaScript-Bibliotheken, in sein Projekt ein? SBT ist auf WebJars standardisiert, daher sollten Sie WebJars verwenden, um Ihre Abhängigkeiten zu verwalten. Leider hat es sich meiner Erfahrung nach als unzureichend erwiesen.
Das Problem mit WebJars
Wie bereits erwähnt, basiert die Standardmethode von Scala.js zum Abrufen von JavaScript-Abhängigkeiten auf WebJars. Scala ist schließlich eine JVM-Sprache. Scala.js verwendet SBT hauptsächlich zum Erstellen von Programmen, und schließlich eignet sich SBT hervorragend zum Verwalten von JAR-Abhängigkeiten.
Aus diesem Grund wurde das WebJar-Format genau für den Import von JavaScript-Abhängigkeiten in der JVM-Welt definiert. Ein WebJar ist eine JAR-Datei mit Web-Assets, wobei eine einfache JAR-Datei nur kompilierte Java-Klassen enthält. Die Idee von Scala.js ist also, dass Sie Ihre JavaScript-Abhängigkeiten importieren sollten, indem Sie einfach WebJar-Abhängigkeiten hinzufügen, auf ähnliche Weise fügt Scala JAR-Abhängigkeiten hinzu.
Nette Idee, außer es funktioniert nicht
Das größte Problem bei Webjars ist, dass eine beliebige Version einer zufälligen JavaScript-Bibliothek selten als WebJar verfügbar ist. Gleichzeitig sind die allermeisten JavaScript-Bibliotheken als NPM-Module verfügbar. Es gibt jedoch eine vermeintliche Brücke zwischen NPM und WebJars mit einem automatisierten npm-to-webjar
Packager. Also habe ich versucht, eine Bibliothek zu importieren, die als NPM-Modul verfügbar ist. In meinem Fall war es VoxelJS, eine Bibliothek zum Erstellen von Minecraft-ähnlichen Welten auf einer Webseite. Ich habe versucht, die Bibliothek als WebJar anzufordern, aber die Brücke ist einfach fehlgeschlagen, weil der Deskriptor keine Lizenzfelder enthält.
Sie können diese frustrierende Erfahrung auch aus anderen Gründen mit anderen Bibliotheken erleben. Einfach ausgedrückt sieht es so aus, als ob Sie nicht auf jede Bibliothek in freier Wildbahn als WebJar zugreifen können. Die Anforderung, WebJars für den Zugriff auf JavaScript-Bibliotheken zu verwenden, scheint zu einschränkend zu sein.
Geben Sie NPM und Browserify ein
Wie ich bereits erwähnt habe, ist das Standardpaketierungsformat für die meisten JavaScript-Bibliotheken der Node Package Manager oder NPM, der in jeder Version von Node.js enthalten ist. Durch die Verwendung von NPM können Sie problemlos auf fast alle verfügbaren JavaScript-Bibliotheken zugreifen.
Beachten Sie, dass NPM der Node -Paketmanager ist. Node ist eine serverseitige Implementierung der V8-JavaScript-Engine und installiert Pakete, die serverseitig von Node.js verwendet werden. So wie es ist, ist NPM für den Browser nutzlos. Die Nützlichkeit von NPM wurde jedoch dank des allgegenwärtigen Browserify-Tools, das natürlich auch als NPM-Paket vertrieben wird, um eine Weile auf die Arbeit mit Browseranwendungen erweitert.
Browserify ist, einfach ausgedrückt, ein Packager für den Browser. Es sammelt NPM-Module, die ein „Bündel“ erzeugen, das in einer Browseranwendung verwendet werden kann. Viele JavaScript-Entwickler arbeiten auf diese Weise – sie verwalten Pakete mit NPM und browserifizieren sie anschließend, um sie in der Webanwendung zu verwenden. Beachten Sie, dass es andere Tools gibt, die auf die gleiche Weise funktionieren, wie z. B. Webpack.
Füllen der Lücke von SBT zu NPM
Aus den gerade beschriebenen Gründen wollte ich eine Möglichkeit, Abhängigkeiten aus dem Web mit NPM zu installieren, Browserify aufzurufen, um Abhängigkeiten für den Browser zu sammeln, und sie dann mit Scala.js zu verwenden. Die Aufgabe stellte sich als etwas komplizierter heraus, als ich erwartet hatte, aber immer noch möglich. Tatsächlich habe ich den Job gemacht und ich beschreibe ihn hier.
Der Einfachheit halber habe ich mich für Browserify entschieden, auch weil ich fand, dass es möglich ist, es innerhalb von SBT auszuführen. Ich habe es nicht mit Webpack versucht, obwohl ich denke, dass es auch möglich ist. Zum Glück musste ich nicht im luftleeren Raum anfangen. Es sind bereits einige Teile vorhanden:
- SBT unterstützt bereits NPM. Das für das Play Framework entwickelte Plugin
sbt-web
kann NPM-Abhängigkeiten installieren. - SBT unterstützt die Ausführung von JavaScript. Dank des
sbt-jsengine
können Sie Node-Tools ausführen, ohne Node selbst zu installieren. - Scala.js kann ein generiertes Bundle verwenden. In Scala.js gibt es eine Verkettungsfunktion, um beliebige JavaScript-Bibliotheken in Ihre Anwendung einzubinden.
Mithilfe dieser Funktionen habe ich eine SBT-Aufgabe erstellt, die die NPM-Abhängigkeiten herunterladen und dann Browserify aufrufen kann, wodurch eine bundle.js
-Datei erstellt wird. Ich habe versucht, die Prozedur in die Kompilierungskette zu integrieren, und ich kann das Ganze automatisch ausführen, aber die Bündelung bei jeder Kompilierung verarbeiten zu müssen, ist einfach zu langsam. Außerdem ändern Sie Abhängigkeiten nicht ständig; Daher ist es vernünftig, dass Sie gelegentlich manuell ein Bundle erstellen müssen, wenn Sie Abhängigkeiten ändern.
Meine Lösung bestand also darin, ein Teilprojekt zu erstellen. Dieses Teilprojekt lädt JavaScript-Bibliotheken mit NPM und Browserify herunter und verpackt sie. Dann habe ich einen bundle
Befehl hinzugefügt, um das Sammeln von Abhängigkeiten durchzuführen. Das resultierende Bundle wird zu Ressourcen hinzugefügt, die in der Scala.js-Anwendung verwendet werden sollen.
Sie sollten dieses „Bundle“ manuell ausführen, wenn Sie Ihre JavaScript-Abhängigkeiten ändern. Wie erwähnt, ist es in der Kompilierungskette nicht automatisiert.
So verwenden Sie den Bundler
Wenn Sie mein Beispiel verwenden möchten, gehen Sie wie folgt vor: Checken Sie zuerst das Repository mit dem üblichen Git-Befehl aus.
git clone https://github.com/sciabarra/scalajs-browserify/
Kopieren Sie dann den bundle
Ordner in Ihr Scala.js-Projekt. Es ist ein Teilprojekt zum Bündeln. Um eine Verbindung zum Hauptprojekt herzustellen, sollten Sie die folgenden Zeilen in Ihre build.sbt
-Datei einfügen:
val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")
Außerdem müssen Sie Ihrer project/plugins.sbt
-Datei die folgenden Zeilen hinzufügen:
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
Sobald Sie fertig sind, haben Sie einen neuen Befehl, bundle
, mit dem Sie Ihre Abhängigkeiten sammeln können. Es generiert eine Datei bundle.js
in Ihrem Ordner src/main/resources
.
Wie ist das Bundle in Ihrer Scala.js-Anwendung enthalten?
Der gerade beschriebene bundle
Befehl sammelt Abhängigkeiten mit NPM und erstellt dann eine bundle.js
. Wenn Sie die Befehle fastOptJS
oder fullOptJS
ausführen, erstellt ScalaJS eine myproject-jsdeps.js
, einschließlich aller Ressourcen, die Sie als JavaScript-Abhängigkeit angegeben haben, also auch Ihre bundle.js
. Um gebündelte Abhängigkeiten in Ihre Anwendung aufzunehmen, sollten Sie die folgenden Einschlüsse verwenden:

<script src="target/scala-2.11/myproject-jsdeps.js"></script> <script src="target/scala-2.11/myproject-fastopt.js"></script> <script src="target/scala-2.11/myproject-launcher.js"></script>
Ihr Bundle ist jetzt als Teil von myproject-jsdeps.js
. Das Bundle ist fertig und wir haben unsere Aufgabe (Abhängigkeiten importieren und in den Browser exportieren) einigermaßen abgeschlossen. Der nächste Schritt ist die Verwendung der JavaScript-Bibliotheken, was ein anderes Problem ist, ein Scala.js-Codierungsproblem. Der Vollständigkeit halber werden wir nun besprechen, wie das Bundle in Scala.js verwendet und Fassaden erstellt werden, um die von uns importierten Bibliotheken zu verwenden.
Verwenden einer generischen JavaScript-Bibliothek in Ihrer Scala.js-Anwendung
Zur Erinnerung: Wir haben gerade gesehen, wie Sie mit NPM und Browserify ein Bundle erstellen und dieses Bundle in Scala.js einbinden. Aber wie können wir eine generische JavaScript-Bibliothek verwenden?
Der vollständige Prozess, den wir im Rest des Beitrags ausführlich erläutern werden, ist:
- Wählen Sie Ihre Bibliotheken aus dem NPM aus und fügen Sie sie in die
bundle/package.json
. - Laden Sie sie mit
require
in eine Bibliotheksmoduldatei inbundle/lib.js
. - Schreiben Sie Scala.js-Fassaden, um das
Bundle
-Objekt in Scala.js zu interpretieren. - Codieren Sie schließlich Ihre Anwendung mit den neu typisierten Bibliotheken.
Hinzufügen einer Abhängigkeit
Wenn Sie NPM verwenden, müssen Sie Ihre Abhängigkeiten in die Datei package.json
, was der Standard ist.
Nehmen wir also an, Sie möchten zwei berühmte Bibliotheken wie jQuery und Loadash verwenden. Dieses Beispiel dient nur zu Demonstrationszwecken, da es bereits einen hervorragenden Wrapper für jQuery als Abhängigkeit für Scala.js mit einem geeigneten Modul gibt und Lodash in der Scala-Welt nutzlos ist. Trotzdem denke ich, dass es ein gutes Beispiel ist, aber nimm es nur als Beispiel.
Gehen Sie also zur Website npmjs.com
und suchen Sie die Bibliothek, die Sie verwenden möchten, und wählen Sie auch eine Version aus. Nehmen wir an, Sie wählen jquery-browserify
Version 13.0.0 und lodash
Version 4.3.0. Aktualisieren Sie dann den dependencies
Ihrer packages.json
wie folgt:
"dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }
Behalten Sie browserify
immer bei, da es zum Generieren des Bundles benötigt wird. Sie müssen es jedoch nicht in das Paket aufnehmen.
Beachten Sie, dass Sie, wenn Sie NPM installiert haben, einfach aus dem bundle
Verzeichnis eingeben können:
npm install --save jquery-browserify lodash
Außerdem wird die package.json
aktualisiert. Wenn Sie NPM nicht installiert haben, machen Sie sich keine Sorgen. SBT installiert eine Java-Version von Node.js und NPM, lädt die erforderlichen JARs herunter und führt sie aus. All dies wird verwaltet, wenn Sie den bundle
Befehl von SBT ausführen.
Exportieren der Bibliothek
Jetzt wissen wir, wie man die Pakete herunterlädt. Der nächste Schritt besteht darin, Browserify anzuweisen, sie in einem Bündel zu sammeln und sie dem Rest der Anwendung zur Verfügung zu stellen.
Browserify ist ein Sammler von require
und emuliert das Node.js-Verhalten für den Browser, was bedeutet, dass Sie irgendwo require
Anforderung haben müssen, um Ihre Bibliothek zu importieren. Da wir diese Bibliotheken nach Scala.js exportieren müssen, generiert das Bundle auch ein JavaScript-Objekt der obersten Ebene mit dem Namen Bundle
. Sie müssen also die lib.js
bearbeiten, die ein JavaScript-Objekt exportiert, und alle Ihre Bibliotheken als Felder dieses Objekts benötigen.
Wenn wir in Scala.js jQuery- und Lodash-Bibliotheken exportieren möchten, bedeutet dies im Code:
module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }
Führen Sie jetzt einfach das Befehlspaket aus bundle
und die Bibliothek wird heruntergeladen, gesammelt und in das Paket eingefügt, sodass sie in Ihrer Scala.js-Anwendung verwendet werden kann.
Zugriff auf das Bundle
Bisher:
- Sie haben das Bundle-Unterprojekt in Ihrem Scala.js-Projekt installiert und richtig konfiguriert.
- Für jede gewünschte Bibliothek haben Sie sie in
package.json
. - Sie haben sie in der
lib.js
benötigt. - Sie haben den
bundle
Befehl ausgeführt.
Als Ergebnis haben Sie jetzt ein JavaScript-Objekt der obersten Ebene Bundle
, das alle Einstiegspunkte für die Bibliotheken bereitstellt, die als Felder dieses Objekts verfügbar sind.
Jetzt können Sie es mit Scala.js verwenden. Im einfachsten Fall können Sie so auf die Bibliotheken zugreifen:
@js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }
Mit diesem Code können Sie auf die Bibliotheken von Scala.js zugreifen. Allerdings ist es nicht so, wie man es mit Scala.js machen sollte, weil die Bibliotheken noch untypisiert sind. Stattdessen sollten Sie typisierte „Fassaden“ oder Wrapper schreiben, damit Sie die ursprünglich nicht typisierten JavaScript-Bibliotheken in einer typisierten schuppigen Weise verwenden können.
Ich kann Ihnen hier nicht sagen, wie Sie die Fassaden schreiben, da dies von der spezifischen JavaScript-Bibliothek abhängt, die Sie in Scala.js einpacken möchten. Ich werde nur ein Beispiel zeigen, um die Diskussion abzuschließen. Weitere Einzelheiten finden Sie in der offiziellen Scala.js-Dokumentation. Sie können auch die Liste der verfügbaren Fassaden konsultieren und den Quellcode zur Inspiration lesen.
Bisher haben wir den Prozess für beliebige, noch nicht zugeordnete Bibliotheken behandelt. Im Rest des Artikels beziehe ich mich auf eine Bibliothek mit einer bereits vorhandenen Fassade, daher ist die Diskussion nur ein Beispiel.
Umhüllen einer JavaScript-API in Scala.js
Wenn Sie require
in JavaScript verwenden, erhalten Sie ein Objekt, das viele verschiedene Dinge sein kann. Es kann eine Funktion oder ein Objekt sein. Es kann sogar nur eine Zeichenfolge oder ein boolescher Wert sein, in diesem Fall wird die Anforderung nur für die require
aufgerufen.
In meinem Beispiel ist jquery
eine Funktion, die ein Objekt zurückgibt, das zusätzliche Methoden bereitstellt. Typische Verwendung ist jquery(<selector>).<method>
. Dies ist eine Vereinfachung, da jquery
auch die Verwendung von $.<method>
zulässt, aber in diesem vereinfachten Beispiel werde ich nicht alle diese Fälle behandeln. Beachten Sie im Allgemeinen, dass bei komplexen JavaScript-Bibliotheken nicht die gesamte API einfach statischen Scala-Typen zugeordnet werden kann. Möglicherweise müssen Sie auf js.Dynamic
zurückgreifen, das eine dynamische (nicht typisierte) Schnittstelle zum JavaScript-Objekt bereitstellt.
Um nur den häufigeren Anwendungsfall zu erfassen, habe ich im Bundle
-Objekt jquery
definiert:
def jquery : js.Function1[js.Any, Jquery] = js.native
Diese Funktion gibt ein jQuery-Objekt zurück. Eine Instanz eines Merkmals wird in meinem Fall mit einer einzigen Methode definiert (eine Vereinfachung, Sie können Ihre eigene hinzufügen):
@js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }
Für die Lodash-Bibliothek modellieren wir die gesamte Bibliothek als JavaScript-Objekt, da es sich um eine Sammlung von Funktionen handelt, die Sie direkt aufrufen können:
def lodash: Lodash = js.native
Wobei das lodash
Merkmal wie folgt ist (ebenfalls eine Vereinfachung, Sie können Ihre Methoden hier hinzufügen):
@js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }
Mit diesen Definitionen können wir jetzt endlich Scala-Code schreiben, indem wir die zugrunde liegenden jQuery- und Lodash-Bibliotheken verwenden, die beide aus NPM geladen und dann durchsucht werden :
object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }
Sie können das vollständige Beispiel hier überprüfen.
Fazit
Ich bin ein Scala-Entwickler und war begeistert, als ich Scala.js entdeckte, weil ich dieselbe Sprache für den Server und den Client verwenden konnte. Da Scala JavaScript ähnlicher ist als Java, ist Scala.js im Browser ziemlich einfach und natürlich. Darüber hinaus können Sie auch leistungsstarke Funktionen von Scala als reichhaltige Sammlung von Bibliotheken, Makros und leistungsstarken IDEs und Build-Tools verwenden. Weitere wichtige Vorteile sind, dass Sie Code zwischen Server und Client freigeben können. Es gibt viele Fälle, in denen diese Funktion nützlich ist. Wenn Sie einen Transpiler für Javascript wie Coffeescript, Babel oder Typescript verwenden, werden Sie bei der Verwendung von Scala.js nicht allzu viele Unterschiede bemerken, aber dennoch viele Vorteile haben. Das Geheimnis besteht darin, das Beste aus jeder Welt zu nehmen und sicherzustellen, dass sie gut zusammenarbeiten.