How to render a React.Suspense? - scala.js

I'm trying to render a React.Suspense on an empty page, but failed with the following error:
Uncaught Invariant Violation: Element type is invalid: expected a
string (for built-in components) or a class/function (for composite
components) but got: object.
Here is the code. It compiles. Does that mean scalajs-react or scalajs is not 100% type safe? What should I do to fix the problem?
package org.myorg
import japgolly.scalajs.react.{AsyncCallback, React, ScalaComponent}
import japgolly.scalajs.react.vdom.html_<^._
import org.scalajs.dom
object MyPage {
private val suspense = React.Suspense(
fallback = <.div("Loading..."),
asyncBody = AsyncCallback.point(<.div("Loaded!")).delayMs(1000))
private val component = ScalaComponent.builder[VdomElement]("Home")
.render_P(p => p)
.build
def main(args: Array[String]): Unit = {
val container = dom.document.getElementById("app")
// This line works
component(<.div("hello")).renderIntoDOM(container)
// Either of the following two lines fails
component(suspense).renderIntoDOM(container)
suspense.renderIntoDOM(container)
}
}
Here are the project files.
build.properties:
sbt.version=1.2.7
build.sbt:
lazy val root = project.in(file("."))
.enablePlugins(ScalaJSPlugin)
.enablePlugins(ScalaJSBundlerPlugin)
.settings(
organization := "org.myorg",
scalaVersion := "2.12.8",
scalaJSUseMainModuleInitializer := true,
webpackBundlingMode := BundlingMode.LibraryOnly(),
emitSourceMaps := false,
npmDependencies in Compile ++= Seq(
"react" -> "16.8.4",
"react-dom" -> "16.8.4"
),
libraryDependencies ++= Seq(
"com.github.japgolly.scalajs-react" %%% "extra" % "1.4.0"
),
jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv()
)
index.html:
<!doctype html>
<html lang="en" data-framework="scalajs-react">
<head>
<meta charset="utf-8">
</head>
<body>
<section id="app"></section>
<script src="../target/scala-2.12/scalajs-bundler/main/root-fastopt-library.js"></script>
<script src="../target/scala-2.12/scalajs-bundler/main/root-fastopt-loader.js"></script>
<script src="../target/scala-2.12/scalajs-bundler/main/root-fastopt.js"></script>
</body>
</html>

This seems to be an issue with how scalajs-react was declaring its imports.
v1.4.1 is on its way to Maven Central now and should fix this.
See https://github.com/japgolly/scalajs-react/issues/522 for detail.

Related

How to use jquery-ui with JSImport

I want to access the jquery ui library in my scala js project. I have tried defining the following main module:
import org.scalajs.jquery.JQueryStatic
import scala.scalajs.js
import org.scalajs.dom
import scalatags.JsDom.all._
import scala.scalajs.js.annotation.JSImport
#JSImport("jquery", JSImport.Namespace)
#js.native
object JQuery extends JQueryStatic
#js.native
trait JQueryUI extends JQueryStatic {
def spinner(options: js.Object = js.Dynamic.literal()): JQueryUI = js.native
}
#JSImport("jquery-ui", JSImport.Namespace)
#js.native
object JQueryUI extends JQueryUI
object App {
def main(args: Array[String]): Unit = {
dom.document.getElementById("root").appendChild(div(input(id := "input")).render)
JQuery("#input").asInstanceOf[JQueryUI].spinner()
}
}
And my build.sbt is as follows:
enablePlugins(ScalaJSBundlerPlugin)
lazy val opexCounter = project.in(file(".")).settings(
name := "Repro",
scalaVersion := "2.12.8",
libraryDependencies ++= Seq(
"org.scala-js" %%% "scalajs-dom" % "0.9.6",
"com.lihaoyi" %%% "scalatags" % "0.6.7",
"be.doeraene" %%% "scalajs-jquery" % "0.9.4"
),
npmDependencies in Compile ++= Seq(
"jquery" -> "2.2.1",
"jquery-ui" -> "1.12.1",
),
mainClass in Compile := Some("App"),
scalaJSUseMainModuleInitializer := true,
webpackDevServerPort := 3000
)
But when I load my page I get the following error in my console:
TypeError: qual$1.spinner is not a function
Is this not the correct way to import the library and if not what is?
The complete source for the project can be found here
I changed my npm dependency from jquery-ui to jquery-ui-bundle and imported it. I also had to make an explicit reference to my JQueryUIImport object in order to ensure it was instantiated. Those 2 changes fixed the problem

Sbt: How to define task for all projects?

