This is a study note of Scala.js. Scala.js brings strong typing and code share to web development. It runs faster than hand-written JavaScript and has good interoperability with JavaScript libraries. It comes with excellent editor support. Due to the complexity of build, this note also comes with a lot of mill
build code.
1 Hi
Create a build.sc
file in the project root folder:
|
|
Create a app/src/Hi.scala
file:
|
|
In the project folder, run mill app
.
Mill builds the Hi.scala
to generate a out/app/fastOpt/dest/out.js
that can be executed in either Node.js or a browser. To generate fully optimized JS code, run mill app.fullOpt
. The size of the out/app/fullOpt/dest/out.js
is 8KB, reduced from 278KB.
2 A Demo with Testing
This demo use scala-js-dom
lib to manipulate DOM.
The test code runs inside Node.js and requires scalajs-env-jsdom-nodejs. It uses NPM package jsdom for Node.js, use npm install jsdom
to install it.
The build.sc
has the following content:
|
|
The app/src/App.scala
file:
|
|
The app/test/src/AppTest.scala
file:
|
|
After npm install jsdom
, use mill app.test
to run the test.
3 A Demo with Scala jQuery
3.1 build.sc
The build file is tricky for a couple of reasons:
- The
udash-jquery
wraps jQuery API for Scalajs program. It requiresjQuery
lib to work properly. To avoid hard code, we use WebJars to manage the dependent js resources. The js resources are merged with genereated js file forfastOpt
,fullOpt
andfastOptTest
. scalatags
lib is used in app and in test. It generatesjsdom
nodes.- When override a command and run the the
super.cmd
, the overriden command is excuted in a special dest. You cannot pass non-constant parameter to
T.task
because it will ccreate circular dependencies. Otherwise, you will get error message like “Target#apply() call cannot usevalue outputFile
defined within the T{…} block combineWebJars(outputFile, taskFile)()“.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
// for Bloop plugin to generate configuration import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` import $ivy.`com.lihaoyi::mill-contrib-playlib:$MILL_VERSION` import mill.playlib.Static import mill.{T, Agg, Module} import mill.scalalib.{DepSyntax, Lib} // implicit class for ivy"string" import mill.scalajslib.ScalaJSModule // use playlib Static to extract resources from webjar object app extends ScalaJSModule with Static { outer => // basic setup for a scala.js application def scalaVersion = "2.13.1" def scalaJSVersion = "1.0.1" def ivyDeps = Agg( ivy"com.lihaoyi::scalatags_sjs1:0.9.0", // scalatags for scalajs ivy"org.webjars:jquery:3.5.0", ivy"io.udash::udash-jquery_sjs1:3.0.4" ) def combineWebJars( outputFile: os.Path, libPath: os.Path, taskFile: os.Path ) = { // matches filename with pattern: "anyword.min.js" val jsFiles = os.walk(libPath) .filter(path => os.isFile(path) && path.last.matches("[^\\.]*\\.min.js$") ) val allFiles = jsFiles :+ taskFile val allContent = allFiles.map(os.read).mkString(";") // throw an exception if an error occurs os.write.over(outputFile, allContent) } // in production, need to define fallOpt def fastOpt = T { val outputFile = T.dest / "out.js" val taskFile = super.fastOpt().path // that's the playlib Static resource val libPath = os.Path(assetsPath(), webJarResources().path) / "lib" combineWebJars(outputFile, libPath, taskFile) PathRef(outputFile) } object test extends Tests { def testFrameworks = Seq("utest.runner.Framework") import mill.scalajslib.api.JsEnvConfig def jsEnvConfig: T[JsEnvConfig] = T { JsEnvConfig.JsDom() } def ivyDeps = Agg( ivy"com.lihaoyi::scalatags_sjs1:0.9.0", ivy"org.scala-js::scalajs-dom::1.0.0", ivy"org.scala-js::scalajs-env-jsdom-nodejs:1.0.0", ivy"org.webjars:jquery:3.5.0", ivy"io.udash::udash-jquery_sjs1:3.0.4", ivy"com.lihaoyi::utest::0.7.4" ) // combine all webJar js files with the scalajs out.js def fastOptTest = T { val taskFile = super.fastOptTest().path val outputFile = T.ctx.dest / "out.js" // that's the playlib Static resource val libPath = os.Path(assetsPath(), webJarResources().path) / "lib" outer.combineWebJars(outputFile, libPath, taskFile) PathRef(outputFile) } } }
3.2 The App Code
The index.html
file has a link to the supported jQuery:
|
|
The app/src/App.scala
file:
|
|
Run mill app.fastOpt
to create the JS file, then open index.html
in a browser.
3.3 The Test Code
The app/test/src/AppTest.scala
file:
|
|
Configuration
Use @JSExportTopLevel
to emit to the global level. Use @js.natvie
and @JSImport
to import native JS stuff.
There are additional setting to use ES module.
The JS evnironment can be nodejs, jsdom, phantomjs or selenium.
Use cross building to compile for Scala.js and Scala JVM.