UI Performance Plugin
The UI Performance Plugin addresses some of the
14 rules from Steve Souders and the Yahoo performance team.
Starting with version 1.2 the plugin is enabled in all environments, including development. See the discussion below about the 'enabled' config attribute for how to disable it for development.
Installation
To integrate the plugin into your application just run the 'install-plugin' Grails script, e.g.
grails install-plugin ui-performance
The plugin is tested primarily in 1.0.4 and 1.1 but does work in 1.0.3. Due to a change in the name of Events.groovy to _Events.groovy, it doesn't work in 1.0.3 without a small change. After you install the plugin, rename plugins/ui-performance-1.2/scripts/_Events.groovy to Events.groovy. This only needs to be done once, and only for 1.0.3.
Features
- minifies and gzips .js and .css files
- configures .js, .css, and image files (including favicon.ico) for caching by renaming with an increasing build number and setting a far-future expires header
- provides taglibs that are version- and gzip-aware to allow developers to work without dealing with gzip or version details
- gzips dynamic text content (GSPs, JSON, XML, etc.)
- the bulk of the work is done during the build, and problems are caught during the build instead of after deployment. An _Events.groovy script hooks into the war building process and gzips/versions/minifies static resources before the war file is packaged
- rewrites inner urls in CSS files to point to versioned images
- supports Sprite images via a taglib
- helps move JavaScript to the end of the body via a taglib
- doesn’t use a servlet - only a few taglibs and a Filter, letting the container (or an external web server) continue to serve static resources
- no temp files and very minimal runtime cost - everything is in the war - and less processing at the server - it only needs to check if client supports gzip when rendering <javascript>, <css>, and <img> tags in GSPs
- Grails-friendly - can be enabled per-environment so that in development mode it does nothing, and is only enabled in production (or 'qa', if you have a 'qa' environment for example) mode, so there's no need to check for modifications when enabled since Grails doesn't support modifications except in development mode
- lots of configuration options
Dynamic text compression uses the
PJL Compressing Filter with configuration borrowed from the
Compress plugin. See
this discussion about the configuration options borrowed from Compress plugin, and the filter's Sourceforge project page for more details about its configuration.
Motivation
To reduce the download size of JavaScript and CSS files, the plugin gzips .js and .css files. Of course not all browsers support gzip, so the original uncompressed files are kept. The "accept-encoding" header is checked to determine if the gzipped version or uncompressed should be sent.
The plugin also minifies both .js and .css using
YUI Compressor.
Another feature that reduces server load is versioning and far-future "Expires" and "Cache-Control" headers. If you do no caching then each time a browser loads a page, it will make a server request to determine if the each file has been updated. Since static resources rarely change, these requests cause unnecessary server traffic with a 304 response code.
Each time you do a build, a new version is applied to all image, .js, and .css files so files can be cached forever. The next time you deploy a new version of your application, all of the resource file names will have changed, so only then will files be requested.
Of course images aren't very compressible, so gzipping them doesn't make much sense, but the image payload of most sites is going to be a lot larger than that of the .js and .css files, so it makes sense to try to get clients to cache those also.
The plugin optionally gzips the HTML and other dynamic content generated by your GSPs and controllers. If you prefer to configure this yourself in your web server then you can disable this feature but leaving it enabled means more server portability and less configuration. You can configure gzip for HTML and text types in Tomcat by adding
compression='on' compressableMimeType='text/html,text/xml,text/plain'
to the HTTP <Connector> element in conf/server.xml. If you're using Apache to server static content, you'll want to use mod_deflate.
You could configure your web server to compress .css and .js files but this is resource-intensive and wasteful if the files rarely change. The plugin instead compresses the files once at build time.
Usage
The plugin is enabled by default in all environments (including development) so configure whether it's enabled per-environment in Config.groovy, for example
environments {
development {
uiperformance.enabled = false
}
}
to disable it in development but enable it for production or custom environments.
You'll need to make changes in your GSPs to take advantage of these features.
To render a <script> tag for a .js file, use
<p:javascript src='util/collections' />
which will render
<script src='/appcontext/js/util/collections__v123.gz.js'></script>
where 'appcontext' is the context the app is deployed under (blank for the root context). Note that the gzipped version is shown here, and the current version is '123'. Also since the extension is obvious, it's omitted.
To render a <link> tag for a .css file, use
which will render
<link rel='stylesheet' type='text/css' href='/appcontext/css/main__v123.gz.css' />
As with .js files the extension is obvious so it's omitted.
To render an <img> tag for an image file, use
<p:image src='logo.png'/>
which will render
<img src='/appcontext/images/logo.png' border='0' />
To render an <input type='image' ...> tag for an image-based form submit button, use
<p:inputImage src='edit.png'/>
which will render
<input type='image' src='/appcontext/images/edit.png' border='0' />
Note that each tag allows any number of extra attributes which are just passed through to the HTML (e.g. to specify the id, style, etc.)
favicons
By default your favicon.ico (or whatever you've named the file) will be processed like any other image. To disable this, remove the 'ico' image extension from the list of valid extensions:
uiperformance.imageExtensions = ['gif', 'png', 'jpg']
To render a versioned favicon tag, use
which will render
<link rel='shortcut icon' href='/favicon.ico' type='image/x-icon'/>
If you have a nonstandard name or location, specify that using the 'src' attribute (omit the extension):
<p:favicon src='images/myfavicon' />
<link rel='shortcut icon' href='images/myfavicon.ico' type='image/x-icon'/>
CSS images
Embedded image urls in CSS files are a problem using this versioning scheme, but since each file is processed during the build, we can rewrite the urls inside all CSS files with the version embedded, so they also agree with the names in the war, e.g.
background-image: url(/images/cluetip/wait.gif);
becomes
background-image: url(/images/cluetip/wait__v123.gif);
Bundling
Another provided optimization is to reduce the number of static files. Typically you'll partition .css and .js files for modularity and reuse but often send the same few files in several pages. So another step in the build process involves bundling JavaScript and CSS files into larger single files. These are minified and gzipped like the rest, and the net effect is fewer requests with a smaller total size.
Bundles are configued as a list of maps:
- the 'type' property is either 'js' or 'css'
- the 'name' property determines the relative name of the combined file
- and the 'files' property specifies a list of relative filenames
Note that the names of the bundled file and the source files are relative just like they'd be specified using the taglibs above - omit the 'js' or 'css' folder and the extension. For example, this configuration
uiperformance.bundles = [
[type: 'js',
name: 'jquery/jquery.all',
files: ['jquery/jquery-1.2.6',
'jquery/jquery.cluetip']],
[type: 'css',
name: 'bundled',
files: ['yui/reset-min',
'main',
'plugins/custom_cluetip']]
]would create js/jquery/jquery.all.js which would contain the contents of js/jquery/jquery-1.2.6.js and js/jquery/jquery.cluetip.js.
It would also create css/bundled.css which would contain the contents of css/yui/reset-min.css, css/main.css, and css/plugins/custom_cluetip.css.
You can create any number of bundles, which contain any number of source files.
Image Sprites
TBD
DependantJavascriptTagLib
Since browsers evaluate scripts as they appear in the html (in case they write to the document) it's best to include them at the end of the body if possible to decrease the apparent load time (the total page and dependent files still takes the same total time, but the browser appears ready quicker).
To help with this and to deal with Grails' use of Sitemesh and includes, you can use the <p:dependantJavascript> and <p:renderDependantJavascript> tags. Wherever you have <script> and <p:javascript> tags (either inline scripts or external scripts using the 'src' attribute) that don't write to the document, enclose them in <p:dependantJavascript> tags, e.g.
<a href='...'>click here<a>
<div id='the_div'>...</div><p:dependantJavascript>
<script type='text/javascript'>
// inline stuff
</script><p:javascript src='whatever'/></p:dependantJavascript>
which cache the content inside the tag. Then render the cached content using the p:renderDependantJavascript tag (typically in a template after the body), e.g.:
<body><g:layoutBody/><p:renderDependantJavascript /></body>
</html>
Note that you can use as many <p:dependantJavascript> tags as you want in the main gsp, any included templates, and the main template and all of the the combined scripts will be rendered by the single renderDependantJavascript tag.
A new feature allows you to specify a short block of javascript as a parameter to the tag, e.g.
<p:dependantJavascript javascript='var foo = 1; alert(foo);' />
This feature can also be used when calling the taglib from another, e.g.
def firstFieldFocus = { attrs, body ->
String javascript = "javascript that gives focus to the first field in the document"
p.dependantJavascript([javascript:javascript])
}<p:addJavascript> is another tag that generates the <script> tags for you but is otherwise the same as dependantJavascript:
<p:addJavascript>
// your javascript
</p:addJavascript>
which will render
<script type='text/javascript'>
// your javascript
</script>
or
<p:addJavascript src='foo' />
which will render
<script type='text/javascript' src='/js/foo.js'></script>
Customization
The default process for determining the version is to look at the version of the root project folder in the Subversion metadata files. If you're not using Subversion or you want to use some other version incrementing logic, you can define a closure in Config.groovy:
uiperformance.determineVersion = { -> /* calculate version here */ }Also, if there are files or groups of files that you want to exclude from processing, you can define a list of Ant-style matcher patterns, e.g.
uiperformance.exclusions = [
"**/grails_logo.jpg",
"**/dojo/**"
]
Other properties:
| Property | Default Value | Meaning |
|---|
| uiperformance.enabled | true | set to false to disable processing |
| uiperformance.processImages | true | set to false to disable processing for all images |
| uiperformance.processCSS | true | set to false to disable processing for all .css files |
| uiperformance.processJS | true | set to false to disable processing for all .js |
| uiperformance.imageExtensions | 'gif', 'png', 'jpg', 'ico' (+) | specify different values to change image types that are processed |
| uiperformance.minifyJs | true | set to false to disable minification for all .js |
| uiperformance.minifyJsAsErrorCheck | false | set to true to minify .js files in-memory for error checking but discard |
| uiperformance.continueAfterMinifyJsError | false | set to true to only warn about .js minification problems rather than failing the build |
| uiperformance.minifyCss | true | set to false to disable minification for all .css |
| uiperformance.minifyCssAsErrorCheck | false | set to true to minify .css files in-memory for error checking but discard |
| uiperformance.continueAfterMinifyCssError | false | set to true to only warn about .css minification problems rather than failing the build |
| uiperformance.keepOriginals | false | set to true to keep the original uncompressed and unversioned files in the war along with the compressed/versioned files |
| uiperformance.html.compress | false | set to true to enable gzip for dynamic text content |
| uiperformance.html.urlPatterns | none | set to restrict dynamic text url patterns that should be processed |
| uiperformance.html.debug | false | set to true to enable PJL filter debug logging |
| uiperformance.html.statsEnabled | false | set to true to enable PJL filter stats tracking |
| uiperformance.html.compressionThreshold | 1024 | sets the minimum content length for compression |
| uiperformance.html.jakartaCommonsLogger | none | set the category of the commons/log4j logger to debug to |
In addition you can configure include/exclude path patterns
via uiperformance.html.includePathPatterns and uiperformance.html.excludePathPatterns, include/exclude content types via uiperformance.html.includeContentTypes and uiperformance.html.excludeContentTypes, and include/exclude user agents via uiperformance.html.includeUserAgentPatterns and uiperformance.html.excludeUserAgentPatterns. See the Configuration section
here for a discussion of the meaning of these options.
If you don't configure content types, the uiperformance.html.includeContentTypes property is set to ['text/html','text/xml','text/plain'] since if this is left unset, the filter will double-gzip already gzipped .css and .js files.
Author
Burt Beckwith
burt@burtbeckwith.comPlease report any issues to the Grails User mailing list and/or write up an issue in JIRA at
http://jira.codehaus.org/browse/GRAILSPLUGINS under the Grails-UI-Performance component.
History
- December 22, 2009
- July 28, 2009
- July 23, 2009
- May 20, 2009
- March 13, 2009
- December 19, 2008
- December 10, 2008
- released version 0.2 in the Grails repository
- August 26, 2008
- released initial version 0.1