(Quick Reference)

3 Declaring resources - Reference Documentation

Authors: Marc Palmer (marc@grailsrocks.com), Luke Daley (ld@ldaley.com), Peter N. Steinmetz (ndoc3@steinmetz.org)

Version: 1.2.14

3 Declaring resources

You can declare your resources in your Application's Config.groovy or in a Resources artefact in your application or plugins.

This is done using a simple DSL in both cases. You define the names of the modules you have and the resources within them, and dependencies between modules.

3.1 The resource DSL

There are few methods to call in the DSL. At the top level method calls taking a single Closure argument are translated into module names. The code in the nested closure represents the module definition.

A module definition can call dependsOn, defaultBundle and resource methods:

modules = {
    core {
        dependsOn 'jquery, utils'
        defaultBundle 'ui'

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' defaultBundle 'ui'

resource url:'/css/forms.css' resource url:'/js/forms.js' } }

The above DSL defines three resource modules; 'core', 'utils' and 'forms'. The resources in these modules will be automatically bundled out of the box according to the module name, resulting in fewer files. You can override this with bundle:'someOtherName' on each resource, or call defaultBundle on the module

3.1.1 The dependsOn method

To declare that a module depends on another, you use dependsOn. It accepts a string or list of names. The string can be comma-delimited to specify multiple dependencies. You can also call dependsOn as many times as you like:

modules = {
    core {
        dependsOn 'jquery, utils'
        dependsOn 'other'
        dependsOn(['heavy', 'metal'])
    }
}

3.1.2 The resource method

Declaring a resource is done using the "resource" method. This takes either a single string URI or a map of arguments that define the URL of the resource and any other specific options you require.

The single-string argument variant is a simple shortcut for resources that are safe to use all defaults:

modules = {
    ui {
        resource '/css/forms.css'
        resource '/js/forms.js'
    }
}

The more powerful map argument form accepts the following arguments:

  • url - Required. The app-relative URL of the resource as a String, or a g:resource-style map of dir, file and plugin attributes.
  • exclude - Optional. A comma-delimited String or a List of names of mappers/operations to exclude. The special "*" value excludes all mappers and operations - the resource will be passed-through as is. You can exclude specific operations such as "minify" if the resource has already been minified.
  • bundle - Optional. The name of the bundle to put the resource into. See "Bundling" for more details
  • disposition - Optional. The disposition of the resource. If not specified, it will default to a value appropriate for the type of the resource. For JavaScript this default is "defer". To force code into the <head>, set it to "head".
  • attrs - Optional. Map of attribute names and values to pass through to the linking tag when the resource is rendered. e.g. use to pass attrs:media:'print' for print-only CSS, or "type". Note that supplying "attrs" will prevent bundling of the resource. Passing in "type" (which is passed through to external tag) allows you to tell the framework that the resource is a specific type irrespective of the file extension. For example if you have a file that ends ".less" but you want the framework to treat it like CSS when rendering links, you need to set attrs:[type:"css"].
  • id - Optional. Module-unique id of the resource to be used when overriding resource properties. Defaults to the url of the resource if none supplied.
  • linkOverride - Optional. The URL to use when rendering links for the resource, instead of the processed resource's URL. Allows your external resources to participate in dependency management, and some niche CDN requirements.
  • wrapper - Optional. A Closure that will be used to render the resource link. The correct markup for linking will be passed in. Used primarily for MSIE workarounds i.e: wrapper: { s -> "<!--if lt IE 8>$s<!endif-->" }. Note that supplying a wrapper will prevent bundling of that resource.
  • plugin - Optional. If applicable, the name of the plugin a resource is being defined in.

Examples:

modules = {
    core {
        resource url:[dir:'css/blueprint',file:'screen.css'], attrs:[media:'screen, projection'], bundle:'core-ui'
        resource url:[dir:'css/blueprint',file:'ie.css'], attrs:[media:'screen, projection'],  
            wrapper: { s -> "<!--[if lt IE 8]>$s<![endif]-->" }

resource id:'main-js', url:'js/coreutils-min.js', disposition: 'head', exclude:'minify'

resource url:'js/lib.js', linkOverride:'http://mycdn.com/js/lib.js' }

ui { resource url:'/css/forms.css', bundle:'core-ui' resource url:'/js/forms.js' } }

modules = {
    'font-awesome' {
        resource url: [plugin: 'font-awesome-resources', dir: 'css', file: 'font-awesome.css']
    }
}

