Utilizarea Scala.js cu NPM și Browserify
Publicat: 2022-03-11Dacă utilizați Scala.js, compilatorul limbajului Scala pentru JavaScript, s-ar putea să găsiți gestionarea dependenței standard a Scala.js prea limitativă în lumea JavaScript modernă. Scala.js gestionează dependențele cu WebJars, în timp ce dezvoltatorii JavaScript gestionează dependențele folosind NPM. Deoarece dependențele produse de NPM sunt de partea serverului, de obicei este nevoie de un pas suplimentar folosind Browserify sau Webpack pentru a genera codul de browser.
În această postare, voi descrie cum să integrez Scala.js cu multitudinea de module JavaScript disponibile pe NPM. Puteți verifica acest depozit GitHub pentru un exemplu de lucru al tehnicilor descrise aici. Folosind acest exemplu și citind această postare, veți putea să vă colectați bibliotecile JavaScript folosind NPM, să creați un pachet folosind Browserify și să utilizați rezultatul în propriul proiect Scala.js. Toate acestea fără a instala măcar Node.js, deoarece totul este gestionat de SBT.
Gestionarea dependențelor pentru Scala.js
Astăzi, scrierea de aplicații în limbi care se compilează în JavaScript devine o practică foarte comună. Din ce în ce mai mulți oameni trec la limbaje JavaScript extinse, cum ar fi CoffeeScript sau TypeScript, sau transpilere precum Babel, care vă permit să utilizați ES6 astăzi. În același timp, Google Web Toolkit (un compilator de la Java la JavaScript) este folosit mai ales pentru aplicațiile de întreprindere. Din aceste motive, ca dezvoltator Scala, nu mai consider că folosirea Scala.js este o alegere ciudată. Compilatorul este rapid, codul produs este eficient și, în general, este doar o modalitate de a folosi același limbaj atât pe front-end, cât și pe back-end.
Acestea fiind spuse, utilizarea instrumentelor Scala în lumea JavaScript nu este încă 100% naturală. Uneori trebuie să umpleți golul de la ecosistemul JavaScript la cel Scala. Scala.js ca limbaj are o interoperabilitate excelentă cu JavaScript. Deoarece Scala.js este un compilator pentru limbajul Scala cu JavaScript, este foarte ușor să interfațezi codul Scala cu codul JavaScript existent. Cel mai important, Scala.js vă oferă posibilitatea de a crea interfețe tipizate (sau fațade) pentru a accesa biblioteci JavaScript netipizate (similar cu ceea ce faceți cu TypeScript). Pentru dezvoltatorii obișnuiți cu limbaje puternic tastate precum Java, Scala sau chiar Haskell, JavaScript este scris prea puțin. Dacă sunteți un astfel de dezvoltator, probabil că motivul principal este că s-ar putea să doriți să utilizați Scala.js este să obțineți un limbaj (puter) tastat pe lângă un limbaj netipizat.
O problemă în lanțul de instrumente standard Scala.js, care se bazează pe SBT și este încă oarecum deschisă, este: Cum să includeți dependențe, cum ar fi biblioteci JavaScript suplimentare, în proiectul dvs.? SBT standardizează pe WebJars, așa că ar trebui să utilizați WebJars pentru a vă gestiona dependențele. Din păcate, din experiența mea s-a dovedit a fi inadecvat.
Problema cu WebJars
După cum am menționat, modul standard Scala.js de a prelua dependențele JavaScript se bazează pe WebJars. Scala este, până la urmă, un limbaj JVM. Scala.js folosește SBT în primul rând pentru construirea de programe și, în cele din urmă, SBT este excelent în gestionarea dependențelor JAR.
Din acest motiv, formatul WebJar a fost definit exact pentru importarea dependențelor JavaScript în lumea JVM. Un WebJar este un fișier JAR care include active web, în care fișierul JAR simplu include doar clase Java compilate. Deci, ideea Scala.js este că ar trebui să importați dependențele JavaScript prin simpla adăugare a dependențelor WebJar, într-un mod similar, Scala adaugă dependențe JAR.
O idee bună, doar că nu funcționează
Cea mai mare problemă cu Webjars este că o versiune arbitrară a unei biblioteci JavaScript aleatorii este rareori disponibilă ca WebJar. În același timp, marea majoritate a bibliotecilor JavaScript sunt disponibile ca module NPM. Cu toate acestea, există o presupusă punte între NPM și WebJars cu un pachet automat npm-to-webjar
. Așa că am încercat să import o bibliotecă, disponibilă ca modul NPM. În cazul meu, a fost VoxelJS, o bibliotecă pentru construirea de lumi asemănătoare Minecraft într-o pagină web. Am încercat să solicit biblioteca ca WebJar, dar bridge-ul a eșuat pur și simplu pentru că nu există câmpuri de licență în descriptor.
Este posibil să vă confruntați cu această experiență frustrantă și din alte motive cu alte biblioteci. Mai simplu spus, se pare că nu puteți accesa fiecare bibliotecă în sălbăticie ca un WebJar. Cerința de a utiliza WebJars pentru a accesa bibliotecile JavaScript pare a fi prea limitativă.
Introduceți NPM și Browserify
După cum am subliniat deja, formatul standard de ambalare pentru majoritatea bibliotecilor JavaScript este Node Package Manager, sau NPM, inclus în orice versiune de Node.js. Folosind NPM, puteți accesa cu ușurință aproape toate bibliotecile JavaScript disponibile.
Rețineți că NPM este managerul de pachete Node . Node este o implementare pe partea de server a motorului JavaScript V8 și instalează pachete pentru a fi utilizate pe partea serverului de către Node.js. Așa cum este, NPM este inutil pentru browser. Cu toate acestea, utilitatea NPM a fost extinsă pentru a lucra cu aplicații de browser, datorită instrumentului ubicuu Browserify, care este, desigur, distribuit și ca pachet NPM.
Browserify este, pur și simplu, un pachet pentru browser. Acesta va colecta module NPM producând un „pachet” utilizabil într-o aplicație de browser. Mulți dezvoltatori JavaScript lucrează în acest fel - gestionează pachetele cu NPM și apoi le navighează pentru a le utiliza în aplicația web. Rețineți că există și alte instrumente care funcționează în același mod, cum ar fi Webpack.
Umplerea golului de la SBT la NPM
Din motivele pe care tocmai le-am descris, ceea ce am vrut a fost o modalitate de a instala dependențe de pe web cu NPM, de a invoca Browserify pentru a aduna dependențe pentru browser și apoi de a le folosi cu Scala.js. Sarcina s-a dovedit a fi puțin mai complicată decât mă așteptam, dar totuși posibilă. Într-adevăr, am făcut treaba și o descriu aici.
Pentru simplitate, am ales Browserify și pentru că am descoperit că este posibil să-l rulez în SBT. Nu am încercat cu Webpack, deși cred că este posibil, de asemenea. Din fericire, nu a trebuit să încep în vid. Există deja un număr de piese la locul lor:
- SBT acceptă deja NPM.
sbt-web
, dezvoltat pentru Play Framework, poate instala dependențe NPM. - SBT acceptă execuția JavaScript. Puteți executa instrumentele Node fără a instala Node în sine, datorită pluginului
sbt-jsengine
. - Scala.js poate folosi un pachet generat. În Scala.js, există o funcție de concatenare pentru a include în aplicația dvs. biblioteci JavaScript arbitrare.
Folosind aceste caracteristici, am creat o sarcină SBT care poate descărca dependențele NPM și apoi invoca Browserify, producând un fișier bundle.js
. Am încercat să integrez procedura în lanțul de compilare și pot rula totul automat, dar trebuie să procesez gruparea la fiecare compilare este pur și simplu prea lent. De asemenea, nu schimbați dependențele tot timpul; prin urmare, este rezonabil să creați manual un pachet o dată când schimbați dependențele.
Deci, soluția mea a fost să construiesc un subproiect. Acest subproiect descarcă și împachetează biblioteci JavaScript cu NPM și Browserify. Apoi, am adăugat o comandă bundle
pentru a efectua colectarea dependențelor. Pachetul rezultat este adăugat la resurse pentru a fi utilizat în aplicația Scala.js.
Ar trebui să executați acest „pachet” manual ori de câte ori vă schimbați dependențele JavaScript. După cum am menționat, nu este automatizat în lanțul de compilare.
Cum se utilizează Bundler-ul
Dacă doriți să utilizați exemplul meu, faceți următoarele: mai întâi, verificați depozitul cu comanda obișnuită Git.
git clone https://github.com/sciabarra/scalajs-browserify/
Apoi, copiați folderul bundle
din proiectul dvs. Scala.js. Este un subproiect pentru grupare. Pentru a vă conecta la proiectul principal, ar trebui să adăugați următoarele linii în fișierul build.sbt
:
val bundle = project.in(file("bundle")) jsDependencies += ProvidedJS / "bundle.js" addCommandAlias("bundle", "bundle/bundle")
De asemenea, trebuie să adăugați următoarele linii în fișierul project/plugins.sbt
:
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1") addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
Odată terminat, aveți o nouă comandă, bundle
, pe care o puteți utiliza pentru a vă aduna dependențele. Va genera un fișier bundle.js
în folderul src/main/resources
.
Cum este inclus pachetul în aplicația dvs. Scala.js?
Comanda bundle
tocmai descrisă adună dependențe cu NPM și apoi creează un bundle.js
. Când rulați comenzile fastOptJS
sau fullOptJS
, ScalaJS va crea un myproject-jsdeps.js
, inclusiv toate resursele pe care le-ați specificat ca dependență JavaScript, de aici și bundle.js
. Pentru a include dependențe grupate în aplicația dvs., ar trebui să utilizați următoarele incluziuni:

