An idea to give Grails tags “E.S.P.”

Posted by: on Jan 19, 2009 | 5 Comments

There exists a problem with all current JS UI plugins in that they produce HTML content that is peppered with inline Javascript and/or CSS. This is widely acknowledged as bad practice and greatly increases page load times.

This can be reduced to a problem of the inability for custom tags to define the contents of JS and CSS used by the current GSP page. This can be generalised to and inability for a tag invocation in a page to contribute to the production of any related content.

There is also a separate problem of taglibs invoked within a page body being unable to influence the content of the <head> section of an HTML response. This problem could be solved by introducing a two-phase GSP page execution, but most likely this should be handled by external resources, eg .js and .css files added to te <head> section via <link> tags.

The solution is quite simple, but with some subtleties to make it truly rock.

In a nutshell, we use some generic taglib functionality to gather information about what JS and CSS (or other resource) information is to be gathered and served for the current page, and have a controller with a cache to serve it up.

What we’re doing here is exploiting the fact that the possible race condition of CSS+JS being loaded before the HTML is full loaded by the browser, is not going to happen to pages laid out by SiteMesh, and even for other pages will only be a problem for the first request (see workarounds for this later).

Introducing the concept of “The E.S.P. Plugin”

This plugin would provide a taglib and a controller, and either another tag, or a service (See below).

The taglib would offer:

<esp:resource group="somepage" type="css"/>

This tag is used in the <head> section of layouts or pages, to indicate that you want to pull in a resource. The group attribute is optional. If there is none the group will default to “${controllerName}.${actionName}”. This is how you control where resources are served up. You can have a resource per page, or you can be smarter and have them grouped in any combinations you like, eg a single CSS file but separate JS files. Some pages may even have more than one such inclusion:

<html>
<head>
  <esp:resource group="common" type="css"/>
  <esp:resource group="common" type="js"/>
  <esp:resource group="contactForm" type="js"/>
</head>
...
</html>

This tag simple outputs a <link> tag with the mimetype + relation set according to the “type” value, and a link to the ESPController action with params “group” and “type” and “template” – if there was a “template” attribute specified. There could be a URL mapping to prettify this to something like:

/esp/contactForm/js

Then we have another tag or service to store strings against the group and type, and to detect if such data has already been registered (to prevent tag slow-down for every request).

For example as a tag called as a method from UI taglibs:

if (!esp.has('contactForm', 'js',
       "grailsui.richtext.${attrs.id}")) {
    esp.store('contactForm', 'js', 
       "grailsui.richtext.${attrs.id}") << "some text"
}

Whether this is a tag or service makes little difference other than convenience. The data would ultimately need to be stored in a cache in a service – the value returned from the store is a StringWriter that can be put in a local var and reused across multiple “<<” invocations.

The page-unique id is required to prevent the same content being cached more than once for that group.

Order of data stored must be preserved so they are written out in the same order.

Tags will use this form for writing out their JS or CSS, while they simultaneously – in most cases – also write out some vanilla and entirely unobtrusive HTML.

NOTE: There should be a form of store() that does not take the “group” parameter, only type and id. This would default to the last ‘group’ value used in the esp:resource tags in <head> (which would be stored as a request attribute).

Next, the ESPController:

This controller would work by retrieving from the internal datastructure all the strings registered for the specified group and resource type, and then:

1) if there is no “template” attribute, simply render them out with a default content type based on the “type” stored

2) if there is a “template” attribute, it will render the GSP template and put all the strings into a list in the model that can be iterated in the GSP – so that you can have a base template and do any decorating you want

3) Set appropriate caching headers (probably subject to Config.groovy settings). Although this is not intended to be a substitute for high performance front end caching of the resources.

4) If there is a request for a group and resource type combination for which there is currently no data, we presume this is due to a request race condition or a first request with an eager browser so it either:

i) returns a “Server busy” or similar status code

or

ii) uses a filter to know when such a request has first been served (checking request attribute for groups and types used in the page), and have the /esp/ request for same groups+type “sleep” until the first request is complete.

or

iii) use a <esp:done/> tag used at the end of the GSP/layout such that this sets a flag in the global data indicating that requests can now be served for the groups+types used in the page.

Hopefully this makes sense. In essence it is quite simple.

It also gives us a new feature – the ability to define “static” data used across different GSP pages and resources, and text fragments for reuse without <g:render>. All it needs is an extra <esp:get> tag to get the content.

I have to say that I prefer option (i). Frankly I couldn’t give a damn about request zero :)

5 Comments

  1. Matthew Taylor
    January 19, 2009

    This is a brilliant idea, but it doesn’t make you any less of an asshole Marc! ;) (I’m sorry, I’m just jealous that I didn’t come up with this idea first.)

    But seriously, this could be an excellent thing for all UI plugins, to help clean up the markup they produce and make debugging easier.

  2. Marc Palmer
    January 20, 2009

    LOL

  3. Andreas Arledal
    January 20, 2009

    This is very clever Marc. With this, maybe even I could start using UI plugins. And of course, the behaviour should be the same for remoteLink and others.

  4. Veena Kaul
    October 12, 2010

    Marc, do you have the sample code for this, which I can readily use it ?

  5. Migrating from the Grails UI-performance plugin to resources plugin. | Tomás Lin’s Programming Brain Dump
    October 21, 2011

    [...] 4. [Optional] Add tags to your layouts to include the tags used by resources plugin to provide ‘ESP‘ [...]