You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否脱离sbt插件,仅以库形式使用Playframework?

嘿,太懂这种“爱Play但恨sbt”的心情了——Mill作为轻量构建工具确实香,但Play和sbt的绑定确实是个硬伤,毕竟它的核心功能(路由编译、资源管理这些)都是靠sbt插件来做的。不过你问的能不能把Play纯当库集合来用,答案是完全可以,只是得自己接手原来sbt插件帮你自动化的那些活儿,下面给你拆解具体步骤:

一、需要引入的Play核心库

你需要在Mill的依赖里加入Play的核心库,groupId都是com.typesafe.play,根据你的Scala版本选对应版本(比如2.8.x或2.9.x系列):

  • play-core:包含Play的HTTP基础、请求响应处理、表单验证等核心逻辑
  • play-server:内置的Netty服务器,用来启动HTTP服务
  • play-routes-compiler:关键!用来编译Play的路由文件,原来sbt插件就是调用这个库来生成路由类的
  • play-json:如果需要JSON序列化/反序列化,这个是必备的
  • twirl-compiler:如果用Play的Twirl模板(.scala.html),需要这个库来编译模板

在Mill的build.sc里大概是这样配置:

object myapp extends ScalaModule {
  def scalaVersion = "2.13.12"
  def ivyDeps = Agg(
    ivy"com.typesafe.play::play-core:2.8.20",
    ivy"com.typesafe.play::play-server:2.8.20",
    ivy"com.typesafe.play::play-routes-compiler:2.8.20",
    ivy"com.typesafe.play::play-json:2.8.20",
    ivy"com.typesafe.play::twirl-compiler:1.5.1"
  )
}
二、手动处理路由编译

原来sbt会自动监听conf/routes文件,编译生成对应的Routes类,现在得自己在Mill里加个任务来做这件事。你可以在build.sc里添加一个自定义任务,调用play-routes-compiler的API来编译路由:

import mill._, scalalib._
import play.routes.compiler._

object myapp extends ScalaModule {
  // ... 之前的配置 ...

  // 自定义路由编译任务
  def compileRoutes = T.task {
    val routesFile = T.workspace / "conf" / "routes"
    val outputDir = T.dest / "generated" / "routes"
    val packageName = "com.myapp.routes" // 生成的路由类的包名

    // 配置路由编译选项
    val options = RoutesCompiler.Options(
      namespace = Some(packageName),
      sourceDir = routesFile.parent.get,
      targetDir = outputDir,
      additionalImports = Nil,
      generateReverseRouter = true,
      generateRefReverseRouter = true
    )

    // 执行编译
    RoutesCompiler.compile(routesFile, options)
    PathRef(outputDir)
  }

  // 把生成的路由代码加入源码路径
  override def sources = T.sources {
    super.sources() ++ Seq(compileRoutes())
  }
}

这样Mill构建时就会先编译路由,把生成的类加入源码,之后就能像原来一样调用routes.Routes来处理请求了。

三、手动启动Play应用

原来sbt的run命令会帮你初始化Play应用并启动服务器,现在得自己写main方法来完成这个流程。核心是创建Play的Application实例,然后启动Netty服务器:

import play.core.server.NettyServer
import play.api.{Application, BuiltInComponentsFromContext}
import play.api.mvc._
import com.myapp.routes.Routes // 导入刚才编译生成的路由类

object Main extends App {
  // 手动构建Play的核心组件
  val components = new BuiltInComponentsFromContext {
    // 指定路由,用编译生成的Routes
    override def router: Router = new Routes(httpErrorHandler, controllerComponents)
    
    // 其他组件用默认实现即可,比如配置、错误处理器等
  }

  // 初始化应用
  val app: Application = components.application
  // 启动Netty服务器
  val server = NettyServer.fromApplication(app)

  println(s"Play app started at http://localhost:${server.httpPort.getOrElse(9000)}")
}
四、额外注意事项
  • 资源管理:原来sbt会把conf目录下的配置文件、国际化消息等自动加入classpath,你需要在Mill里配置资源目录:在build.sc里添加def resources = T.sources(Seq(PathRef(T.workspace / "conf")))
  • 模板编译:如果用Twirl模板,类似路由编译,你需要在Mill里加个任务调用twirl-compiler编译.scala.html文件,把生成的类加入源码路径
  • 热重载:Mill也支持热重载,你可以配置~run任务,但需要确保路由、模板的编译任务在代码变更时自动触发,可能需要自定义任务依赖

这样下来,你就能用Mill构建,把Play纯当库来使用,完全摆脱sbt的束缚啦!

内容的提问来源于stack exchange,提问作者caeus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:19:24