the new static resources framework
DESCRIPTION
My talk on the new Resources plugin for Grails, at Groovy Grails Exchange 2010TRANSCRIPT
The new static resources framework
About me
Marc Palmer
Paid to develop WeceemFounder at NoticeLocal and Spotty Mushroom
Old skool Grails user since 0.4
Plugin writing maniac
Resource management
Web performance
Eternal caching
Minifying
Zipping
Bundling
Load order
Old Problems
Ant
Java
Boilerplate code
Spring MVC config
Code verbosity
New Problems
Download performance
Complex UI from plugins
Blueprint jQuery jQuery UI
Your application
CSS JS JS +CSS
Admin UISecurity UI
Scaffolding
Custom CSS
Custom JS
Optimization hell
Who includes resources & when?
Is the resource already optimized?
Load order
Performance: I want control
Plugin version conventions help:
grails-blueprint-0.9grails-jquery-1.4.3grails-jquery-ui-1.8.2
Resources plugin
Resource declaration DSL
Dependency resolution
Resource tags
Mapping pipeline
Modular and extensible
Processed at runtime
Runtime?!
Runtime?!
Only at startup!
Works in any environment
Can be bypassed
Supports reloading
Tiny trade-off
Installing itmarcmacbook:AwesomeApp marc$ grails install-plugin resources Welcome to Grails 1.3.1 - http://grails.org/Licensed under Apache Standard License 2.0Grails home is set to: /usr/local/grails-1.3.1
Base Directory: /Users/marc/Projects/AwesomeAppResolving dependencies...Dependencies resolved in 1172ms.Running script /usr/local/grails-1.3.1/scripts/InstallPlugin.groovyEnvironment set to developmentInstalling zip ../checkout/Resources/grails-resources-1.0-RC1.zip... ... [mkdir] Created dir: /Users/marc/.grails/1.3.1/projects/AwesomeApp/plugins/resources-1.0-RC1 [unzip] Expanding: /Users/marc/Projects/checkout/Resources/grails-resources-1.0-RC1.zip into /Users/marc/.grails/1.3.1/projects/AwesomeApp/plugins/resources-1.0-RC1Installed plugin resources-1.0-RC1 to location /Users/marc/.grails/1.3.1/projects/AwesomeApp/plugins/resources-1.0-RC1. ...Resolving plugin JAR dependencies ...Executing resources-1.0-RC1 plugin post-install script ...Plugin resources-1.0-RC1 installedmarcmacbook:AwesomeApp marc$
So far so...?Before After
Resource DSL
modules = { grailsDefaults { resource url:[dir:'css', file:'main.css'] resource url:[dir:'js', file:'application.js'], disposition:'head' resource url:[dir:'js/prototype', file:'prototype.js'] } myStuff { dependsOn 'grailsDefaults' resource url:[dir:'css', file:'branding.css'] resource url:[dir:'js', file:'ui-logic.js'] }}
File: grails-app/config/AwesomeResources.groovy
Changes to GSP
<html> <head> <title>Welcome to Grails</title> <meta name="layout" content="main" /> <r:use modules="myStuff"/> </head> <body><!-- ... --> </body></html>
File: grails-app/views/index.gsp
What does this page do?
What does it need to do it?
Explicit resource tags are wrong
Changes to layout<html> <head> <title><g:layoutTitle default="Grails" /></title> <r:layoutResources /> <link rel="shortcut icon" href="${r.resource(dir:'images',file:'favicon.ico')}" type="image/x-icon" /> <g:layoutHead /> </head> <body> <div id="spinner" class="spinner" style="display:none;"> <img src="${r.resource(dir:'images',file:'spinner.gif')}" alt="Spinner" /> </div> <div id="grailsLogo" class="logo"><a href="http://grails.org">
<img src="${r.resource(dir:'images',file:'grails_logo.png')}" alt="Grails" border="0" /></a></div>
<g:layoutBody />
<r:layoutResources />
<script type="text/javascript"> oneLineAmazingUI(); </script> </body></html>
File: grails-app/views/layouts/main.gsp
Link disposition
<head> <r:layoutResources/> <!-- head --></head><body> ... <r:layoutResources/> <!-- defer --></body>
The basic tags
<script src="..."> <script src="..."> <link rel="stylesheet" ... /><link rel="stylesheet" ... />
<r:use modules="myStuff"/>
<g:resource .../> <r:resource .../>
<img .../> <r:img .../>
Disposition in DSL
modules = { grailsDefaults { resource url:[dir:'css', file:'main.css'] resource url:[dir:'js', file:'application.js'], disposition:'head' resource url:[dir:'js/prototype', file:'prototype.js'] } myStuff { dependsOn 'grailsDefaults' resource url:[dir:'css', file:'branding.css'] resource url:[dir:'js', file:'ui-logic.js'] }}
File: grails-app/config/AwesomeResources.groovy
Better...Before After
Bundlingmodules = { grailsDefaults { resource url:[dir:'css', file:'main.css'] resource url:[dir:'js', file:'application.js'], disposition:'head' resource url:[dir:'js/prototype', file:'prototype.js'], bundle: 'core' } myStuff { dependsOn 'grailsDefaults' defaultBundle 'core' resource url:[dir:'css', file:'branding.css'] resource url:[dir:'js', file:'ui-logic.js'] }}
Cross-module bundle!
Dependency overrides
Text
modules = { grailsDefaults { defaultBundle 'core' resource url:[dir:'css', file:'main.css'] } myStuff { dependsOn 'grailsDefaults, blueprint' defaultBundle 'core' resource url:[dir:'css', file:'branding.css'] resource url:[dir:'js', file:'ui-logic.js'] } overrides { jquery { defaultBundle 'core' } 'jquery-ui' { resource id:'js', bundle: 'core' resource id:'theme', bundle: 'core' } 'blueprint' { resource id:'main', bundle: 'core' } }}
Deferred inline JS
<r:script>
initUIThatDoesNotSuck();</r:script>
We love the debugDevelopment reloads
Turn it all off: add ?_debugResources=y
Cache defeat: add ?_refreshResources=y
X-Grails-Resources-Original-Src:/bundle-grailsDefaults.js, /js/application.js, /js/prototype/prototype.js
What just happened?
Declarative resources
Single tag mechanism
Optimal, smart ordering
Resource de-deduping
Bundling
Now the fun part
Mapping pipelineCopy to work dir
Apply mappers
Modify resource
Add response handler
Update URI
Mapper Artefacts
Text
class TestResourceMapper {
def priority = Integer.MAX_VALUE def map(resource, config) { def file = new File(resource.processedFile.parentFile,
"_${resource.processedFile.name}") assert resource.processedFile.renameTo(file) resource.processedFile = file resource.updateActualUrlFromProcessedFile() }}
Bundling Mapper
/js/jquery-ui/jquery-ui.css
/css/blueprint/screen.css
/bundle_main.css
/css/main.css
CSS rewriting
Both CSS and image may be renamedand/or moved
/css/main.css:
body { background-image: url(../images/bg.png);}
/bundle_main.css:
body { background-image: url(../changed.png);}
Before
After
Resources Others
Zipped Resources
Compresses files to xxx.css.gz
Keeps URI the same
Sets Content-Encoding: gzip
grails install-plugin zipped-resources
Cached Resources
Renames to SHA256 digest of contents
Shortens name to base62 encoding
Flattens directory structure
Sets Expires to 1 year
grails install-plugin cached-resources
/js/some-bloated-lib/plugin/foo.js
/b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9.js
/UpLdvv429fMac6FhEx5FMU2F3Cg1yj4HwUGmihFvjYR.js
In production already
Config conventions
grails.resources.<mapper>.excludes = [...]
grails.resources.modules = { ... }
grails.resources.debug = true
grails.resources.work.dir = ...
grails.resources.uri.prefix = 'static'
grails.resources.adhoc.patterns = ...
Integration into coreDSL
Dependency resolutionTags
Mapping pipelineBundling
CSS Rewriting
Mapper pluginse.g. zipped-resources
Grails Core
grails-resources 2.0
grails-xxxx-resources
Future
Minify
CSS Sprites
Smart image links with auto w & h
E.S.P. - externalising inline JS
Flavours (content variants)
CDN up loaders
Special thanks...
Peter Ledbrook
Luke Daley
Stéphane Maldini
Robert Fletcher
Burt Beckwith
Q & A and linksSample app:http://bit.ly/awesomeapp1http://bit.ly/awesomeapp2
http://grails.org/plugin/resourceshttp://grails.org/plugin/zipped-resourceshttp://grails.org/plugin/cached-resourceshttp://noticelocal.comhttp://www.experienceoz.com.auhttp://www.icescrum.org
Also check out some unsung plugins:taxonomy, invitation-only, cache-headers,one-time-data, email-confirmation