diff --git a/Dockerfile b/Dockerfile index 658ff60..962cc70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN \ rm sbt-$(cat /sbt-version).rpm && \ mkdir /project WORKDIR /project +ENV SBT_OPTS="-XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=2G -Xmx2G -Xms1G" # This stage can be used for testing whether the build is compatible with fresh versions of JDK and NodeJS/NPM FROM fedora:latest AS sbt-version-bleeding-edge @@ -38,6 +39,6 @@ RUN \ rm sbt-$(cat /sbt-version).rpm && \ mkdir /project WORKDIR /project - +ENV SBT_OPTS="-Xmx2G -Xms1G" FROM conservative AS default diff --git a/build.sbt b/build.sbt index 97b95c5..0a53cd6 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ val appVersion= "1.0" lazy val scalaV = "2.13.12" -val bootstrap = "org.webjars" % "bootstrap" % "5.3.2" +val bootstrapVersion = "5.3.2" import com.typesafe.sbt.web.PathMapping import com.typesafe.sbt.web.pipeline.Pipeline @@ -100,18 +100,14 @@ lazy val server = (project in file("server")).settings( simpleUrlUpdate / includeFilter := "*.css" || "*.js" || "*.html", // triggers scalaJSPipeline when using compile or continuous compilation Compile / compile := ((Compile / compile) dependsOn scalaJSPipeline).value, - Concat.groups := Seq( - "main.min.js" -> group(Seq("zbdb-stats-client-jsdeps.min.js", "zbdb-stats-client-opt/main.js")) - ), + Concat.groups := Seq("main.min.js" -> group(Seq("zbdb-stats-client-opt-bundle.js"))), libraryDependencies ++= Seq( "com.vmunier" %% "scalajs-scripts" % "1.2.0", guice, - bootstrap, + "org.webjars" % "bootstrap" % bootstrapVersion, specs2 % Test ), -).enablePlugins(PlayScala, JSDependenciesPlugin, SbtWeb)//.dependsOn(sharedJvm) - -def toPathMapping(f: File): PathMapping = f -> f.getName +).enablePlugins(PlayScala, SbtWeb, WebScalaJSBundlerPlugin)//.dependsOn(sharedJvm) // Generates client JS; other assets are generated by the server subproject lazy val client = (project in file("client")).settings( @@ -122,24 +118,23 @@ lazy val client = (project in file("client")).settings( scalaVersion := scalaV, scalaJSUseMainModuleInitializer := true, Test / scalaJSUseMainModuleInitializer := false, + webpack / version := "5.88.2", // https://github.com/ScalablyTyped/Converter/issues/546 + webpackConfigFile := Some(baseDirectory.value / "custom.webpack.config.js"), + libraryDependencies ++= Seq( "org.scala-js" %%% "scalajs-dom" % "2.7.0", "com.lihaoyi" %%% "scalatags" % "0.12.0", "com.nrinaudo" %%% "kantan.csv" % "0.7.0", ), - - // Some magic required for compatibility with running the website directly from SBT (using Play) - Compile / fastLinkJS / jsMappings += toPathMapping((Compile / packageJSDependencies).value), - Compile / fullLinkJS / jsMappings += toPathMapping((Compile / packageMinifiedJSDependencies).value), - - jsDependencies ++= Seq( - bootstrap / "bootstrap.bundle.min.js", - "org.webjars" % "momentjs" % "2.10.6" / "min/moment.min.js", - "org.webjars" % "moment-timezone" % "0.4.0-1" / "moment-timezone-with-data.js" dependsOn "min/moment.min.js", - "org.webjars.npm" % "chart.js" % "4.4.0" / "chart.umd.js", - "org.webjars.npm" % "chartjs-adapter-moment" % "1.0.0" / "chartjs-adapter-moment.min.js" dependsOn "chart.umd.js", + Compile / npmDependencies ++= Seq( + "bootstrap" -> bootstrapVersion, + "moment" -> "2.10.6", + "moment-timezone" -> "0.4.0", + "chart.js" -> "4.4.0", + "chartjs-adapter-moment" -> "1.0.1", ), -).enablePlugins(ScalaJSPlugin, JSDependenciesPlugin, ScalaJSWeb)//.dependsOn(sharedJs) + stIgnore ++= List("moment", "moment-timezone", "bootstrap", "chart.js", "chartjs-adapter-moment"), +).enablePlugins(ScalaJSPlugin, ScalaJSBundlerPlugin, ScalablyTypedConverterPlugin)//.dependsOn(sharedJs) /*lazy val shared = (crossProject.crossType(CrossType.Pure) in file("shared")). settings(scalaVersion := scalaV). diff --git a/client/custom.webpack.config.js b/client/custom.webpack.config.js new file mode 100644 index 0000000..7d36313 --- /dev/null +++ b/client/custom.webpack.config.js @@ -0,0 +1,29 @@ +var webpack = require('webpack'); +var path = require('path'); + +module.exports = require('./scalajs.webpack.config'); + +module.exports.resolve ||= {}; +module.exports.resolve.alias ||= {}; +module.exports.resolve.alias['chart.js'] = path.resolve( + __dirname, + 'node_modules', + 'chart.js', + 'auto', + 'auto.js' +); +module.exports.resolve.alias['moment.js'] = path.resolve( + __dirname, + 'node_modules', + 'moment', + 'min', + 'moment.min.js' +); + +module.exports.plugins ||= []; +module.exports.plugins.push( + new webpack.IgnorePlugin({ + resourceRegExp: /^\.\/locale$/, + contextRegExp: /moment$/, + }) +) diff --git a/client/src/main/scala/com/example/Moment.scala b/client/src/main/scala/com/example/Moment.scala index 2c76c40..bbdf0e2 100644 --- a/client/src/main/scala/com/example/Moment.scala +++ b/client/src/main/scala/com/example/Moment.scala @@ -5,43 +5,24 @@ import com.example.moment.Moment import scala.scalajs.js import scala.scalajs.js.annotation._ import scala.language.implicitConversions +import scala.scalajs.js.annotation._ -@js.native trait MomentSingleton extends js.Any { - //def moment(): Moment = js.native - def utc(): Moment = js.native - def utc(x: Number): Moment = js.native - //def utc(xNumber[]): Moment = js.native - def utc(time: String): Moment = js.native - /*def moment.utc(String, String): Moment = js.native - def moment.utc(String, String[]): Moment = js.native - def moment.utc(String, String, String): Moment = js.native - def moment.utc(Moment): Moment = js.native - def moment.utc(Date): Moment = js.native*/ +@js.native +@JSImport("moment-timezone", JSImport.Namespace) +object MomentTimezone extends js.Any{ def tz(time: String, tz: String): Moment = js.native } @js.native -@JSGlobalScope +@JSImport("moment", JSImport.Namespace) object MomentJsGlobal extends js.Any { - def moment(moment: Moment): Moment = js.native - def moment(dateString: String): Moment = js.native - def moment(dateString: String, format: String): Moment = js.native - def moment(dateString: String, format: String, locale: String): Moment = js.native - def moment(dateString: String, format: String, strict: Boolean): Moment = js.native - def moment(dateString: String, format: String, locale: String, strict: Boolean): Moment = js.native - - def moment: MomentSingleton = js.native - + def apply(moment: Moment): Moment = js.native + def apply(dateString: String): Moment = js.native } package object moment { - @inline def moment(moment: Moment): Moment = MomentJsGlobal.moment(moment: Moment) - @inline def moment(dateString: String): Moment = MomentJsGlobal.moment(dateString: String) - @inline def moment(dateString: String, format: String): Moment = MomentJsGlobal.moment(dateString: String, format: String) - @inline def moment(dateString: String, format: String, locale: String): Moment = MomentJsGlobal.moment(dateString: String, format: String, locale: String) - @inline def moment(dateString: String, format: String, strict: Boolean): Moment = MomentJsGlobal.moment(dateString: String, format: String, strict: Boolean) - @inline def moment(dateString: String, format: String, locale: String, strict: Boolean): Moment = MomentJsGlobal.moment(dateString: String, format: String, locale: String, strict: Boolean) - @inline def moment: MomentSingleton = MomentJsGlobal.moment + @inline def moment(moment: Moment): Moment = MomentJsGlobal(moment: Moment) + @inline def moment(dateString: String): Moment = MomentJsGlobal(dateString: String) } package moment{ diff --git a/client/src/main/scala/com/v6ak/zbdb/App.scala b/client/src/main/scala/com/v6ak/zbdb/App.scala index f69c4dd..c7b387d 100644 --- a/client/src/main/scala/com/v6ak/zbdb/App.scala +++ b/client/src/main/scala/com/v6ak/zbdb/App.scala @@ -1,26 +1,35 @@ package com.v6ak.zbdb import com.example.moment._ +import com.example.MomentTimezone.tz import org.scalajs.dom +import org.scalajs.dom._ import scala.scalajs.js.Thenable.Implicits._ import scala.scalajs.js.JSON -import scala.scalajs.js.annotation.JSExport +import scala.scalajs.js.annotation._ import scala.util.{Failure, Success} import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import scala.scalajs.js object App { + private def init(): Unit = { + // Modules with side effects + Require.require("bootstrap/js/dist/dropdown") + Require.require("chartjs-adapter-moment") + } + @JSExport def main(args: Array[String]): Unit = { + init() dom.window.onload = { (_: Any) => val body = dom.window.document.body val fileName = body.getAttribute("data-file") val maxHourDelta = body.getAttribute("data-max-hour-delta").toInt val formatVersionNumber = body.getAttribute("data-format-version").toInt - val startTime = moment.tz(body.getAttribute("data-start-time"), body.getAttribute("data-timezone")) - val endTime = moment.tz(body.getAttribute("data-end-time"), body.getAttribute("data-timezone")) + val startTime = tz(body.getAttribute("data-start-time"), body.getAttribute("data-timezone")) + val endTime = tz(body.getAttribute("data-end-time"), body.getAttribute("data-timezone")) if(!startTime.isValid()) sys.error("startTime is invalid") if(!endTime.isValid()) sys.error("endTime is invalid") dom.console.log("startTime", startTime.toString) diff --git a/client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala b/client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala index 036adc5..ee8b1aa 100644 --- a/client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala +++ b/client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala @@ -6,7 +6,7 @@ import scala.scalajs.js.annotation._ import scalatags.JsDom.all._ @js.native -@JSGlobal("bootstrap.Modal") +@JSImport("bootstrap/js/dist/modal", JSImport.Namespace) class BsModal (el: Element) extends js.Any { def show(): Unit = js.native } diff --git a/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala b/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala index c1c995a..89fd006 100644 --- a/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala +++ b/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala @@ -9,14 +9,21 @@ import scalatags.JsDom.all._ import Bootstrap.DialogUtils import com.example.moment.{Moment, moment} -@JSGlobal @js.native class Chart(el: Element, data: js.Any) extends js.Object { +@JSImport("chart.js", "Chart") +@js.native +class Chart(el: Element, data: js.Any) extends js.Object { def resize(): Unit = js.native def update(): Unit = js.native def destroy(): Unit = js.native } +@JSImport("chart.js", "Chart") +@js.native +object Chart extends js.Any { + def register(module: js.Any*): Unit = js.native +} + object ChartJsUtils { - // Chart.register(…) def zeroMoment = moment("2000-01-01") // We don't want to mutate it diff --git a/client/src/main/scala/com/v6ak/zbdb/Require.scala b/client/src/main/scala/com/v6ak/zbdb/Require.scala new file mode 100644 index 0000000..afe218e --- /dev/null +++ b/client/src/main/scala/com/v6ak/zbdb/Require.scala @@ -0,0 +1,11 @@ +package com.v6ak.zbdb + +import scala.scalajs.js.annotation._ +import scala.scalajs.js + +@JSGlobalScope +@js.native +object Require extends js.Object { + def require(name: String): js.Any = js.native + +} diff --git a/pack.sh b/pack.sh index f0fe193..1d26c33 100755 --- a/pack.sh +++ b/pack.sh @@ -13,14 +13,13 @@ version=$( scalaVersion=$( sbt server/scalaVersion | tail -n1 | sed 's/^.* //' | grep -oE '[0-9.A-Z-]+' | head -n2 | tail -n1 | sed 's/^\([^.]*.[^.]*\).*/\1/' ) -scalaJsVersion=1 # TODO: extract if [ -e target/assets ]; then rm -r target/assets fi mkdir -p target mkdir target/assets -unzip -d target/assets "./server/target/scala-$scalaVersion/zbdb-stats-server_sjs${scalaJsVersion}_$scalaVersion-$version-web-assets.jar" +unzip -d target/assets "./server/target/scala-$scalaVersion/zbdb-stats-server_$scalaVersion-$version-web-assets.jar" if [ -e pack.zip ]; then diff --git a/project/plugins.sbt b/project/plugins.sbt index 6a6aaf6..549ee42 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -31,6 +31,8 @@ addSbtPlugin("com.github.karelcemus" % "sbt-filter" % "1.1.0") addSbtPlugin("org.github.ngbinh" % "sbt-simple-url-update" % "1.0.4") -addSbtPlugin("org.scala-js" % "sbt-jsdependencies" % "1.0.2") +addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta43") + +addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.21.1") dependencyOverrides += "org.scala-lang.modules" %% "scala-xml" % "2.1.0"