Я пытаюсь создать многомодульный проект scala sbt с React. js front-end, где один модуль объединяет Play с React. js. Чтобы добиться этого, я следовал инструкциям, приведенным здесь , хотя из-за многомодульной структуры это кажется немного сложнее. Проект имеет следующую структуру:
Веб-приложение подпроекта имеет React. js интерфейс в каталоге "ui".
Проблема в том, что FrontendRunHook не запускает «npm run start» после успешного запуска игры. Этот метод запускается, но процесс не запускается, и у меня нет React. js пользовательский интерфейс, если я не запускаю "npm run start" вручную. Другой хук (beforeStarted), который запускает "npm install", работает нормально.
FrontendRunHook. scala
import play.sbt.PlayRunHook
import sbt._
import scala.sys.process.Process
/**
* Frontend build play run hook.
* https://www.playframework.com/documentation/2.8.x/SBTCookbook
*/
object FrontendRunHook {
def apply(base: File): PlayRunHook = {
object UIBuildHook extends PlayRunHook {
var process: Option[Process] = None
/**
* Change the commands in `FrontendCommands.scala` if you want to use Yarn.
*/
var install: String = FrontendCommands.dependencyInstall
var run: String = FrontendCommands.serve
// Windows requires npm commands prefixed with cmd /c
if (System.getProperty("os.name").toLowerCase().contains("win")){
install = "cmd /c" + install
run = "cmd /c" + run
}
/**
* Executed before play run start.
* Run npm install if node modules are not installed.
*/
override def beforeStarted(): Unit = {
if (!(base / "webapp" / "ui" / "node_modules").exists()) Process(install, base / "webapp" / "ui").!
}
/**
* Executed after play run start.
* Run npm start
*/
override def afterStarted(): Unit = {
process = Some(
Process(run, base / "webapp" / "ui").run
)
}
/**
* Executed after play run stop.
* Cleanup frontend execution processes.
*/
override def afterStopped(): Unit = {
process.foreach(p => p.destroy())
process = None
}
}
UIBuildHook
}
}
ui-build.sbt
import java.io.File
import scala.sys.process.Process
/*
* UI Build hook Scripts
*/
// Execution status success.
val Success = 0
// Execution status failure.
val Error = 1
// Run serve task when Play runs in dev mode, that is, when using 'sbt run'
// https://www.playframework.com/documentation/2.8.x/SBTCookbook
PlayKeys.playRunHooks += baseDirectory.map(FrontendRunHook.apply).value
// True if build running operating system is windows.
val isWindows = System.getProperty("os.name").toLowerCase().contains("win")
// Execute on commandline, depending on the operating system. Used to execute npm commands.
def runOnCommandline(script: String)(implicit dir: File): Int = {
if(isWindows){ Process("cmd /c set CI=true&&" + script, dir) } else { Process("env CI=true " + script, dir) } }!
// Check of node_modules directory exist in given directory.
def isNodeModulesInstalled(implicit dir: File): Boolean = {
(dir / "node_modules").exists()
}
// Execute `npm install` command to install all node module dependencies. Return Success if already installed.
def runNpmInstall(implicit dir: File): Int =
if (isNodeModulesInstalled) Success else runOnCommandline(FrontendCommands.dependencyInstall)
// Execute task if node modules are installed, else return Error status.
def ifNodeModulesInstalled(task: => Int)(implicit dir: File): Int =
if (runNpmInstall == Success) task
else Error
// Execute frontend test task. Update to change the frontend test task.
def executeUiTests(implicit dir: File): Int = ifNodeModulesInstalled(runOnCommandline(FrontendCommands.test))
// Execute frontend prod build task. Update to change the frontend prod build task.
def executeProdBuild(implicit dir: File): Int = ifNodeModulesInstalled(runOnCommandline(FrontendCommands.build))
// Create frontend build tasks for prod, dev and test execution.
lazy val `ui-test` = taskKey[Unit]("Run UI tests when testing application.")
`ui-test` := {
implicit val userInterfaceRoot = baseDirectory.value / "webapp/ui"
if (executeUiTests != Success) throw new Exception("UI tests failed!")
}
lazy val `ui-prod-build` = taskKey[Unit]("Run UI build when packaging the application.")
`ui-prod-build` := {
implicit val userInterfaceRoot = baseDirectory.value / "webapp/ui"
if (executeProdBuild != Success) throw new Exception("Oops! UI Build crashed.")
}
// Execute frontend prod build task prior to play dist execution.
dist := (dist dependsOn `ui-prod-build`).value
// Execute frontend prod build task prior to play stage execution.
stage := (stage dependsOn `ui-prod-build`).value
// Execute frontend test task prior to play test execution.
test := ((test in Test) dependsOn `ui-test`).value
build.sbt
import Common._
name := "game_cards"
version := "0.1"
lazy val `root` = (project in file("."))
.settings(commonSettings: _*)
.enablePlugins(PlayScala)
.dependsOn(common)
.aggregate(common)
.dependsOn(game)
.aggregate(game)
.dependsOn(statistics)
.aggregate(statistics)
.dependsOn(webapp)
.aggregate(webapp)
lazy val `common` = (project in file("common"))
.settings(commonSettings: _*)
.settings(
inThisBuild(List(
organization := "com.blackjack.statistics"
)),
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play-json" % "2.8.0",
"org.apache.kafka" %% "kafka" % kafkaVersion,
),
fork := true
)
.settings(PB.targets in Compile := Seq(scalapb.gen() -> (sourceManaged in Compile).value))
.enablePlugins(PlayScala)
lazy val `game` = (project in file("game"))
.settings(commonSettings: _*)
.settings(
inThisBuild(List(
organization := "com.blackjack.game"
)),
resolvers += Resolver.bintrayRepo("cakesolutions", "maven"),
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.2.3",
"org.apache.kafka" %% "kafka" % kafkaVersion,
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
"com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test,
"org.scalatest" %% "scalatest" % "3.0.8" % Test,
"com.thesamet.scalapb" %% "compilerplugin" % "0.9.0",
"net.cakesolutions" %% "scala-kafka-client-akka" % "2.0.0"
),
fork := true
)
.settings(
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
)
.settings(PB.targets in Compile := Seq(scalapb.gen() -> (sourceManaged in Compile).value))
.enablePlugins(PlayScala)
.dependsOn(common)
lazy val `statistics` = (project in file("statistics"))
.settings(commonSettings: _*)
.settings(
inThisBuild(List(
organization := "com.blackjack.statistics"
)),
resolvers += Resolver.bintrayRepo("cakesolutions", "maven"),
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.2.3",
"org.apache.kafka" %% "kafka" % kafkaVersion,
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
"com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test,
"org.scalatest" %% "scalatest" % "3.0.8" % Test,
"com.thesamet.scalapb" %% "compilerplugin" % "0.9.0",
"net.cakesolutions" %% "scala-kafka-client-akka" % "2.0.0"
),
fork := true
)
.settings(PB.targets in Compile := Seq(scalapb.gen() -> (sourceManaged in Compile).value))
.settings(
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
)
.enablePlugins(PlayScala)
.dependsOn(common)
lazy val `webapp` = (project in file("webapp")).enablePlugins(PlayScala)
.settings(commonSettings: _*)
.settings(watchSources ++= (baseDirectory.value / "webapp" / "ui" ** "*").get)
.settings(
libraryDependencies ++= Seq(
jdbc , ehcache , ws , specs2 % Test , guice,
"org.webjars" %% "webjars-play" % "2.6.3",
"org.webjars" % "angularjs" % "1.7.9",
"org.webjars" % "bootstrap" % "4.4.1"
),
fork := true
)
.settings(
resolvers += "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases"
)
.enablePlugins(PlayScala)
.disablePlugins(PlayFilters)
.settings(
watchSources ++= (baseDirectory.value / "webapp" / "ui" ** "*").get
)