Skip to content

Instantly share code, notes, and snippets.

@zedar
Last active August 29, 2015 13:57
Show Gist options
  • Save zedar/9518241 to your computer and use it in GitHub Desktop.
Save zedar/9518241 to your computer and use it in GitHub Desktop.
Grails resources plugin - add custom resource mapper to map urls from css files that contain ? or # attributes.

If you use resources plugin and in css files you do have uri-s with attributes (after the ?) or with # the output uri-s will not be calculated correctly. This is because resource plugin checks if file with the given uri exists in file system without extracting clean uri (without attributes).

For example you can have following declaration in your css file (part of font-awesome):

@font-face {
  font-family: 'FontAwesome';
  src:  url('../fonts/fontawesome-webfont.eot'); /* IE9 */
  src:  url('../fonts/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
        url('../fonts/fontawesome-webfont.woff') format('woff'), /* Modern browser */
        url('../fonts/fontawesome-webfont.ttf') format('truetype'), /* Safari, Android, iOS */
        url('../fonts/fontawesome-webfont.svg?#fontawesomeregular') format('svg'); /* Legacy iOS */
  font-weight: normal;
  font-style: normal;
}

The solution:

  1. In your grails-app add resourceMappers folder
  2. Add new class CSSFontsRewriterResourceMapper.groovy to resourceMappers folder
  3. Rebuild your project
import org.grails.plugin.resource.CSSLinkProcessor
import org.grails.plugin.resource.mapper.MapperPhase
/**
* This mapper is the second phase of CSS rewriting but for fonts references.
* Some fonts, like awesome, require attributes after the '?'.
*
* It finds any "resource:" URIs and then re-relativizes the absolute URI that follows it, using the final
* locations of all the resources now that all the other CSS-processing mappers have been applied.
*
* @see CSSPreprocessor mapper for phase 1 of the process.
*
*/
class CSSFontsRewriterResourceMapper {
// default mappers does not have priority attribute declared, what means they have value 0
// set priority to value > 0
def priority = 1000
def phase = MapperPhase.LINKREALISATION
def grailsResourceProcessor
static defaultIncludes = ['**/*.css']
/**
* Find all url() and fix up the url if it is not absolute
* NOTE: This needs to run after any plugins that move resources around, but before any that obliterate
* the content i.e. before minify or gzip
*/
def map(resource, config) {
def resURI = resource.sourceUrl
def processor = new CSSLinkProcessor()
processor.process(resource, grailsResourceProcessor) { prefix, originalUrl, suffix ->
if (originalUrl.startsWith('resource:')) {
def uri = originalUrl - 'resource:'
if (log.debugEnabled) {
log.debug "Calculated URI of CSS resource [$originalUrl] as [$uri]"
}
// extract uri without query
def qidx = uri.indexOf('?')
def hidx = uri.indexOf('#')
def chopIdx = -1
// if there's hash we chop there, it comes before query
if (hidx >= 0 && qidx < 0) {
chopIdx = hidx
}
// if query params, we chop there even if have hash. Hash is after query params
if (qidx >= 0) {
chopIdx = qidx
}
def sourceUri = chopIdx >= 0 ? uri[0..chopIdx-1] : uri
// Strictly speaking this is query params plus fragment ...
def sourceUrlParamsAndFragment = chopIdx >= 0 ? uri[chopIdx..-1] : null
// This triggers the processing chain if necessary for any resource referenced by the CSS
def linkedToResource = grailsResourceProcessor.getResourceMetaForURI(sourceUri, true, resURI) { res ->
// If there's no decl for the resource, create it with image disposition
// otherwise we may pop out as a favicon...
res.disposition = 'cssresource'
// recalculate uri with extracted attributes
res.setSourceUrl(uri)
}
if (linkedToResource) {
if (log.debugEnabled) {
log.debug "Calculating URL of ${linkedToResource?.dump()} relative to ${resource.dump()}"
}
def fixedUrl = linkedToResource.relativeToWithQueryParams(resource)
def replacement = "${prefix}${fixedUrl}${suffix}"
if (log.debugEnabled) {
log.debug "Rewriting CSS URL '${originalUrl}' to '$replacement'"
}
return replacement
} else {
log.warn "Cannot resolve CSS resource, leaving link as is: ${originalUrl}"
}
}
return "${prefix}${originalUrl}${suffix}"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment