Proposal: Resource handling improvements

pledbrook edited this page Sep 13, 2011 · 8 revisions

Proposal for resource mechanism changes in Grails

Note that this has now become the Resources plugin

As the plugin ecosystem is maturing, dependencies on external resources (JS, CSS, images etc) are becoming more of an issue.

Also issues surrounding how to effectively control minifying/compression/caching of resources provided by plugins are becoming more complex.

Application response time is also an issue as higher level tags write more and more code inside the HTML response.

We may be able to make Grails remove a lot of this pain.

Basic proposals:

  • GSP Pages should not care where a resource comes from, only what resource modules they need e.g. jQuery
  • Grails to provide new/upgraded g:resource tags that create “resource dependencies” that are resolved smartly by grails
  • Grails to allow apps and plugins to declare “resource dependencies” that they provide
  • Grails to provide new tag to smartly rendering link/script tags as appropriate in sections, without duplication if layout GSP and a view GSP both request same resource
  • Grails to provide tags that allow developers to inject JS code “inline” in the GSP rendering process, but for this to be served to the client in a separate JS file (which is therefore cacheable). AKA ‘E.S.P.’
  • (Maybe) Grails to provide some way for application developers to define how resource dependencies should be bundled/minified/compressed. This would probably remain in the domain of a plugin like ui-performance however.
  • Provide a uniform way to include resources (as minified or not) during development. Currently all plugins implement this in their own ways, and you cannot turn it on and off with one var in Config.

Some specific current issues:

1. Many plugins have added different tags for rendering and similar, due to this missing in Grails core. See GRAILS-4499

2. Plugins like ui-performance require a user to change the tags they use for all image, css, js links. This is onerous and plugin-specific.

3. Plugins that want to take advantage of something like ui-performance have to be explicitly coded to do so, even when they are just pulling in resources. However requiring the user to use ui-performance just to use another dependency e.g. jQuery plugin, is not attractive.

I don’t understand this point. Could you clarify? Peter

4. Transitive dependencies on resources e.g. jQuery 1.4 can partly be tackled by using the “plugin for JS libs with same version” pattern that is starting to be used. However this does not solve the problem fully as it relies on all plugins in an app to be written (updated) to work this way. Some will always lag and the result is duplicate resources and slow page loads / piecemeal apps.

5. There is no way to get a report of resource dependencies that your app has. e.g. what is the list of CSS, JS and image files required (across Grails plugins)?

Suggestions for solution – new resource tag with dependency info

If we added a new g:resourceLink tag, it can be used by both apps and plugins to declare a resource the page depends on:

<g:resourceLink lib="jquery" ver="1.4"/>

Since this tag could potentially create multiple <link> and <script> tags, how about calling it <resourceBundle>? Peter

This would stick in the required script tag, as well as recording in the request scope that this library has been included. The URL would be composed by referring to a list of resource dependencies provided by plugins/the app (in a DSL or doWithXXXXX)

This can also work together with resource grouping information, so that developers can modularize their own JS, but have it minimized and combined into a single file for deployment i.e.:

<g:resourceLink module="editor"/>
<g:resourceLink module="usermanager"/>

These would be separate JS files in the app source, and produce two script tags in development, but it would then be possible to allow the app dev to specify that these should be bundled into a single minified file for production.

The DSLs/mechanisms for specifying resource groupings etc are not important at this point. What matters is that:

- Grails could fail-fast at runtime if a non-provided resource is requested

- Grails can prevent plugins, GSP fragments etc from re-including resources that are already present (ugly, and still requires cache hit in browser). This is also part of multi-resource-in-one-file bundling mechanisms (e.g. the way ui-performance bundles files up).

- You can get reports (because of the DSL) of what resources are provided in your app, by the various plugins (important for being able to bundle them up / decide on minifying etc)

By having a uniform interface for declaring resource dependencies in a page, plugins like ui-performance would be able (with hooks) to modify the returned URLs.

Suggestion for pushing page-specific JS code out into cacheable JS files – E.S.P.

Very often through the use of Grails tags and smart UI plugins, a lot of inline JS ends up in the content. Even if you have ui-performance style smarts to push it to the end of the page, it still adds bloat to content – much of this is usually not user-data specific (or shouldn’t be if well coded!) so it is a prime candidate for caching.

To achieve this, a tag is needed to “capture” the code with some kind of grouping identifier (defaulting to the current page/request URL). This code would not be written to the output. Instead, the developer would have to add a single tag to the

<g:magicResources/>   // Made up name!

This tag would write up to one script tag, calling to an internal servlet/controller that returns a single file containing the concatenated output of all the invocations of the “capture” tag – optionally minifying, zipping and setting infinite caching on it (ui-performance style).

The benefits for page load time can be pretty big on complex UIs, and all the is needed is that tag writers amend their code to wrap their code output in this capture tag.

More (but old) thoughts on this are in this blog post

Another example here is that jQuery plugin’s tag could be modified to use this capturing, thus trivially pushing out your inline jQuery code to files.

This mechanism can then be tied into the resource dependency mechanism, so that using g:magicResources creates an implicit (or explicitly named) resource dependency, so that this cacheable JS output can again be bundled into other files.

You could even make g:javascript for inline javascript code automatically use this mechanism, so you can still write your JS inline (if it is page-specific) and it still ends up cacheable and in a separate file.

Finally… and this is a bit off-piste really, this would mean a lot of ui-performance’s magic can be simplified and if you imagine ui-performance upgraded to use SHA digests for file names instead of version numbers, you suddenly get even better results where redeploying your app does not force all clients to load all new content – only those that actually changed.