diff --git a/asset-pipeline-grails/grails-app/assets/javascripts/asset-pipeline/test/test_simple_require.js b/asset-pipeline-grails/grails-app/assets/javascripts/asset-pipeline/test/test_simple_require.js
new file mode 100644
index 00000000..b769ba26
--- /dev/null
+++ b/asset-pipeline-grails/grails-app/assets/javascripts/asset-pipeline/test/test_simple_require.js
@@ -0,0 +1,3 @@
+//=require libs/file_a
+
+console.log("This and file_a should be included");
\ No newline at end of file
diff --git a/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetMethodTagLib.groovy b/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetMethodTagLib.groovy
index e2a30b94..348a5b1b 100644
--- a/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetMethodTagLib.groovy
+++ b/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetMethodTagLib.groovy
@@ -16,16 +16,19 @@ class AssetMethodTagLib {
def assetPath = {final def attrs ->
final def src
final UrlBase urlBase
+ final boolean useManifest
if (attrs instanceof Map) {
- src = attrs.src
- urlBase = attrs.absolute ? SERVER_BASE_URL : CONTEXT_PATH
+ src = attrs.src
+ urlBase = attrs.absolute ? SERVER_BASE_URL : CONTEXT_PATH
+ useManifest = attrs.useManifest ?: true
}
else {
- src = attrs
- urlBase = CONTEXT_PATH
+ src = attrs
+ urlBase = CONTEXT_PATH
+ useManifest = true
}
- return assetProcessorService.assetBaseUrl(request, urlBase) + assetProcessorService.getAssetPath(Objects.toString(src))
+ return assetProcessorService.assetBaseUrl(request, urlBase) + assetProcessorService.getAssetPath(Objects.toString(src), useManifest)
}
}
diff --git a/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetsTagLib.groovy b/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetsTagLib.groovy
index e440dcf6..c1f9629b 100644
--- a/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetsTagLib.groovy
+++ b/asset-pipeline-grails/grails-app/taglib/asset/pipeline/grails/AssetsTagLib.groovy
@@ -1,35 +1,36 @@
package asset.pipeline.grails
-import grails.util.Environment
-import grails.core.GrailsApplication
-import asset.pipeline.AssetPipeline
-import asset.pipeline.AssetPipelineConfigHolder
import asset.pipeline.AssetHelper
import asset.pipeline.AssetPipeline
+import asset.pipeline.AssetPipelineConfigHolder
+import grails.core.GrailsApplication
import org.grails.buffer.GrailsPrintWriter
-
class AssetsTagLib {
static namespace = 'asset'
static returnObjectForTags = ['assetPath']
+ static final ASSET_REQUEST_MEMO = "asset-pipeline.memo"
private static final LINE_BREAK = System.getProperty('line.separator') ?: '\n'
+
GrailsApplication grailsApplication
def assetProcessorService
/**
* @attr src REQUIRED
+ * @attr asset-defer OPTIONAL ensure script blocks are deferred to when the deferrred-scripts is used
+ * @attr uniq OPTIONAL Output the script tag for the given resource only once per request, note that uniq mode cannot be bundled
*/
def javascript = {final attrs ->
final GrailsPrintWriter outPw = out
attrs.remove('href')
- element(attrs, 'js', 'application/javascript', null) {final String src, final String queryString, final outputAttrs, final String endOfLine ->
+ element(attrs, 'js', 'application/javascript', null) {final String src, final String queryString, final outputAttrs, final String endOfLine, final boolean useManifest ->
if(attrs.containsKey('asset-defer')) {
- script(outputAttrs + [type: "text/javascript", src: assetPath(src: src) + queryString],'')
+ script(outputAttrs + [type: "text/javascript", src: assetPath(src: src, useManifest: useManifest) + queryString],'')
} else {
- outPw << '' << endOfLine
+ outPw << '' << endOfLine
}
}
@@ -40,50 +41,74 @@ class AssetsTagLib {
*
* @attr href OPTIONAL standard URL attribute
* @attr src OPTIONAL alternate URL attribute, only used if {@code href} isn't supplied, or if {@code href} is Groovy false
+ * @attr uniq OPTIONAL Output the stylesheet tag for the resource only once per request, note that uniq mode cannot be bundled
*/
def stylesheet = {final attrs ->
final GrailsPrintWriter outPw = out
- element(attrs, 'css', 'text/css', Objects.toString(attrs.remove('href'), null)) {final String src, final String queryString, final outputAttrs, final String endOfLine ->
+ element(attrs, 'css', 'text/css', Objects.toString(attrs.remove('href'), null)) {final String src, final String queryString, final outputAttrs, final String endOfLine, final boolean useManifest ->
+ outPw << ''
if (endOfLine) {
- outPw << '' << endOfLine
- }
- else {
- outPw << link([rel: 'stylesheet', href: src] + outputAttrs)
+ outPw << endOfLine
}
}
}
+ private boolean isIncluded(def path) {
+ HashSet memo = request."$ASSET_REQUEST_MEMO"
+ if (memo == null) {
+ memo = new HashSet()
+ request."$ASSET_REQUEST_MEMO" = memo
+ }
+ !memo.add(path)
+ }
+
+ private static def nameAndExtension(String src, String ext) {
+ int lastDotIndex = src.lastIndexOf('.')
+ if (lastDotIndex >= 0) {
+ [uri: src.substring(0, lastDotIndex), extension: src.substring(lastDotIndex + 1)]
+ } else {
+ [uri: src, extension: ext]
+ }
+ }
+
private void element(final attrs, final String ext, final String contentType, final String srcOverride, final Closure output) {
def src = attrs.remove('src')
if (srcOverride) {
src = srcOverride
}
+ def uniqMode = attrs.remove('uniq') != null
+
src = "${AssetHelper.nameWithoutExtension(src)}.${ext}"
def conf = grailsApplication.config.grails.assets
- final def nonBundledMode = (!AssetPipelineConfigHolder.manifest && conf.bundle != true && attrs.remove('bundle') != 'true')
+ final def nonBundledMode = uniqMode || (!AssetPipelineConfigHolder.manifest && conf.bundle != true && attrs.remove('bundle') != 'true')
if (! nonBundledMode) {
- output(src, '', attrs, '')
+ output(src, '', attrs, '', true)
}
else {
- final int lastDotIndex = src.lastIndexOf('.')
- final def uri
- final def extension
- if (lastDotIndex >= 0) {
- uri = src.substring(0, lastDotIndex)
- extension = src.substring(lastDotIndex + 1)
- }
- else {
- uri = src
- extension = ext
- }
+ def name = nameAndExtension(src, ext)
+ final String uri = name.uri
+ final String extension = name.extension
+
final String queryString =
attrs.charset \
? "?compile=false&encoding=${attrs.charset}"
: '?compile=false'
+ if (uniqMode && isIncluded(name)) {
+ return
+ }
+ def useManifest = !nonBundledMode
+
AssetPipeline.getDependencyList(uri, contentType, extension)?.each {
- output(it.path, queryString, attrs, LINE_BREAK)
+ if (uniqMode) {
+ def path = nameAndExtension(it.path, ext)
+ if (path.uri == uri || !isIncluded(path)) {
+ output(it.path, queryString, attrs, LINE_BREAK, useManifest)
+ }
+ } else {
+ output(it.path, queryString, attrs, LINE_BREAK, useManifest)
+ }
}
}
}
diff --git a/asset-pipeline-grails/src/main/groovy/asset/pipeline/AssetPipelineGrailsPlugin.groovy b/asset-pipeline-grails/src/main/groovy/asset/pipeline/AssetPipelineGrailsPlugin.groovy
index 103e4f26..d8408f38 100644
--- a/asset-pipeline-grails/src/main/groovy/asset/pipeline/AssetPipelineGrailsPlugin.groovy
+++ b/asset-pipeline-grails/src/main/groovy/asset/pipeline/AssetPipelineGrailsPlugin.groovy
@@ -89,7 +89,10 @@ class AssetPipelineGrailsPlugin extends grails.plugins.Plugin {
log.warn "Unable to find asset-pipeline manifest, etags will not be properly generated"
}
}
- if(manifestFile?.exists()) {
+
+ def useManifest = assetsConfig.useManifest ?: true
+
+ if(useManifest && manifestFile?.exists()) {
try {
manifestProps.load(manifestFile.inputStream)
assetsConfig.manifest = manifestProps
diff --git a/asset-pipeline-grails/src/main/groovy/asset/pipeline/grails/AssetProcessorService.groovy b/asset-pipeline-grails/src/main/groovy/asset/pipeline/grails/AssetProcessorService.groovy
index 347def0b..ba114c4a 100644
--- a/asset-pipeline-grails/src/main/groovy/asset/pipeline/grails/AssetProcessorService.groovy
+++ b/asset-pipeline-grails/src/main/groovy/asset/pipeline/grails/AssetProcessorService.groovy
@@ -46,10 +46,17 @@ class AssetProcessorService {
return mapping
}
+ String getAssetPath(final String path, final boolean useManifest) {
+ getAssetPath(path, grailsApplication.config.grails.assets as ConfigObject, useManifest)
+ }
- String getAssetPath(final String path, final ConfigObject conf = grailsApplication.config.grails.assets) {
+ String getAssetPath(final String path, final ConfigObject conf = grailsApplication.config.grails.assets, final boolean useManifest = true) {
final String relativePath = trimLeadingSlash(path)
- return manifest?.getProperty(relativePath) ?: relativePath
+ if (useManifest) {
+ return manifest?.getProperty(relativePath) ?: relativePath
+ } else {
+ return relativePath
+ }
}
diff --git a/asset-pipeline-grails/src/test/groovy/asset/pipeline/grails/AssetsTagLibSpec.groovy b/asset-pipeline-grails/src/test/groovy/asset/pipeline/grails/AssetsTagLibSpec.groovy
index 918619d5..bb7f72bb 100644
--- a/asset-pipeline-grails/src/test/groovy/asset/pipeline/grails/AssetsTagLibSpec.groovy
+++ b/asset-pipeline-grails/src/test/groovy/asset/pipeline/grails/AssetsTagLibSpec.groovy
@@ -15,14 +15,11 @@
*/
package asset.pipeline.grails
-
import asset.pipeline.AssetPipelineConfigHolder
import asset.pipeline.fs.FileSystemAssetResolver
import grails.test.mixin.TestFor
import spock.lang.Specification
-
-
/**
* @author David Estes
*/
@@ -89,12 +86,34 @@ class AssetsTagLibSpec extends Specification {
output == '' + LINE_BREAK + '' + LINE_BREAK + '' + LINE_BREAK + '' + LINE_BREAK + '' + LINE_BREAK
}
+ void "should not return javascript link twice in uniq mode"() {
+ given:
+ final def assetSrc = "asset-pipeline/test/test_simple_require.js"
+ final def depAssetSrc = "asset-pipeline/test/libs/file_a.js"
+ expect:
+ tagLib.javascript(src: assetSrc, uniq: true) == '' + LINE_BREAK + '' + LINE_BREAK
+ tagLib.javascript(src: assetSrc, uniq: true) == ''
+ tagLib.javascript(src: depAssetSrc, uniq: true) == ''
+ cleanup:
+ request."${AssetsTagLib.ASSET_REQUEST_MEMO}" = null
+ }
+
+ void "should return javascript and stylesheets of the same basename"() {
+ given:
+ final def jsAssetSrc = "asset-pipeline/test/test.js"
+ final def cssAssetSrc = "asset-pipeline/test/test.css"
+ expect:
+ tagLib.javascript(src: jsAssetSrc, uniq: true) != ''
+ tagLib.javascript(src: jsAssetSrc, uniq: true) == ''
+ tagLib.stylesheet(src: cssAssetSrc, uniq: true) != ''
+ }
+
void "should return stylesheet link tag when debugMode is off"() {
given:
grailsApplication.config.grails.assets.bundle = true
final def assetSrc = "asset-pipeline/test/test.css"
expect:
- tagLib.stylesheet(href: assetSrc) == ''
+ tagLib.stylesheet(href: assetSrc) == ''
}
void "should always return stylesheet link tag when bundle attr is 'true'"() {
@@ -104,7 +123,7 @@ class AssetsTagLibSpec extends Specification {
params."_debugAssets" = "y"
final def assetSrc = "asset-pipeline/test/test.css"
expect:
- tagLib.stylesheet(href: assetSrc, bundle: 'true') == ''
+ tagLib.stylesheet(href: assetSrc, bundle: 'true') == ''
}
void "should return stylesheet link tag with seperated files when debugMode is on"() {
@@ -121,6 +140,18 @@ class AssetsTagLibSpec extends Specification {
output == '' + LINE_BREAK + '' + LINE_BREAK
}
+ void "should not return stylesheet link twice in uniq mode"() {
+ given:
+ final def assetSrc = "asset-pipeline/test/test.css"
+ final def depAssetSrc = "asset-pipeline/test/test2.css"
+ expect:
+ tagLib.stylesheet(src: assetSrc, uniq: true) == '' + LINE_BREAK + '' + LINE_BREAK
+ tagLib.stylesheet(src: depAssetSrc, uniq: true) == ''
+ tagLib.stylesheet(src: assetSrc, uniq: true) == ''
+ cleanup:
+ request."${AssetsTagLib.ASSET_REQUEST_MEMO}" = null
+ }
+
void "should return image tag"() {
given:
final def assetSrc = "grails_logo.png"