用公共资产工作

本章会讲到提供你的应用程序的静态资源,如JavaScript, CSS 和图像。

在Play中提供公共资源和服务其它任何HTTP请求是相同的。它使用同样的路由作为常规资源,使用 controller/action 的路径来发布CSS, JavaScript或图像文件到客户端。

public/ 文件夹

按照惯例公共资产保存在应用程序的public 文件夹。可以将此文件夹按您喜欢的方式组织。我们推荐以下方式:

public
 └ javascripts
 └ stylesheets
 └ images

按照这个结构你就可以简单开始了, 但你理解其工作原理后可以随便修改它。

WebJars

WebJars 提供一个便利和传统的打包机制,它是Activator的sbt的一部分。例如你可以声明使用流行的Bootstrap 库 ,简单地通过添加以下依赖到你的构建文件:

libraryDependencies += "org.webjars" % "bootstrap" % "3.3.4"

为方便,WebJars 自动提取到与公共资产相关的lib 文件夹。例如, 如果你声明一个依赖RequireJs ,然后你可以从视图引用它,如下:

<script data-main="@routes.Assets.at("javascripts/main.js")" type="text/javascript" src="@routes.Assets.at("lib/requirejs/require.js")"></script>

注意这个lib/requirejs/require.js 路径。lib 文件夹表示提取WebJar assets, requirejs 文件夹对应于WebJar artifactId, 和require.js 引用到WebJar的根所指的需要的资产。

公共资产是如何打包的?

在构建处理期间, public 文件夹的内容被处理和添加到应用程序的类路径。

当你打包你的应用程序, 应用程序的所有资产, 包括所有子项目,都聚合到单个jar中, 在target/my-first-app-1.0.0-assets.jar这里。这个jar包括到发布中,因此Play应用程序可以提供他们。这个jar还可以用于部署资产到CDN或反向代理。

资产控制器

Play自带内置控制器来提供公共资产。默认情况下, 这个控制器提供缓存, ETag, gzip和压缩支持。

这个控制器可以在默认的Play JAR获得,为controllers.Assets,并且定义了带二个参数的单个at action:

Assets.at(path: String, file: String)

path 参数必须是固定的,并且定义由action管理的目录。file 参数通常从请求路径中动态提取。

这里是在conf/routes 文件中常用的Assets 控制器的映射:

GET  /assets/*file        controllers.Assets.at(path="/public", file)

注意我们定义*file 动态部分匹配.* 正则表达式。 因此, 如果你发送这个请求到服务器:

GET /assets/javascripts/jquery.js

路由会调用Assets.at action,并带以下参数:

controllers.Assets.at("/public", "javascripts/jquery.js")

这个 action会查找和提供文件(如果文件存在的话)。

公共资产的反向路由

对于在路由文件中映射的任何控制器, 一个反向路由会创建在controllers.routes.Assets。你可使用这个来反转需要获取公共资源的URL。例如, 从一个模板:

<script src="@routes.Assets.at("javascripts/jquery.js")"></script>

这会产生以下结果:

<script src="/assets/javascripts/jquery.js"></script>

注意当使用反向路由时,我们没有指定第一个folder 参数。这是因为我们的路由文件定义了单个Assets.at action映射,而folder 参数已经固定。因此它不需要指定。

但是,如果你定义二个Assets.at action映射, 像这个:

GET  /javascripts/*file        controllers.Assets.at(path="/public/javascripts", file)
GET  /images/*file             controllers.Assets.at(path="/public/images", file)

那么当使用反向路由时,你就需要指定二个参数:

<script src="@routes.Assets.at("/public/javascripts", "jquery.js")"></script>
<img src="@routes.Assets.at("/public/images", "logo.png")" />

公共资产的反向路由和指纹识别(fingerprinting)

sbt-web 给Play带来高度可配置的资产管道, 比如在你的构建文件:

pipelineStages := Seq(rjs, digest, gzip)

上述代码会按RequireJs 优化 (sbt-rjs), digester (sbt-digest) 和接着是压缩(sbt-gzip)来排序。不像许多sbt任务, 这些任务按照声明的顺序执行, 一个接一个。

本质上资产指纹允许您的静态资产与积极的缓存指令提供到浏览器。这将导致一种改进的用户体验,因为以后访问您的网站将减少需要下载的资产。Rails还描述了资产指纹的好处。

上述pipelineStages 的声明和必需的在plugins.sbt中的addSbtPlugin 声明的插件是你需要的起点。你必须接着声明Play要什么版本化的资产。以下路由文件记录声明所有版本化的资产:

GET  /assets/*file  controllers.Assets.versioned(path="/public", file: Asset)

确保你通过写上file: Asset指出这个file 是一个资产。

然后你可以使用反向路由, 例如在scala.html 视图内:

<link rel="stylesheet" href="@routes.Assets.versioned("assets/css/app.css")">

我们强烈建议使用资产指纹。

Etag 支持

Assets 控制器自动管理 ETag HTTP 标头。ETag 值是从 digest 生成(如果 sbt-digest 开始使用于资产管道) ,否则是从资源名称和文件的最后修改日期。如果资源文件嵌入到文件, JAR文件的最后修改日期会被使用。

当web浏览器创建一个请求指定这个 Etag ,那么服务器可以响应 304 NotModified

Gzip 支持

如果具有相同名称的资源但使用.gz后缀,那么Assets 控制器也会提供后者,并添加以下HTTP标头:

Content-Encoding: gzip

包含sbt-gzip 插件到你的构建,并在pipelineStages声明它的位置, 所有需求的文件会生成gzip文件。

额外的Cache-Control 指令

对于缓存为目的来说使用 Etag 通常就够了。但是如果您要为特殊的资源指定一个自定义 Cache-Control 标头,你可以在application.conf 文件中指定它。例如:

# Assets configuration
# ~~~~~
"assets.cache./public/stylesheets/bootstrap.min.css"="max-age=3600"

托管资产

从Play 2.3开始通过基于插件的sbt-web 管理资产处理。在 2.3 之前Play 托管资产处理是以CoffeeScript, LESS, JavaScript linting (ClosureCompiler) 和 RequireJS 优化的形式。以下各节描述 sbt-web 和 如何实现相当于2.2的功能。 尽管注意到 Play 不限于资产处理技术,慢慢地sbt-web上越来越来的插件可以使用。请检出 sbt-web 项目以学习更多关于插件的知识。

许多插件使用sbt-web的js-engine 插件。js-engine 能够执行写入插件到 Node API 或者在JVM内通过优秀的 Trireme 项目, 或直接在性能优越的 Node.js 。注意这些工具仅用在开发周期期间,并且不参与Play应用程序的运行期。如果你安装了Node.js,那么你 可以声明以下环境变量。对于Unix, 如果SBT_OPTS 被定义,那么你可以:

export SBT_OPTS="$SBT_OPTS -Dsbt.jse.engineType=Node"

以上声明确保当执行任何sbt-web插件时Node.js被使用。