Kevin M. Gill在他的博文Preprocessing CSS in Grails以及Improve Grails Performance With Static Resources中,跟我们分享了在Grails应用的CSS中如何简化对静态资源管理的方法。
Web开发时,总会需要在CSS中修改静态资源的路径,比如图片。如果资源数量巨大,就需要大量的重复的替换工作,这样还容易出现错误。这时,想到Groovy中的${},如果CSS中能使用${},将图片的存放位置放在配置文件或者数据库中进行统一管理。一旦遇到图片存放路径发生改变的情况,只需要修改几处或者仅仅一处的位置信息就可以了,岂不是很方便?
在使用CSS时,如果设定背景图之类的内容,通常是如下写法:
.some_div { ... background: url('http://localhost:8080/GrailsUI/static/img/grails_logo.png'); ... }
或者将图片的位置写成相对位置,比如:
.some_div { ... background: url('../static/img/grails_logo.png'); ... }
下面来看看具体实现步骤!
- 先将原先的CSS文件进行改造,见如下代码:
.some_div { ... background: url(${groovyq.staticResource(dir:"/img/", file:"grails_logo.png")}); ... }
- 在%GrailsApp%/grails-app/taglib目录下创建一个TagLib,来设定groovyq.staticResource要执行的内容,见如下代码:
class StaticResourcesTagLib { static namespace = "groovyq" def grailsApplication def staticResource = { attrs, body -> def dir = (attrs.dir) ? attrs.dir : "" def file = (attrs.file) ? attrs.file : "" def url="${grailsApplication.config.myapp.staticresources.url}" + "/${dir}/${file}" out << body() << url } }
- 在%GrailsApp%/grails-app/config.groovy文件中添加:myapp.staticresources.url = "http://localhost:8080/GrailsUI/static",用来指定静态资源的路径。
- 添加一个名为StaticResource的Controller,用来装入CSS和处理CSS文件中的${}中的内容。StaticResourceController的全部代码如下:
class StaticResourceController { def groovyPagesTemplateEngine def css = { def file = params.id def cssPath = servletContext.getRealPath("css/${file}.css") def resourceFile = new File(cssPath) if (!resourceFile.exists()) { // 404: Not Found render(text:"File Not Found",status:404) return } def buffer = null try { buffer = processTemplate(resourceFile, params) } catch (Exception ex) { log.error "Failed to process template", ex } if (buffer != null) { render(text:buffer, contentType:"text/css") } else { // 500: Internal Server Error render(text:"Failed to process template", status:500) } } def processTemplate(resourceFile, model) { def buffer = resourceFile.getText() def template = groovyPagesTemplateEngine.createTemplate(buffer, "${resourceFile.getPath()}") def writer = new StringWriter() template.make(model).writeTo(writer) return writer.toString() } }
- 修改gsp页面中对CSS的引用: ,这句代码会根据/staticResource/css调用StaticResourceController中css这个闭包,而pcss则是这个闭包中需要用到的file的值。这样就可以了!
- 如果静态资源很多,通过StaticResourceController装入这些资源是很耗费内存的,可以在StaticResourceController中进行一些Cache的控制。见如下css闭包的代码:
def css = { def file = params.id def cssPath = servletContext.getRealPath("css/${file}.css") def resourceFile = new File(cssPath) if (!resourceFile.exists()) { render(text:"File Not Found",status:404) return } def ifModifiedSince = request.getHeader("If-Modified-Since") if (ifModifiedSince) { String[] formats = [DateUtils.PATTERN_ASCTIME, DateUtils.PATTERN_RFC1036, DateUtils.PATTERN_RFC1123] def dt_ifModifiedSince = DateUtils.parseDate(ifModifiedSince, formats) long lastMod = (long) (resourceFile.lastModified() / 1000) long isModSince = (long) (dt_ifModifiedSince.getTime() / 1000) if (lastMod <= isModSince) { render (status:304) return } } def now = new Date(System.currentTimeMillis() + (2693000L * 1000)) response.setHeader("Expires", DateUtils.formatDate(now, DateUtils.PATTERN_RFC1123)) response.setHeader("Cache-Control", "public") response.setHeader("Vary", "Accept-Encoding") response.addHeader("Last-Modified", DateUtils.formatDate(new Date(resourceFile.lastModified()), DateUtils.PATTERN_RFC1123)) def buffer = null try { buffer = processTemplate(resourceFile, params) } catch (Exception ex) { log.error "Failed to process template", ex } if (buffer != null) { render (text:buffer, contentType:"text/css") } else { render(text:"Failed to process template", status:500) } }
上述方式实际上书写的并不是纯正的CSS文件,而是一个Groovy Template文件。之后,利用groovyPagesTemplateEngine对其进行处理产生最终所需的CSS文件, 根据这个思路,我们可以对javascript、media作同样的处理,达到简化对应用中静态资源管理的目地。