The new static resources framework

Post on 17-May-2015

3.896 views 0 download

Tags:

description

My talk on the new Resources plugin for Grails, at Groovy Grails Exchange 2010

Transcript of The new static resources framework

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