Grails 2.0 倒计时:静态资源

工程 | Peter Ledbrook | 2011 年 6 月 30 日 | ...

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 确切地知道一个模块需要哪些资源,包括传递依赖关系中指定的资源。作为额外的好处,模块确保资源始终在页面中按正确的顺序声明,并且没有重复。通过这样做,模块消除了大部分静态资源管理的痛苦。

声明模块

您可以在应用程序和插件中声明资源模块。有几种方法可以做到这一点,但最常见的方法是在项目中添加一个或多个专用工件。对于应用程序,这可能是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",通常表示正文的末尾(尽管您可以控制确切的位置,您很快就会看到)。默认情况下,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 插件将内联图像和脚本称为“临时资源”。这些资源通常不在模块中声明,而是在页面中遇到时按需处理。标准内联图像链接看起来像这样

<img src="${resource(dir:'images',file:'grails_logo.png')}" ... />

那么我们如何使 Resources 插件知道图像文件呢?我们不需要!从 Grails 2.0 开始,<g:resource/>标签(如上所示用作方法)会自动将资源注册到插件(如果已安装)。这意味着映射器执行的所有魔法(我稍后会谈到)都将应用于给定的资源。当然,<g:resource/>可用于任何类型的资源,而不仅仅是图像。

内联脚本略有不同,因为它们不是指向外部文件的链接。但它们仍然可以从 Resources 插件中受益。默认情况下,使用<g:javascript/>标签声明的所有内联脚本的行为都将与往常一样。但是,如果您用<g:javascript/>替换<r:script/>标签,则内联脚本将移动到第二个<r:layoutResources/>的位置(它们的默认 disposition 为 "defer")。您还可以声明一个显式的 disposition,以便脚本进入<head>中。最棒的是,内联脚本保留了在页面中声明的顺序。

还有一个临时资源来源需要提及:CSS。样式表是指定背景和其他类型图像的常用方法,但 CSS 文件不会像 GSP 文件那样处理。Resources 插件是否知道这些链接?幸运的是,是的。即使 Resources 插件修改了其管理的所有资源的 URL 路径,但这对您来说并不重要,因为插件也会自动更新 CSS 文件中的链接。一切都会正常工作!

神奇的映射器

Resources 插件本身使得静态资源的管理比以前简单得多。但这仅仅是故事的一部分。由于插件了解所有资源并控制其链接生成,因此它可以执行额外的处理以添加有趣的行为。此处理由可扩展的映射器管道完成

Resources Mapper Pipeline Diagram

不要将此图视为绝对正确的参考:作为用户,确切的管道及其工作原理实际上并不重要。关键是,如果您愿意,您可以添加自己的映射器实现,或者简单地安装一个提供自身某些映射器的插件。结果是一些非常强大的功能,几乎无需任何努力。

例如,假设您已安装 Resources 插件,定义了一些模块,并且您的 GSP 视图和布局已设置为使用相应的标签。只需安装 Cached ResourcesZipped Resources 插件,您将立即开始满足一些 ySlow 建议,例如拥有长期有效的 Expires 标头以及所有静态资源的 gzip 压缩(尽管您可以禁用特定文件类型的 gzip 压缩,例如图像,因为它们往往已经过压缩)。不应该这么简单,对吧?但事实就是这样。

结论

Resources 插件已经存在一段时间了,并且已经在一些生产环境的网站中使用。它甚至在 Grails 网站 上使用。这也不足为奇:它将静态资源的管理能力大幅提升,并结合了一个映射管道,使您可以轻松使用强大的功能。经验丰富的 Web 开发人员会立即注意到其中的区别,并且您预计未来会有 越来越多的插件 支持 Resources。它甚至有自己的 用户指南

由于该插件带来的优势,Grails 2 将其作为新 Grails 项目的默认插件,并将其集成到一些核心标签中,例如<g:resource>. 但是,即使您还不能使用 Grails 2,您仍然可以在旧版本的 Grails 中使用它,并轻松享受出色的资源管理。其关键设计原则之一是,它可以安装在任何 Grails 应用程序中,而不会破坏任何内容。

无论您使用哪个版本的 Grails,安装此插件都会改善您作为 Web 开发人员的生活。这可不是小事。

获取 Spring Newsletter

与 Spring Newsletter 保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部