<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>
Pachetul dvs. este acum disponibil ca parte a myproject-jsdeps.js
. Pachetul este gata și ne-am terminat oarecum sarcina (importarea dependențelor și exportarea lor în browser). Următorul pas este utilizarea bibliotecilor JavaScript, care este o problemă diferită, o problemă de codare Scala.js. Pentru a fi complet, vom discuta acum despre cum să folosim pachetul în Scala.js și să creăm fațade pentru a folosi bibliotecile pe care le-am importat.
Utilizarea unei biblioteci JavaScript generice în aplicația dvs. Scala.js
Pentru a recapitula, tocmai am văzut cum să folosim NPM și Browserify pentru a crea un pachet și a include acel pachet în Scala.js. Dar cum putem folosi o bibliotecă JavaScript generică?
Procesul complet, pe care îl vom explica în detaliu în restul postării, este:
- Selectați bibliotecile dvs. din NPM și includeți-le în
bundle/package.json
. - Încărcați-le cu
require
într-un fișier modul de bibliotecă, înbundle/lib.js
. - Scrieți fațade Scala.js pentru a interpreta obiectul
Bundle
în Scala.js. - În cele din urmă, codificați aplicația folosind bibliotecile nou tastate.
Adăugarea unei dependențe
Folosind NPM, trebuie să includeți dependențele dvs. în fișierul package.json
, care este standardul.
Deci, să presupunem că doriți să utilizați două biblioteci celebre precum jQuery și Loadash. Acest exemplu este doar în scop demonstrativ, deoarece există deja un wrapper excelent pentru jQuery disponibil ca dependență pentru Scala.js cu un modul adecvat, iar Lodash este inutil în lumea Scala. Cu toate acestea, cred că este un exemplu bun, dar luați-l doar ca exemplu.
Deci, accesați site-ul web npmjs.com
și localizați biblioteca pe care doriți să o utilizați și selectați și o versiune. Să presupunem că alegeți jquery-browserify
versiunea 13.0.0 și lodash
versiunea 4.3.0. Apoi, actualizați blocul de dependencies
al packages.json
dvs. după cum urmează:
"dependencies": { "browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0" }
Păstrați întotdeauna browserify
, deoarece este necesar pentru a genera pachetul. Totuși, nu trebuie să îl includeți în pachet.
Rețineți că, dacă aveți NPM instalat, puteți doar să tastați din directorul bundle
:
npm install --save jquery-browserify lodash
De asemenea, va actualiza package.json
. Dacă nu aveți instalat NPM, nu vă faceți griji. SBT va instala o versiune Java a Node.js și NPM, va descărca JAR-urile necesare și le va rula. Toate acestea sunt gestionate atunci când rulați comanda bundle
de la SBT.
Exportarea bibliotecii
Acum știm cum să descarcăm pachetele. Următorul pas este să instruiți Browserify să le adune într-un pachet și să le facă disponibile pentru restul aplicației.
Browserify este un colector de require
, emulând comportamentul Node.js pentru browser, ceea ce înseamnă că trebuie să aveți undeva un require
importarea bibliotecii. Deoarece trebuie să exportăm acele biblioteci în Scala.js, pachetul generează și un obiect JavaScript de nivel superior numit Bundle
. Deci, ceea ce trebuie să faceți este să editați lib.js
, care exportă un obiect JavaScript și să solicitați toate bibliotecile dvs. ca câmpuri ale acestui obiect.
Dacă vrem să exportăm în bibliotecile Scala.js jQuery și Lodash, în cod aceasta înseamnă:
module.exports = { "jquery": require("jquery-browserify"), "lodash": require("lodash") }
Acum, doar executați bundle
de comenzi și biblioteca va fi descărcată, adunată și plasată în pachet, gata să fie utilizată în aplicația dvs. Scala.js.
Accesarea pachetului
Până acum:
- Ați instalat subproiectul pachetului în proiectul dvs. Scala.js și l-ați configurat corect.
- Pentru orice bibliotecă dorită, ați adăugat-o în
package.json
. - Le-ați cerut în
lib.js
- Ai executat comanda
bundle
.
Ca rezultat, aveți acum un obiect JavaScript Bundle
de nivel superior, care oferă toate punctele de intrare pentru biblioteci, disponibile ca câmpuri ale acestui obiect.
Acum sunteți gata să-l utilizați cu Scala.js. În cel mai simplu caz, puteți face ceva de genul acesta pentru a accesa bibliotecile:
@js.native object Bundle extends js.Object { def jquery : js.Any = js.native def lodash: js.Any = js.native }
Acest cod vă permite să accesați bibliotecile din Scala.js. Cu toate acestea, nu este modul în care ar trebui să o faceți cu Scala.js, deoarece bibliotecile sunt încă netipizate. În schimb, ar trebui să scrieți „fațade” sau învelișuri tastate, astfel încât să puteți utiliza bibliotecile JavaScript netascate inițial într-o manieră tastată.
Nu vă pot spune aici cum să scrieți fațadele, deoarece depinde de biblioteca JavaScript specifică pe care doriți să o includeți în Scala.js. Voi arăta doar un exemplu, pentru a finaliza discuția. Vă rugăm să verificați documentația oficială Scala.js pentru mai multe detalii. De asemenea, puteți consulta lista de fațade disponibile și puteți citi codul sursă pentru inspirație.
Până acum, am acoperit procesul pentru biblioteci arbitrare, încă nemapate. În restul articolului mă refer la o bibliotecă cu o fațadă deja disponibilă, așa că discuția este doar un exemplu.
Încheierea unui API JavaScript în Scala.js
Când utilizați require
în JavaScript, obțineți un obiect care poate fi multe lucruri diferite. Poate fi o funcție sau un obiect. Poate fi chiar doar un șir sau un boolean, caz în care require
este invocată doar pentru efectele secundare.
În exemplul pe care îl fac, jquery
este o funcție, care returnează un obiect care oferă metode suplimentare. Utilizarea tipică este jquery(<selector>).<method>
. Aceasta este o simplificare, deoarece jquery
permite și utilizarea $.<method>
, dar în acest exemplu simplificat nu voi acoperi toate acele cazuri. Rețineți, în general, pentru bibliotecile JavaScript complexe, nu toate API-urile pot fi mapate cu ușurință la tipuri Scala statice. Poate fi necesar să recurgeți la js.Dynamic
oferind o interfață dinamică (netipată) obiectului JavaScript.
Deci, pentru a captura doar cazul de utilizare mai frecvent, am definit în obiectul Bundle
, jquery
:
def jquery : js.Function1[js.Any, Jquery] = js.native
Această funcție va returna un obiect jQuery. O instanță a unei trăsături este în cazul meu definită cu o singură metodă (o simplificare, puteți adăuga propria dvs.):
@js.native trait Jquery extends js.Object { def text(arg: js.Any): Jquery = js.native }
Pentru biblioteca Lodash, modelăm întreaga bibliotecă ca obiect JavaScript, deoarece este o colecție de funcții pe care o puteți apela direct:
def lodash: Lodash = js.native
Unde trăsătura lodash
este după cum urmează (de asemenea, o simplificare, puteți adăuga metodele dvs. aici):
@js.native trait Lodash extends js.Object { def camelCase(arg: js.Any): String = js.native }
Folosind aceste definiții, acum putem scrie cod Scala utilizând bibliotecile de bază jQuery și Lodash, ambele încărcate din NPM și apoi navigate :
object Main extends JSApp { def main(): Unit = { import Bundle._ jquery("#title").text(lodash.camelCase("This is a test")) } }
Puteți verifica exemplul complet aici.
Concluzie
Sunt un dezvoltator Scala și am fost încântat când am descoperit Scala.js, deoarece puteam folosi același limbaj atât pentru server, cât și pentru client. Deoarece Scala este mai asemănător cu JavaScript decât cu Java, Scala.js este destul de ușor și natural în browser. În plus, puteți utiliza și funcții puternice ale Scala ca o colecție bogată de biblioteci, macrocomenzi și IDE-uri puternice și instrumente de construcție. Alte avantaje cheie sunt că puteți partaja codul între server și client. Există o mulțime de cazuri în care această caracteristică este utilă. Dacă utilizați un transpiler pentru Javascript precum Coffeescript, Babel sau Typescript, nu veți observa prea multe diferențe atunci când utilizați Scala.js, dar există totuși multe avantaje. Secretul este să luați ce este mai bun din fiecare lume și să vă asigurați că lucrează împreună frumos.