能否脱离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
相关产品推荐
相关产品推荐

