SP05/16: Scala.js, Gradle vs. SBT
Änderungen beim ScalaProfis-Podcast
»Alles neu macht der Mai«. In diesem Sinne haben wir ein paar kleinere Anpassungen vorgenommen:
- Die Episoden werden nun nicht mehr fortlaufend, sondern nach Jahr und Monat nummeriert — das ist unserer Meinung nach hilfreicher, da man die Episode gleich zeitlich einordnen kann.
- Auf der Übersichtsseite werden nur noch die Themen der jeweiligen Episode aufgeführt.
- Unterabschnitte der Show-Notes einer Episode sind jetzt besser als solche erkennbar.
- Zu viele Kanäle machen die Pflege aufwändig. Daher konzentrieren wir uns auf Twitter und stellen die Google+ Seite ein.
ScalaJS
Sven hat sich mit ScalaJS beschäftigt.
Warum nicht direkt JavaScript?
Für jemanden, der statisch typisierte, mächtige Sprachen gewöhnt ist, ist das Programmieren in JavaScript kein Vergnügen:
- die Auto-Completion in der IDE schlägt entweder nichts oder dutzende von (an dieser Stelle nicht relevanten) Feldern/Methoden vor
- selbst simple Refactorings wie Umbenennungen erfordern viel Handarbeit — je allgemeiner der ursprüngliche Name, desto schlimmer
- keine sinnvolle Modularisierung
- umständliche Erstellung von Klassen
- ungewohntes Sprachverhalten (Scope lokaler Variablen)
- keine Immutability
- geschwätzige Syntax (zumindest im Vergleich zu Scala)
Einiges wird mit ECMAScript 6 besser (Module, Arrow-Syntax für Funktionen, Klassen, let
-Deklarationen für »korrektes« Scoping, const
). In der Realität kommt man aber um einen ES6-nach-ES5-Transpiler wie Babel nicht herum.
Was für Alternativen gibt es zu JavaScript?
Wenn man eh einen Transpiler nutzt kann man auch gleich eine brauchbare Programmiersprache nehmen. Folgende Alternativen habe ich mir angeschaut:
- CoffeeScript: Dynamisch, bringt somit nur marginale Verbesserungen, dafür ohne Runtime-Overhead (Library)
- TypeScript: Sehr populär, statisch typisiert, erfordert somit Fassaden (für viele Libraries verfügbar), nicht sonderlich mächtig, kaum Runtime-Overhead
- Kotlin: JavaScript-Generierung von Anfang an vorgesehen, schmale Standard-Library, aber noch nicht verfügbar
- Scala.js: Volle Mächtigkeit von Scala zum Preis großer Runtime-Artefakte
Entscheidung für Scala.js
Maßgeblich war für mich die geplante Förderung von Scala.js durch das Scala Center — das suggeriert Ernsthaftigkeit und Zukunftssicherheit.
Ebenfalls interessant: Code-Sharing zwischen Frontend und Backend (ist natürlich via Node.js auch mit JavaScript möglich).
Wie funktioniert Scala.js
Scala.js ist im Wesentlichen ein Compile-Plug-In, das den Scala AST (der zuvor den größten Teil der Scala-To-JVM-Pipeline durchlaufen hat und dem entsprechend vereinfacht ist) in einen JavaScript AST übersetzt (Link).
Zum Zugriff auf JavaScript-Funktionen werden Fassaden benötigt. Die besorgt man sich entweder fertig oder man erstellt sie einfach selbst:
@js.native
trait Window extends js.Object {
val document: HTMLDocument = js.native
var location: String = js.native
def innerWidth: Int = js.native
def innerHeight: Int = js.native
def alert(message: String): Unit = js.native
def open(url: String, target: String,
features: String = ""): Window = js.native
def close(): Unit = js.native
}
@js.native
object GlobalDomDefinition extends js.GlobalScope {
val window: Window = js.native
}
Um Scala-Klassen aus JavaScript nutzbar zu machen müssen diese exportiert werden:
@JSExport
class Foo(val x: Int) {
@JSExport
def square(): Int = x*x // note the (), omitting them has a different behavior
@JSExport("foobar")
def add(y: Int): Int = x+y
}
Was geht nicht?
Es kann fast alles genutzt werden, was Scala bietet. Ausgeschlossen sind im Wesentlichen Reflection-basierte Funktionalitäten.
Wie sieht’s mit Performance und Overhead aus?
Um die Performance zu testen hat das Scala.js-Team einen Benchmark nach Scala.js überführt und die Ergebnisse auf der Scala.js-Seite veröffentlicht. Extrakt: Die Ausführung via Scala.js lag je nach Anwendungsfall 10 bis 20% über der nativen JavaScript-Lösung.
Ausnahme: Bei einem Collection-lastigen Test hat die Nutzung der Scala-Collections die Ausführungszeit um den Faktor 2.2 steigen lassen. Nachdem der Benchmark auf die Nutzung von JavsScript-Arrays umgestellt wurde, war der Benchmark wieder nur ca. 20% langsamer.
Was die Größe des resultierenden JavaScripts angeht: Während der Entwicklung wird zugunsten der Turnaround-Zeiten eine schnelle Optimierung durchgeführt. Ein einfaches AngularJS-Projekt hatte hier bei mir ca. 1 MB Größe. Der Release-Build wird unter Einsatz des Google Closure-Compilers größenoptimiert und lieferte bei mir 196 kB.
Aber Achtung: Der Google Closure Compiler macht die Anwendung wieder etwas langsamer.
Aufbau eines Scala.js-Projekts
Scala.js-Projekte werden mit SBT erstellt. Dabei kommen drei Source-Verzeichnisse zum Einsatz:
jvm
: Scala-Sourcen hin, die nur für die JVM compiliert werdenjs
: Scala-Sourcen, die nur nach JavaScript compiliert werdenshared
: Scala-Sourcen, die vonjvm
- undjs
-Sourcen genutzt werden können — diese werden also sowohl für die JVM als auch für JavaScript compiliert.
Ich habe Scala.js in einem Play-Projekt genutzt.
Testing & Debugging
ScalaTest 3 bringt als Haupt-Feature die Unterstützung für Scala.js — somit können die Tests mit einem bewährten Framework geschrieben werden.
Debugging im Browser funktioniert, da Scala.js Source-Maps generiert. Durch die Optimierung, sind allerdings teilweise Variablen nicht verfügbar und auf diversen Source-Zeilen können keine Breakpoints gesetzt werden. Evtl. müsste man mal probieren die Fast-Optimization im Entwicklungsprozess zu deaktivieren.
Erfahrungen
Mit Scala.js macht Frontend-Entwicklung endlich Spaß und wird produktiv! Wenn Fehler auftreten kann die Suche nach der Ursache aufwändig werden, aber wie immer hilft Erfahrung hier weiter.
Aufgrund des Overheads, den die Standard-Library produziert, ist Scala.js sicherlich nicht zum Aufwerten einer normalen Web-Seite geeignet. Geht es aber um eine vollwertige Web-Applikation, mit der sich der Anwender längere Zeit beschäftigt, steht in meinen Augen einer Nutzung in Produktivsystemen nichts mehr im Wege.
Links
- Frontend to Backend: Everything is on Scala
- Hands-on Scala.js (freies »eBook«)
- Scala-Js-Fiddle
Gradle vs. SBT
Gradle
- In Groovy geschriebene DSL für Build-System
- Plug-Ins können in jeder JVM Sprache geschrieben werden (also auch in Scala)
- Convention over Configuration
- Hervorragende Dokumentation
- Ant-Tasks können direkt aus Gradle aufgerufen werden
Installation
Gradle muss nicht von allen Entwicklern installiert werden. Wenn ein Projekt neu aufgesetzt wird kann ein Entwickler der Gradle
installiert hat den Gradle-Wrapper erzeugen. Der Gradle-Wrapper wird dann zusammen mit dem restlichen Code im Repository abgelegt
und kann von allen anderen Entwicklern direkt verwendet werden.
Die konkrete Version ist dann Build-Skript festgelegt und wird bei der ersten Verwendung runter geladen.
Definieren von Abhängigkeiten
scalaMajorVersion = 2.10
scalaVersion = 2.10.5
dependencies {
compile "org.scala-lang:scala-library:$scalaVersion"
compile "org.scala-lang:scala-reflect:$scalaVersion"
compile("com.escalatesoft.subcut:subcut_$scalaMajorVersion:2.0") {
exclude module: 'scala-compiler'
}
}
Definieren von Tasks
task taskA << {
println("Hello Gradle!")
}
Etwas komplizierter:
task taskA {
description "Gibt 'Hello Gradle!' aus"
doLast {
println("Hello Gradle!")
}
}
Aufrufen von anderen Tasks
Aus einem Gradle-Task heraus können andere Tasks nicht direkt ausgeführt werden. Das geht nur indirekt über Abhängigkeiten:
task taskA {
...
}
task taskB {
...
}
task taskC {
dependsOn(taskA, taskB)
...
}
Wenn sichergestellt werden muss, dann beim Aufruf von Task C erst A und dann B ausgeführt wird kann entweder noch eine Abhängigkeit
von Task B zu Task eingerichtet werden
taskB.dependsOn taskA
oder (z.B. wenn Task B auch unabängig von Task A ausgeführt werden kann) über mustRunAfter
eine Reihennfolge festgelegt werden:
taskB.mustRunAfter taskA
Zugriff auf Einstellungen
task printMainTargetDirectory << {
println(sourceSets.main.output.classesDir)
}
Solange man den Namen kennt kann man dynamisch auf jede Einstellung zugreifen. Allerdings ist das nicht typsicher und Tippfehler
fallen auch erst zur Laufzeit auf.
SBT
- In Scala geschriebene DSL für Build-Systeme (wobei Benjamin findet, dass SBT eher eine API als eine DSL ist)
- Convention over Configuration
- Sehr gute Unterstützung von inkrementeller Compilierung
- Man arbeitet in einer Konsole, so dass nicht für jeden Befehl die Build-Skripte neu kompiliert werden müssen
- Cross-Compile für verschiedene Scala Versionen
Installation
SBT muss nicht installiert werden. Auf der Homepage kann man sich ein sehr schlankes Paket runterladen, dass so wie der Gradle-Wrapper
nur die unbendingt notwendigen Dateien enthält und den notwendigen Rest bei Bedarf nach lädt. Dieses Paket kann man auch im Repository
ablegen.
Die in einem Projekt verwendete SBT Version wird in project/build.properties
definiert.
Definieren von Abhängigkeiten
scalaVersion in Global := "2.10.5"
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-library" % scalaVersion.value,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"com.escalatesoft.subcut" %% "subcut" % "2.0" exclude("org.scala-lang", "scala-compiler")
)
Definieren von Tasks
val taskA = taskKey[Unit]("Gibt 'Hello sbt!' aus")
taskA := {
println("Hello sbt!")
}
Aufrufen von anderen Tasks
val taskA = taskKey[Unit]("...")
val taskB = taskKey[Unit]("...")
val taskC = taskKey[Unit]("...")
taskC := {
taskA.value
taskB.value
}
Anders als man denken könnte führt taskC
damit aber nicht taskA
und taskB
in der angegebenen Reihennfolge aus — zumindest
nicht solange es keine Abhängigkeiten zwischen den Tasks gibt. SBT führt die Tasks parallel aus und abhängig von den Tasks
und eventuellen Abhängigkeiten zu anderen Tasks kann es sogar sein, dass taskB
vor taskA
ausgeführt wird.
Wenn man eine bestimmte Reihenfolge erzwingen will kann man das wie bei Gradle über Abhängigkeiten machen oder über Def.sequential
:
val taskA = taskKey[Unit]("...")
val taskB = taskKey[Unit]("...")
val taskC = taskKey[Unit]("...")
taskA := ...
taskB := ...
taskC := Def.sequential(taskA, taskB).value
Zugriff auf Einstellungen
val printMainTargetDirectory = taskKey[Unit]("Prints the main target directory")
printMainTargetDirectory := println((classDirectory in Compile).value)
Der Zugriff auf Einstellungen oder dem Rückgabewert einer Task erfolgt immer über .value
. Das funktioniert aber nur innerhalb
einer Task oder wenn man den Wert für eine andere Einstellung festlegt.
Gradle vs. SBT
Vorteile Gradle
- Dokumentation
- Einfacher Zugriff auf Einstellungen (allerdings nicht Typsicher)
- Besser lesbarer und verständlicherer Code
Vorteile SBT
- Konsole
- Performance (insbesonders inkrementelles compilieren, ausführen der durch eine Änderung betroffenen Tests)
- Typsicher
- Ökosystem
Links
Scala Native
Your favourite language gets closer to bare metal.
Ahead-of-time-Compilierung für Scala inklusive Low-Level-Operationen wie Structs und Pointer und Interoperabilität mit C.
Projekt vom EPFL.
Link
Scala Compiler Hacking
Miles Sabin hat in einem Blog erläutert wie er den PR für SI-2712 entwickelt hat.
Links
clippy
Clippy ist ein SBT-Plugin, dass schwer verständliche Compiler-Fehler in verständliche übersetzt.
Beispiel
[error] TheNextFacebook.scala:16: type mismatch;
[error] found : akka.http.scaladsl.server.StandardRoute
[error] required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]
[error] Http().bindAndHandle(r, "localhost", 8080)
wird übersetzt in
[error] Clippy advises: did you forget to define an implicit akka.stream.ActorMaterializer?
[error] It allows routes to be converted into a flow.
[error] You can read more at http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0/scala/http/routing-dsl/index.html
Links
https://github.com/softwaremill/scala-clippy
Einbetten von Dateiinhalten via Macro: Leider nicht praxistauglich
Sven hatte von seiner Lösung geschwärmt, bei der Inhalte einer HTML-Datei via Macro als String
eingebettet wurden. Leider ist diese Lösung nicht praxistauglich, da SBT die Abhängigkeit zwischen den beiden Sourcen nicht korrekt erkennt: Ändert sich die HTML-Datei, so erkennt SBT nicht, dass es die importierende Scala-Source-Datei neu kompilieren muss. Somit ist diese Lösung nicht praktisch einsetzbar.
Expressions in string interpolation
Unser Hörer Daniel Jentsch hat sich Svens Fragestellung aus der letzten Episode angenommen, wie man einen String-Interpolator erstellt, der nicht das Ergebnis eines Ausdrucks einbettet, sondern den Ausdruck selbst (um z.B. Reafctoring-sichere Templates im Umfeld von Scala.js zu erstellen).
Das Ergebnis findet Ihr im Artikel Expressions in string interpolation
Libraray-Liste »Awesome Scala«
Awesome Scala ist eine von der Community erstellte Liste von nützlichen Scala-Libraries, Frameworks und Software.
Vielen Dank für den Tipp von Stefan Kaufmann.
Titelsong basierend auf Wish You Were Here von THE.MADPIX.PROJECT lizensiert unter Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0).