I would like to be able to define a task for all projects in my sbt.build:
lazy val project1 = project.in(`.` / "project1)
...
lazy val project2 =
...
lazy val upload = taskKey[Unit]("upload a config file from project to server)
upload := {
val file = baseDirectory.value / "config.json"
...
}
The problem is this definition works only when I call sbt upload, but I would like to be able to call it for each subproject: sbt project1/upload and sbt project2/upload.
Is there a way to do it, without using inputKey?
See Organizing the build:
For more advanced users, another way of organizing your build is to define one-off auto plugins in project/*.scala. By defining triggered plugins, auto plugins can be used as a convenient way to inject custom tasks and commands across all subprojects.
project/UploadPlugin.scala
package something
import sbt._
import Keys._
object UploadPlugin extends AutoPlugin {
override def requires = sbt.plugins.JvmPlugin
override def trigger = allRequirements
object autoImport {
val upload = taskKey[Unit]("upload a config file from project to server")
}
import autoImport._
override lazy val projectSettings = Seq(
upload := {
val n = name.value
println(s"uploading $n..")
}
)
}
build.sbt
Here's how you can use it:
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.5"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.aggregate(project1, project2)
.settings(
name := "Hello"
)
lazy val project1 = (project in file("project1"))
lazy val project2 = (project in file("project2"))
build.sbt does not have to mention anything about UploadPlugin, since it's a triggered plugin. From the shell you can call:
sbt:Hello> project1/upload
uploading project1..
[success] Total time: 0 s, completed Jul 20, 2018
sbt:Hello> project2/upload
uploading project2..
[success] Total time: 0 s, completed Jul 20, 2018
You can add the task as a setting of the project you want :
lazy val uploadTask = {
lazy val upload = taskKey[Unit]("upload a config file from project to server)
upload := {
val file = baseDirectory.value / "config.json"
...
}
}
project.in(`.` / "project1).settings(uploadTask)

sscalaJSModuleKind := ModuleKind.CommonJSModule - cannot invoke main method anymore :(

I am trying to build a new facade that uses a lot of JSImport statements. I wanted to put it in a subfolder of a project I am currently working on, to improve it while at it.
Before my root build.sbt looked like this for the scala.js part:
lazy val client = (project in file("modules/client"))
.enablePlugins(ScalaJSPlugin, ScalaJSWeb)
.settings(generalSettings: _*)
.settings(
name := "client",
libraryDependencies += CrossDependencies.scalaTags,
persistLauncher := true
)
now I added this: scalaJSModuleKind := ModuleKind.CommonJSModule, which is incompatible with the persistLauncher setting, so I removed persistLauncher := true
Of course in my view I could no longer just add client-launcher.js. So I tried to wrap the main-method call manually, like this:
<script type="text/javascript">
tld.test.Test().main()
</script>
Now, this does NOT work IF scalaJSModuleKind := ModuleKind.CommonJSModule is added to my build.sbt. If I remove that setting everything works just fine.
This is my Test
package tld.test
import org.scalajs.dom
import scala.scalajs.js.JSApp
object Test extends JSApp
{
import scalatags.JsDom.all._
def main(): Unit =
{
// Add js script dynamically
val s = script(
"alert('Hello World!')"
)
dom.document.getElementsByTagName("head")(0).appendChild(s.render)
}
}
Now, if I remove that ModuleKind-setting an alert pops up with 'Hello World', but if it's there nope. What is causing this and how can I prevent it?
edit
After answer from #sjrd I tried the following:
plugins.sbt:
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.5.0")
addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.5.0")
build.sbt:
lazy val client = (project in file("modules/client"))
.enablePlugins(ScalaJSBundlerPlugin, ScalaJSWeb) // ScalaJSBundlerPlugin automatically enables ScalaJSPlugin
.settings(generalSettings: _*)
.settings(
name := "client"
, libraryDependencies += CrossDependencies.scalaTags
//, scalaJSModuleKind := ModuleKind.CommonJSModule // ScalaJSBundlerPlugin implicitly sets moduleKind to CommonJSModule enables ScalaJSPlugin
)
lazy val server = (project in file("modules/server"))
.enablePlugins(PlayScala, WebScalaJSBundlerPlugin)
.settings(generalSettings: _*)
.settings(
name := "server"
,libraryDependencies ++= Seq(
CrossDependencies.scalaTest,
CrossDependencies.scalactic,
CrossDependencies.scalaTags,
"com.typesafe.play" %% "play-json" % "2.6.0-M1")
,scalaJSProjects := Seq(client)
,pipelineStages in Assets := Seq(scalaJSPipeline)
//,pipelineStages := Seq(digest, gzip)
,compile in Compile := ((compile in Compile) dependsOn scalaJSPipeline).value
)
But during compilation I get:
ERROR in ./fastopt-launcher.js
[info] Module not found: Error: Cannot resolve 'file' or 'directory' /home/sorona/scalajstestbed/modules/client/target/scala-2.12/scalajs-bundler/main/client-fastopt.js in /home/sorona/scalajstestbed/modules/client/target/scala-2.12/scalajs-bundler/main
edit: Solution is to then include client-fastopt-bundle.js et voila
Changing the module kind significantly changes the shape of the output file, include its external "specification". In particular, it is not a script that can be embedded in Web page anymore. Instead, it is a CommonJS module.
To be able to include it in a Web page, you will need to bundle it. The best way to do so is too use scalajs-bundler.

How to load javascript in the JVM cross project

I have run into some difficulties and was wondering if someone can help me. I have the following Build.scala and I am trying to access the the compile javascript from the JVM project.
lazy val webProject = CrossProject(base = file("./main/web"), crossType = CrossType.Full, jvmId = "api-gateway", jsId = "web-js")
.settings(
name := "web",
unmanagedSourceDirectories in Compile += baseDirectory.value / "shared" / "main" / "scala",
libraryDependencies ++= Dependencies.Client.sharedDeps.value)
.jvmSettings(
persistLauncher := true,
persistLauncher in Test := false,
libraryDependencies ++= Dependencies.Client.jvmDeps.value)
.jsSettings(libraryDependencies ++= Dependencies.Client.jsDeps.value)
lazy val webJS = webProject.js.enablePlugins(ScalaJSPlugin)
lazy val webJVM = webProject.jvm
.settings((resources in Compile) += (fastOptJS in(webJS, Compile)).value.data)
.dependsOn(dominos)
The compile javascript is generated
[info] Fast optimizing /.../main/web/js/target/scala-2.11/web-fastopt.js
When I try to access the compile javascript by running get server, it can't be found.
object Main extends App {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val routes = pathEndOrSingleSlash(getFromResource("web-fastopt.js"))
Http().bindAndHandle(routes, "localhost", 8080)
}
Isn't this line suppose to add the javascript the the JVM's resources folder when it runs?
(resources in Compile) += (fastOptJS in(webJS, Compile)).value.data
Any help would be greatly appreciated.
Seems like this like doesn't work for me for some reason
(resources in Compile) += (fastOptJS in(webJS, Compile)).value.data
Instead I ended having to move the fastOptJS file
lazy val webJVM = webProject.jvm
.settings(Seq(fastOptJS, fullOptJS, packageJSDependencies)
.map(pkg ⇒ crossTarget in(webJS, Compile, pkg) := scalaJSOutput.value))
I also needed to add
getFromResourceDirectory("")
to the Akka Http routes.

ExternalJSEnv doesn't see javascript dependencies in launcher-webpage

Simple setup :
lazy val root = project.in(file("."))
.settings(scalaJSSettings: _*)
.settings(utestJsSettings: _*)
.settings(persistLauncher := true)
.settings(persistLauncher in Test := false)
.settings(
name := "bindings",
version := "0.0.1-SNAPSHOT",
scalaVersion := "2.11.2",
libraryDependencies ++= Seq(
"org.scala-lang.modules.scalajs" %%% "scalajs-dom" % "0.6",
"com.lihaoyi" %%% "utest" % "0.2.3" % "test"
),
jsDependencies += "org.webjars" % "d3js" % "3.4.11" / "d3.js", //d3.min.js
autoCompilerPlugins := true,
test in Test := (test in(Test, fastOptStage)).value,
requiresDOM := true,
traceLevel := 0
)
phantomjs-launcher-webpage.html contains really super valid stuff :
<script type="text/javascript" src="/path/scalajs-bindings/target/scala-2.11/classes/d3.js"></script>
<script type="text/javascript" src="/path/scalajs-bindings/target/scala-2.11/bindings-fastopt.js"></script>
<script type="text/javascript">
// Phantom.js code launcher
// Origin: /path/scalajs-bindings/target/scala-2.11/bindings-launcher.js
window.addEventListener('load', function() {
((typeof global === "object" && global &&
global["Object"] === Object) ? global : this)["com"]["whatever"]["scalajs"]["Appp"]().main();
}, false);
</script>
Now, in browser it can call d3, I tried with equivalent html, but using PhantomJS or NodeJS if I do fastOptStage::run or fastOptStage::test I get
TypeError: undefined is not an object (evaluating 'd3["scale"]')
The code looks like this:
object Appp extends JSApp {
override def main(): Unit = {
val fill = d3.scale.category20()
println(fill)
}
}
You can have your object d3 extend js.GlobalScope and it will be looked for in the global JavaScript scope (i.e. where d3.js will put it):
object Appp extends JSApp {
object d3 extends js.GlobalScope {
def scale: js.Dynamic = ???
}
override def main(): Unit = {
val fill = d3.scale.category20()
println(fill)
}
}
However, in the case of d3, you might want to consider having the d3 package object itself extend js.GlobalScope:
package mbostock
package object d3 extends js.GlobalScope {
def scale: js.Dynamic = ???
}
I strongly recommend reading the JavaScript interoperability guide, notably the part about calling JavaScript from Scala.js.
The reason it wasn't working was that I had d3 object in a package object :
package object mbostock {
object d3 {...}
}
It also doesn't work as an inner class :
object Appp extends JSApp {
object d3 {...}
override def main(): Unit = {...}
}