抢先一步
VMware 提供培训和认证,助力你的职业发展。
了解更多Web 应用通常严重依赖我们所说的静态资源,例如 Javascript、CSS 和图像文件。在 Grails 应用中,它们被放置在项目的web-app目录下,然后从 HTML 中引用。例如,
<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
将创建指向文件web-app/css/main.css的链接。这一切都非常简单。你甚至可能认为当前的支持已完全满足任何人的需求。你还想做些什么呢?
这是个好问题。答案取决于你应用的复杂性,但让我们从上面提到的 CSS 链接示例开始。为什么我们必须完整地输入<link rel="..." href=...>?只需查看扩展名,我们就知道该资源是一个 CSS 文件。我们也知道 CSS 文件应该使用上面的元素链接到 HTML 页面中。所以我们基本上是在为一个 Grails 本身应该能够处理的事情进行大量重复输入。
“好的,”我听到你这样说,“但是我们可以为 CSS 链接添加一个标签。”确实可以,而且 Grails 2 将包含一个<g:external/>标签,它会智能地为资源选择合适的链接类型。但现在考虑一下生产环境:将 CSS 和 Javascript 文件捆绑在一起不是个好主意吗?压缩它们怎么样?(顺便说一句,雅虎提供了一个名为 ySlow 的工具,它可以为你提供关于这些优化的建议)。那么我们如何实现这些优化呢?你可能可以临时处理,但这样做会限制你的选择,因为你无法定义资源之间的关系。我们真正想要的是一种在中心位置声明资源并指示哪些资源应该被处理和捆绑在一起的方法。
如果我还没有说服你静态资源可以有更好的管理方式,那么请考虑一下 Grails 插件。许多插件提供了它们自己的静态资源。事实上,有些插件提供的资源彼此相同。例如,旧版本的 YUI、Bubbling 和 Grails UI 插件都包含了 YUI 库。我们真的需要多个完整的 Javascript 库副本吗?当然不需要。部分问题可以通过插件间的依赖来解决,但这本身效果不佳,因为插件无法声明其某些资源依赖于另一个插件中的特定资源。最终,开发者需要确保所有合适的资源(包括传递性依赖)都包含在每个页面中。这可能需要一些反复试验才能确保所有必需的资源都链接到页面中,并且顺序正确。
幸运的是,我们可以做得更好。请看 Resources 插件。
只需安装这一个插件,你就可以开始在可重用的模块中声明你的静态资源(Javascript、CSS 等)。然后你可以在模块之间定义依赖关系,这样 Grails 就能准确知道一个模块需要哪些资源,包括那些在传递性依赖中指定的资源。此外,模块还能确保资源在页面中总是按照正确的顺序声明,并且没有重复。通过这种方式,模块极大地减轻了静态资源管理的麻烦。
你可以在应用和插件中声明资源模块。有几种方法可以实现,但最常见的方法是向项目中添加一个或多个专用的 artifact。对于应用来说,这可能是grails-app/conf/ApplicationResources.groovy。另一个例子是 YUI 插件中的grails-app/conf/YuiPluginResources.groovy。这些文件的基本结构如下所示
modules = {
core {
resource url: 'js/core.js', disposition: 'head'
resource url: 'js/ui.js'
resource url: 'css/main.css'
resource url: 'css/branding.css'
resource url: 'css/print.css', attrs: [media: 'print']
}
utils {
dependsOn 'jquery'
resource url: 'js/utils.js'
}
forms {
dependsOn 'core', 'utils'
resource url: 'css/forms.css'
resource url: 'js/forms.js'
}
}
"core"、"utils" 和 "forms" 是我们应用模块的名称,正如你可能猜到的那样,"forms" 模块依赖于 "core" 和 "utils"。你还可以看到 "utils" 模块依赖于 "jquery",这是一个由 jQuery 插件提供的模块。这种结构最好通过图表形式来表示
如果我们更深入地研究上面的模块定义,我们可以看到模块内的单个资源是使用 "resource" 加上 URL 来声明的。这个 URL 是资源相对于项目中的web-app目录的位置。如果你愿意,你还可以添加额外的属性来精细控制资源,特别是通过 "disposition" 设置。
有两种标准的 disposition(处置):"head",表示资源放在 <head> 元素内;"defer",通常表示放在 body 的末尾(尽管你可以控制精确的位置,稍后你会看到)。默认情况下,CSS 文件的 disposition 是 "head",而 Javascript 文件使用 "defer",但这些默认设置可以在每个资源的基础上被覆盖。
现在我们有了定义简单或复杂资源模块网络的基础,这些网络可以跨越应用和插件边界。从用户的角度来看,更好的是,插件需要确保其模块包含适当的资源和依赖关系。
因此,Grails 现在可能知道你的应用及其插件提供了哪些静态资源,但它仍然不知道哪些页面需要哪些资源。为此,你需要对 GSP 文件进行一些修改。
如你所知,之前你必须在布局和视图中明确声明所有 CSS 和 Javascript 链接。那么 Resources 插件如何改变这一点呢?不是放置指向单个资源的链接,而是声明你的页面需要哪些模块,这使得<head>代码块更加简洁。此外,你还可以指定资源链接应该放在页面中的哪个位置。这是一个使用 Resources 插件的非常简单的布局示例
<html>
<head>
...
<r:require modules="common, jquery"/>
<r:layoutResources/>
</head>
<body>
...
<r:layoutResources/>
</body>
</html>
如你所料,<r:require>标签告诉 Grails 应该在页面中包含哪些 静态资源,而<r:layoutResources>标签指定链接应该 放在哪里。你应该有两个<r:layoutResources>标签:一个放在<head>中,另一个放在<body>中。任何 disposition 设置为 "head" 的资源将放在第一个标签的位置,而 disposition 设置为 "defer" 的资源将插入到第二个标签的位置。
就是这样!大部分工作在于确保你的模块定义是正确的。
我到目前为止介绍的对于 CSS、Javascript 和 favicon 文件非常有效,因为 Resources 插件知道要为它们生成什么链接以及将这些链接放在哪里。但是内联图像和脚本怎么办呢?
Resources 插件将内联图像和脚本称为“即时资源”(ad-hoc resources)。这些资源通常不声明在模块中,只是在页面中遇到时进行处理。一个标准的内联图像链接看起来像这样
<img src="${resource(dir:'images',file:'grails_logo.png')}" ... />
那么我们如何让 Resources 插件知道这个图像文件呢?我们不需要!从 Grails 2.0 开始,<g:resource/>标签(上面用作方法)如果插件已安装,会自动将资源注册到插件中。这意味着 mappers 执行的所有“魔法”(稍后我会讲到)都将应用于给定的资源。当然,<g:resource/>可以用于任何类型的资源,而不仅仅是图像。
内联脚本有点不同,因为它们不是指向外部文件的链接。但它们仍然可以从 Resources 插件中受益。默认情况下,使用<g:javascript/>标签声明的所有内联脚本将按它们一贯的方式运行。但是如果你将<g:javascript/>替换为<r:script/>标签,内联脚本将被移动到第二个<r:layoutResources/>的位置(它们的默认 disposition 是 "defer")。你也可以声明一个明确的 disposition,以便脚本进入<head>中。最重要的是,内联脚本保留了它们在页面中声明的顺序。
还有一个需要提到的即时资源来源:CSS。样式表是指定背景图和其他类型图片的常用方法,但 CSS 文件不像 GSP 文件那样被处理。Resources 插件是否知道这些链接呢?幸运的是,是的。即使 Resources 插件修改了它管理的所有资源的 URL 路径,这对你来说也无关紧要,因为插件也会自动更新 CSS 文件中的链接。一切都会正常工作!
就其本身而言,Resources 插件使得静态资源的管理比以前简单得多。但这只是故事的一部分。由于插件知道所有资源并控制它们的链接生成,它可以执行额外的处理以添加有趣的行为。这种处理是通过一个可扩展的 mappers 流水线完成的
不要将此图表视为绝对正确参考:作为用户,确切的流水线及其工作方式并不重要。关键是如果你愿意,你可以添加自己的 mapper 实现,或者简单地安装一个提供自己 mapper 的插件。结果是几乎零成本地获得了一些非常强大的功能。
例如,假设你已经安装了 Resources 插件,定义了一些模块,并且你的 GSP 视图和布局已设置为使用适当的标签。只需安装 Cached Resources 和 Zipped Resources 插件,你将立即开始满足一些 ySlow 建议,例如为所有静态资源设置一个长期的 Expires 头部和启用 gzip 压缩(尽管你可以禁用特定文件类型(如图像)的 gzip 压缩,因为它们通常已经压缩过了)。这不应该这么容易,对吧?但它确实是。
Resources 插件已经存在一段时间了,并且已经在一些生产站点中使用。它甚至在 Grails 网站 上使用。这不足为奇:它极大地改进了静态资源管理,并提供了一个 mapper 流水线,实现了易于使用但功能强大的特性。经验丰富的 Web 开发人员会立即注意到其中的区别,并且可以预见未来会有越来越多的插件支持 Resources。它甚至有自己的用户指南。
由于该插件带来的优势,Grails 2 将其设为新 Grails 项目的默认插件,并将其集成到一些核心标签中,例如<g:resource>。但即使你还不能使用 Grails 2,你仍然可以在旧版本的 Grails 中使用它,并以很少的努力获得出色的资源管理益处。它的一个关键设计原则是,它可以在任何 Grails 应用中安装而不会破坏任何东西。
无论你使用哪个版本的 Grails,安装此插件都将改善你作为 Web 开发人员的生活。这可不是小事一桩。