Scenario
场景
I have a fairly simple Scalatra project with Scala.js and LESS for which I need to create an sbt build configuration. The project is separated into three parts: jvm, js, shared code.
我有一个非常简单的Scala项目。需要创建sbt构建配置所需的js和更少。项目分为三个部分:jvm、js和共享代码。
My current build config uses xsbt-web-plugin for WAR packaging and I'd like to set up sbt-web so it could deal with the processing of LESS sources.
我目前的构建配置使用xsbt-web插件进行WAR打包,我想建立sbt-web,这样它就可以处理更少的源。
The Issue
这个问题
With current config, when I run package command, sbt-web puts the assets in WEB-INF/classes/main/META-INF/resources/webjars/dataretrieverjvm/0.1.0-SNAPSHOT. I'd like to place them in WEB_INF/public instead, but I can't figure out how I could achieve that.
使用当前配置,当我运行包命令时,sbt-web将资产放在WEB-INF/class /main/META-INF/resources/webjar /dataretrieverjvm/0.1.0 snapshot中。我想把它们放到WEB_INF/public中,但是我不知道如何实现它。
This is how my Build.scala looks like at the moment:
这就是我的构建方式。scala现在看起来是:
import org.scalajs.sbtplugin.cross.CrossProject
import sbt._
import com.earldouglas.xwp._
import play.twirl.sbt._
import org.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import com.typesafe.sbt.web._
import com.typesafe.sbt.web.SbtWeb.autoImport._
import com.typesafe.sbt.less.SbtLess.autoImport._
object DataRetrieverBuild extends Build {
private val organization = "Foobar Ltd"
private val scalaVersion = "2.11.7"
private val scalaBinaryVersion = "2.11"
private val scalatraVersion = "2.3.1"
private val akkaVersion = "latest.release"
private val scalacOptions = Seq(
"-unchecked", "-deprecation", "-Yinline-warnings", "-optimise", "-target:jvm-1.8", "-Xlint", "-feature"
)
private val javacOptions = Seq(
"-Xlint:all"
)
private val jvmLibraryDependencies = Def.setting(
Seq(
"ch.qos.logback" % "logback-classic" % "latest.release",
"com.mchange" % "c3p0" % "latest.release",
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.play" %% "anorm" % "latest.release",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"org.apache.commons" % "commons-email" % "latest.release",
"org.json4s" %% "json4s-jackson" % "latest.release",
"org.scalatra" %% "scalatra" % scalatraVersion,
"org.scalatra" %% "scalatra-auth" % scalatraVersion,
"org.slf4j" % "slf4j-api" % "latest.release",
"com.ibm.tools.target" % "was-liberty" % "8.5.x.3" % "provided"
)
)
private val jsLibraryDependencies = Def.setting(
Seq(
"be.doeraene" %%% "scalajs-jquery" % "latest.release",
"org.scala-js" %%% "scalajs-dom" % "latest.release",
"org.webjars" % "bootstrap" % "3.3.5" exclude("org.webjars", "jquery")
)
)
private val jsWebjarDependencies = Def.setting(
Seq(
"org.webjars" % "jquery" % "2.1.4" / "jquery.js" minified "jquery.min.js",
"org.webjars" % "underscorejs" % "1.8.3" / "underscore.js" minified "underscore-min.js",
"org.webjars" % "bootstrap" % "3.3.5" / "bootstrap.js" minified "bootstrap.min.js" dependsOn("jquery.js", "underscore.js", "moment.js"),
"org.webjars" % "ractive" % "0.7.1" / "ractive.js" minified "ractive.min.js",
"org.webjars" % "momentjs" % "2.10.6" / "moment.js" minified "moment.min.js"
)
)
private[this] val artifactPath = file(".")
private val autoAPIMappings = true
private val scalaDocOptions = Seq(
"-implicits", "-diagrams"
)
private lazy val sharedBuildSettings = Seq(
Keys.organization := organization,
Keys.name := "DataRetrieverShared",
Keys.version := "0.1.0-SNAPSHOT",
Keys.scalaVersion := scalaVersion,
Keys.scalaBinaryVersion := scalaBinaryVersion,
Keys.scalacOptions ++= scalacOptions,
Keys.scalacOptions in (Compile, Keys.doc) ++= scalaDocOptions ++ Opts.doc.title("DataRetrieverShared"),
Keys.javacOptions ++= javacOptions,
Keys.target in (Compile, Keys.doc) := file("jvm-api"),
Keys.autoAPIMappings := autoAPIMappings
)
private lazy val jvmBuildSettings = Seq(
Keys.organization := organization,
Keys.name := "DataRetrieverJVM",
Keys.version := "0.1.0-SNAPSHOT",
Keys.scalaVersion := scalaVersion,
Keys.scalaBinaryVersion := scalaBinaryVersion,
Keys.scalacOptions ++= scalacOptions,
Keys.scalacOptions in (Compile, Keys.doc) ++= scalaDocOptions ++ Opts.doc.title("DataRetrieverJVM"),
Keys.javacOptions ++= javacOptions,
Keys.checksums in Keys.update := Nil,
Keys.resolvers ++= Seq(
"IBM" at "http://public.dhe.ibm.com/ibmdl/export/pub/software/websphere/wasdev/maven/repository/"
),
Keys.libraryDependencies ++= jvmLibraryDependencies.value,
Keys.artifactPath in Keys.`package` ~= { defaultPath => artifactPath / defaultPath.getName },
Keys.artifactName in Keys.`package` := {
(sv: ScalaVersion, module: ModuleID, artifact: Artifact) =>
s"${artifact.name}.${artifact.extension}"
},
WebappPlugin.autoImport.webappWebInfClasses := true,
LessKeys.compress in Assets := true,
WebKeys.webTarget := Keys.target.value / "webapp" / "WEB-INF",
Keys.target in (Compile, Keys.doc) := file("jvm-api"),
Keys.autoAPIMappings := autoAPIMappings
)
private lazy val jsBuildSettings = Seq(
Keys.organization := organization,
Keys.name := "DataRetrieverJS",
Keys.version := "0.1.0-SNAPSHOT",
Keys.scalaVersion := scalaVersion,
Keys.scalaBinaryVersion := scalaBinaryVersion,
Keys.scalacOptions ++= scalacOptions,
Keys.scalacOptions in (Compile, Keys.doc) ++= scalaDocOptions ++ Opts.doc.title("DataRetrieverJS"),
Keys.libraryDependencies ++= jsLibraryDependencies.value,
jsDependencies ++= jsWebjarDependencies.value,
Keys.skip in packageJSDependencies := false,
Keys.target in (Compile, Keys.doc) := file("js-api"),
Keys.autoAPIMappings := autoAPIMappings
) ++ (
Seq(packageJSDependencies, fastOptJS, fullOptJS) map { packageJSKey =>
Keys.crossTarget in(Compile, packageJSKey) := Keys.baseDirectory.value / ".." / "jvm" / "src" / "main" / "webapp" / "WEB-INF" / "js"
}
)
lazy val root = Project(
id = "data-retriever-root",
base = file(".")
).aggregate(dataRetrieverJVM, dataRetrieverJS)
lazy val dataRetriever = CrossProject(
id = "data-retriever",
base = file("."),
crossType = CrossType.Full
).settings(
Defaults.coreDefaultSettings ++ sharedBuildSettings:_*
).jvmSettings(
Defaults.coreDefaultSettings ++ jvmBuildSettings:_*
).jsSettings(
Defaults.coreDefaultSettings ++ jsBuildSettings:_*
)
lazy val dataRetrieverJS = dataRetriever.js.enablePlugins(ScalaJSPlugin)
lazy val dataRetrieverJVM = dataRetriever.jvm.enablePlugins(WarPlugin, SbtTwirl, SbtWeb)
}
Partial Solution
部分解决方案
WebKeys.exportedAssets in Assets := SbtWeb.syncMappings(Keys.streams.value.cacheDirectory, (WebKeys.exportedMappings in Assets).value, Keys.target.value / "webapp" / "WEB-INF" / "public"),
WebKeys.exportedMappings in Assets := (WebKeys.exportedMappings in Assets).value.map(item => item._1 -> item._2.replaceAll("""(.*(/|\\))*(.*)""", "$3"))
This way the assets are copied to WEB-INF/public which is great, but sadly sbt-web still copies them to WEB-INF/classes.
这样,资产就被复制到WEB-INF/public,这很好,但是不幸的是,sbt-web仍然将它们复制到WEB-INF/类。
2 个解决方案
#1
3
It looks like the META-INF/resources/webjars/dataretrieverjvm/0.1.0-SNAPSHOT path might be coming from createWebJarMappings
in sbt-web.
看起来META-INF/resources/webjar /dataretrieverjvm/0.1.0快照路径可能来自sbt-web中的createwebjarments。
What happens if you strip it out?
如果去掉它会怎么样?
WebKeys.exportedMappings in Assets :=
(WebKeys.exportedMappings in Assets).value map { case (file, string) =>
import org.webjars.WebJarAssetLocator.WEBJARS_PATH_PREFIX
val prefix = s"${WEBJARS_PATH_PREFIX}/${moduleName.value}/${version.value}/"
(file, string.replace(prefix, ""))
}
#2
0
One could use the org.webjars.WebJarAssetLocator
which helps to resolve partial paths into full paths in webjars. That's how it works: given a link in the HTML page like <script src="/js/bootstrap.min.js"></script>
a Servlet could be configured to resolve paths against classpath.
可以使用org.webjar。WebJarAssetLocator帮助在webjar中将部分路径解析为完整路径。这就是它的工作原理:在HTML页面中提供一个链接,比如
Example of a resource manager for Undertow:
资源管理器的示例:
public class WebLocatorWithClassPathFallbackResourceManager extends ClassPathResourceManager {
private WebJarAssetLocator webJarAssetLocator = new WebJarAssetLocator();
public WebLocatorWithClassPathFallbackResourceManager(ClassLoader classLoader) {
super(classLoader);
}
@Override
public Resource getResource(String resourcePath) throws IOException {
try {
return super.getResource(webJarAssetLocator.getFullPath(resourcePath));
} catch (MultipleMatchesException|IllegalArgumentException e) {
// handle mismatch
}
return super.getResource(resourcePath);
}
}
Then one could place a servlet to handle "/*"
mapping and just serve resources from that classpath. It is supposed that your resources are located in /META-INF/resources
- which is available for Servlet 3.0+.
然后可以放置一个servlet来处理“/*”映射,并只提供来自该类路径的资源。假设您的资源位于/META-INF/resources中——Servlet 3.0+可用。
#1
3
It looks like the META-INF/resources/webjars/dataretrieverjvm/0.1.0-SNAPSHOT path might be coming from createWebJarMappings
in sbt-web.
看起来META-INF/resources/webjar /dataretrieverjvm/0.1.0快照路径可能来自sbt-web中的createwebjarments。
What happens if you strip it out?
如果去掉它会怎么样?
WebKeys.exportedMappings in Assets :=
(WebKeys.exportedMappings in Assets).value map { case (file, string) =>
import org.webjars.WebJarAssetLocator.WEBJARS_PATH_PREFIX
val prefix = s"${WEBJARS_PATH_PREFIX}/${moduleName.value}/${version.value}/"
(file, string.replace(prefix, ""))
}
#2
0
One could use the org.webjars.WebJarAssetLocator
which helps to resolve partial paths into full paths in webjars. That's how it works: given a link in the HTML page like <script src="/js/bootstrap.min.js"></script>
a Servlet could be configured to resolve paths against classpath.
可以使用org.webjar。WebJarAssetLocator帮助在webjar中将部分路径解析为完整路径。这就是它的工作原理:在HTML页面中提供一个链接,比如
Example of a resource manager for Undertow:
资源管理器的示例:
public class WebLocatorWithClassPathFallbackResourceManager extends ClassPathResourceManager {
private WebJarAssetLocator webJarAssetLocator = new WebJarAssetLocator();
public WebLocatorWithClassPathFallbackResourceManager(ClassLoader classLoader) {
super(classLoader);
}
@Override
public Resource getResource(String resourcePath) throws IOException {
try {
return super.getResource(webJarAssetLocator.getFullPath(resourcePath));
} catch (MultipleMatchesException|IllegalArgumentException e) {
// handle mismatch
}
return super.getResource(resourcePath);
}
}
Then one could place a servlet to handle "/*"
mapping and just serve resources from that classpath. It is supposed that your resources are located in /META-INF/resources
- which is available for Servlet 3.0+.
然后可以放置一个servlet来处理“/*”映射,并只提供来自该类路径的资源。假设您的资源位于/META-INF/resources中——Servlet 3.0+可用。