Resolving browser-specific issues using the Grails Resources plugin

While upgrading an application to Grails 2 recently, we encountered a couple browser-specific issues (well, more than a couple) that I thought were pretty interesting. These two are relatively well-known in general but I thought the solutions particular to the Resources plugin would be worth documenting, as it may take a little bit of time to debug and resolve.

Problem 1: Internet Explorer version 9 and under stop rendering CSS after a certain point.

Internet Explorer version 9 and under has a limit on both the number of CSS files a page can use (maximum of 31), as well as the number of rules (4095) any one CSS file can have. The plugin helps alleviate the max-files per page limitation, but may hit the max-rules cap in older IE versions. Note that this is not a file size restriction, so simply having a large/uncompressed file is not the issue.

The problem arises when a resource module contains CSS files with a lot of rules (such as those for a framework), as by default the CSS resources would get placed into the same bundle, and rendered as a single file.

So for example, a bundle comprised of bootstrap.css, flat-ui.css, and some custom files may initially look like:

core {
    resource url: '/css/bootstrap.css'
    resource url: '/css/flat-ui.css'

    resource url: '/css/custom/core.css'
    resource url: '/css/custom/core2.css'
}

If the combined number of rules in those 4 files is greater than 4095, IE9 and under will simply stop rendering the rules after the limit, resulting in some not-so-pretty pages. The obvious fix is to try to split up the bundle into 2 or more pieces, although if the files are all related, this may slightly ruin the “bundle” abstraction:

Solution 1

framework {
    resource url: '/css/bootstrap.css'
    resource url: '/css/flat-ui.css'
}

core {
    dependsOn 'framework'

    resource url: '/css/custom/core.css'
    resource url: '/css/custom/core2.css'
}

The ‘dependsOn’ keyword ensures that bundles and pages that rely on ‘core’ will also load the bootstrap module, but (as it currently stands) the 2 bundles will be loaded in separate CSS files (namely, bundle-bundle_framework_head.css and bundle-bundle_core_head.css), which should resolve the problem.

However, say core.css and core2.css each contain a large number of rules already, but we don’t want to break them out into yet more separate modules. In this case we can use the ‘exclude‘ keyword to skip bundling for either (or both) files:

Solution 2

framework {
    resource url: '/css/bootstrap.css'
    resource url: '/css/flat-ui.css'
}

core {
    dependsOn 'framework'

    resource url: '/css/custom/core.css', exclude: 'bundle'
    resource url: '/css/custom/core2.css'
}

This will have the same effect as above, splitting up the module’s CSS files (so that we’d get bundle_framework_head.css, core.css, and bundle-bundle_core_head.css), while keeping them together in the config. The drawback with these solutions is that they affects all browsers, so if you really wanted to have as few resources calls as possible, you may need an alternate fix. However, note that using the Resource plugin’s built-in wrappers also disables bundling of the module.

Problem 2: Chrome displays a 404 “Not Found” error on a .min.map file when it is not provided for a JS file that specifies it.

Starting with version 1.9, jQuery started adding source map URL’s to its minified files in the form of

//@ sourceMappingURL=jquery-1.10.2.min.map

This allows browser dev tools to debug errors back to the source file, even when then the minified version of a Javascript file is used. While Firefox silently fails if the source isn’t found, Chrome will throw a 404 “Not Found” error for the source/uncompressed file assuming:

1. The console is open.
2. The “Enable Source Maps” option is checked (enabled by default).

Although the first condition ensures that your users in prod will not run into the issue, it’s useful to have access to the feature (especially if debugging in a non-dev/QA environment). Typically the fix would involve including the source and map in the directory, or to simply remove the sourceMappingUrl line, BUT… if you’re using the Grails jQuery plugin, neither of those is really an option without re-packaging the plugin. Chrome looks for the source map file in

<app>/static/plugins/jquery-<version>/js/jquery/jquery-<version>.min.map

since that is where the minified jQuery file comes from by default. Therefore we can’t edit the file, nor add the source map to it if we want to stick with the originaljQuery plugin’s code.

Luckily we can override the jquery bundle (using the ‘overrides’ DSL keyword) and add a ‘linkOverride‘ option to “[change the] URL to use when rendering links for the resource, instead of the processed resource’s URL.” In other words, we can “trick” Chrome into thinking it fetched the minified jQuery file from a different location:

def jqver = org.codehaus.groovy.grails.plugins.jquery.JQueryConfig.SHIPPED_VERSION

overrides {
    jquery {
        resource id: 'js', linkOverride: "/js/jquery/jquery-${jqver}.min.js"
    }
}

We just need to be sure that the directory we specify also contains the map file (although it does not need anything else), so the physical structure would look like:

-- jquery-<version> (the Grails jQuery plugin)
  -- web-app
    -- js
      -- jquery
        -- jquery-<version>.js
        -- jquery-<version>.min.js

-- <your app>
  -- web-app 
    -- js 
      -- jquery
        -- jquery-<version>.min.map

We can verify that both the minified and non-minified versions of the file are available by going into the “Source” tab in the Chrome debugger, whereas only the minified version was visible before (and from a different location). Fortunately it looks like the the next version of the jQuery plugin will resolve this issue, but in the meantime, we can implement this workaround to resolve the 404.

As I mentioned, neither of these are really “new” problems, but I learned quite a bit about the Resources plugin in the process, so hopefully this can help people in similar situations.

One thought on “Resolving browser-specific issues using the Grails Resources plugin

  1. Bobby Warner says:

    The jQuery 1.11 release is taking longer than I thought it would. So, I released a point release of the plugin with the map file in version 1.10.2.1.

  2. Igor Shults says:

    Oh awesome, even better. Thanks Bobby!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*