3.1.3 The defaultBundle method

By default, modules with more than one resource will be auto-bundled using the module name as a bundle name. You can override this by setting the default bundle name for the module by calling defaultBundle. This name can be the same as bundles used in other modules, to bundle resoures across module boundaries.

The method takes a String or boolean as parameter. A string sets the default bundle name, and passing in a boolean false will turn off all default bundling for that module.

modules = {
    core {
        defaultBundle 'core-ui'

resource url:[dir:'css/blueprint',file:'screen.css'], attrs:[media:'screen, projection'] resource url:[dir:'css/blueprint',file:'ie.css'], attrs:[media:'screen, projection'], wrapper: { s -> "<!--[if lt IE 8]>$s<![endif]-->" }

resource id:'main-js', url:'js/coreutils-min.js', disposition: 'head', exclude:'minify'

resource url:'js/lib.js', linkOverride:'http://mycdn.com/js/lib.js' }

ui { defaultBundle 'core-ui'

resource url:'/css/forms.css', bundle:'theme' resource url:'/js/forms.js' } }

Here you can see that the fallback bundle for both modules is set to "core-ui" and individual resources can still override this.

3.2 Resource artefacts

The best place to put these declarations is in a resource artefact. These have a filename ending in Resources.groovy and live in grails-app/conf.

Note that these are Groovy ConfigSlurper scripts that are therefore environment-aware, so you can declare different resources for e.g. dev, test and production.

Example grails-app/conf/MyAppResources.groovy:

modules = {
    core {
        dependsOn 'jquery, utils'
        defaultBundle 'ui'

resource url:'/js/core.js', disposition: 'head' resource url:'/js/ui.js' }

utils { dependsOn 'jquery'

resource url:'/js/utils.js' }

forms { dependsOn 'core,utils' defaultBundle 'ui'

resource url:'/css/forms.css' resource url:'/js/forms.js' } }

3.3 Config.groovy

Applications can also define resources in Config.groovy if they wish. Simply assign the DSL to the grails.resources.modules property.

Example in grails-app/conf/Config.groovy:

grails.resources.modules = {
    core {
        dependsOn 'jquery, utils'
        defaultBundle 'ui'

resource url:'/js/core.js', disposition: 'head' resource url:'/js/ui.js' }

utils { dependsOn 'jquery'

resource url:'/js/utils.js' }

forms { dependsOn 'core,utils' defaultBundle 'ui'

resource url:'/css/forms.css' resource url:'/js/forms.js' } }

3.4 Bundling

Bundling files is the process of concatenating multiple files of the same type into one, so that there are fewer requests made to the server. This can significantly improve page load times even when using ETag or Last-Modified caching schemes.

In a typical application you might bundle most or all of your CSS into one file, and all your common JS into one file, with per-page JS files for page-specific features.

This works very well in combination with the cached-resources plugin an mapper which hashes your files. It means that your clients will cache the (larger) bundled files "forever" but you needn't worry if your next deployment changes one file within a bundle - the bundle will be rebuilt with a new hash and downloaded to the client, as the URL for the resource will have changed.

There is of course a trade-off here, in that a change to one JS file in a bundle will trigger a download of the entire JS bundle for all clients.

It's important to understand that bundling is automatically performed per-module, but you can set it to bundle resources across modules - even those you have not defined yourself. For example using the jquery-ui plugin, you can force it to bundle all the jQuery and jQuery UI JS code into one file, mixed in along with your own code - all in the correct dependency ordering.

The framework will never bundle files that have different dispositions together - this makes no sense. Bundles are therefore automatically named to include the disposition (you may not see this if another mapper such as cached-resources is renaming the files).

Bundling can be controlled in a number of ways. The logic runs as follows:

  1. If a resource declaration has an explicit bundle property set, this is used.
  2. If there is no explicit bundle property set on the resource, it will use the default for the module
  3. The default for the module is "bundle_" plus the name of the module unless defaultBundle has been called
  4. If defaultBundle has been called in the module with a string, that name is used for the resource's bundle
  5. If defaultBundle has been called in the module with a boolean false, no bundling will occur on the resource, only resources with a bundle specified will be bundled.

NOTE If a resource declares "attrs" or "wrapper" it will not be bundled at all. This is because bundling does not make sense in this situation unless all the attrs and wrapper logic match.