【问题标题】:sbt-web asset output directory setupsbt-web 资产输出目录设置
【发布时间】:2015-11-22 21:03:20
【问题描述】:

场景

我有一个相当简单的 Scalatra 项目,其中包含 Scala.js 和 LESS,我需要为其创建一个 sbt 构建配置。项目分为三个部分:jvm、js、共享代码。

我当前的构建配置使用 xsbt-web-plugin 进行 WAR 打包,我想设置 sbt-web 以便它可以处理 LESS 源的处理。

问题

使用当前配置,当我运行 package 命令时,sbt-web 将资产放入 WEB-INF/classes/main/META-INF/resources/webjars/dataretrieverjvm/0.1.0-SNAPSHOT .我想将它们放在 WEB_INF/public 中,但我不知道如何实现。

这就是我的 Build.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)
}

部分解决方案

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"))

通过这种方式,资产被复制到 WEB-INF/public,这很棒,但遗憾的是 sbt-web 仍然将它们复制到 WEB-INF/classes。 p>

【问题讨论】:

    标签: scala sbt scalatra xsbt-web-plugin sbt-web


    【解决方案1】:

    看起来 META-INF/resources/webjars/dataretrieverjvm/0.1.0-SNAPSHOT 路径可能来自 sbt-web 中的 createWebJarMappings

    如果你去掉它会发生什么?

    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, ""))
      }
    

    【讨论】:

    • 是的,我已经找到了这段代码。我明天去看看。我希望我可以在不分叉插件的情况下关闭此功能。看来我可以简单地跳过这一步。如果不可行,我会提出一个向后兼容的解决方案。我们拭目以待。
    • @Athelionas,您是否已经添加了禁用该功能的可能性?或者我应该使用 James 代码来避免我的资产和 Web 资源被放置在 WEB-INF 文件夹中?是否建议将属于项目本身的资源放在 META-INF 特殊文件夹中?据我所知,这个文件夹应该只包含关于项目的信息(创建者、依赖项、服务提供者等),而不是项目的一部分。
    • 此外。必须知道项目的版本才能从项目的代码中读取网络资源(使用 ClassLoader#getResource(String) ),这很烦人。
    • 请注意,对于 Windows,您应该将正斜杠替换为反斜杠。 val prefix = s"${WEBJARS_PATH_PREFIX}/${moduleName.value}/${version.value}/".relplace("/","\\")
    • 确认这有效并且.css文件在正确的位置结束。
    【解决方案2】:

    可以使用org.webjars.WebJarAssetLocator,它有助于将部分路径解析为 webjar 中的完整路径。这就是它的工作原理:给定 HTML 页面中的链接,例如 <script src="/js/bootstrap.min.js"></script>,可以将 Servlet 配置为根据类路径解析路径。

    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);
        }
    }
    

    然后可以放置一个 servlet 来处理 "/*" 映射,并只提供来自该类路径的资源。假设您的资源位于/META-INF/resources - 适用于 Servlet 3.0+。

    【讨论】:

      猜你喜欢
      • 2018-09-13
      • 2022-06-22
      • 1970-01-01
      • 2013-04-21
      • 2017-02-25
      • 1970-01-01
      • 2015-07-25
      • 2018-08-21
      • 1970-01-01
      相关资源
      最近更新 更多