You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2014/07/01 16:38:26 UTC

git commit: TAP5-2139: It should be possible to control whether a given JavaScript library is minimized or not

Repository: tapestry-5
Updated Branches:
  refs/heads/master 64ef39cf2 -> accbb5374


TAP5-2139: It should be possible to control whether a given JavaScript library is minimized or not


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/accbb537
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/accbb537
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/accbb537

Branch: refs/heads/master
Commit: accbb5374aa29333d2a666d4225c66a77b56581f
Parents: 64ef39c
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Tue Jul 1 07:38:11 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Tue Jul 1 07:38:11 2014 -0700

----------------------------------------------------------------------
 54_RELEASE_NOTES.md                             |  6 +-
 .../org/apache/tapestry5/TapestryConstants.java | 12 +++-
 .../assets/JavaScriptStackAssembler.java        |  5 +-
 .../assets/JavaScriptStackAssemblerImpl.java    | 56 +++++++++++-----
 .../assets/JavaScriptStackMinimizeDisabler.java | 67 ++++++++++++++++++++
 .../assets/StackAssetRequestHandler.java        | 12 ++--
 .../JavaScriptStackPathConstructorImpl.java     | 11 ++--
 .../javascript/JavaScriptStackSourceImpl.java   | 21 +++++-
 .../apache/tapestry5/modules/AssetsModule.java  | 14 ++++
 .../javascript/ExtensibleJavaScriptStack.java   | 40 +++++++++++-
 .../services/javascript/JavaScriptStack.java    | 10 ++-
 .../javascript/JavaScriptStackSource.java       | 13 +++-
 .../JavascriptAggregationStrategy.java          | 42 ++++++++++++
 .../services/javascript/StackExtension.java     | 12 +++-
 .../services/javascript/StackExtensionType.java | 10 ++-
 .../webresources/AbstractMinimizer.java         | 15 +++++
 .../webresources/GoogleClosureMinimizer.java    | 15 ++++-
 .../t5/webresources/services/AppModule.java     |  3 +
 18 files changed, 317 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/54_RELEASE_NOTES.md
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.md b/54_RELEASE_NOTES.md
index 4c80c5d..caabffc 100644
--- a/54_RELEASE_NOTES.md
+++ b/54_RELEASE_NOTES.md
@@ -77,6 +77,10 @@ different library (or subclasses from a library to the application). As long as
 corresponding the the base class' library. In prior releases, the resolution was against the
 subclass' library, which would fail.
 
+It is now possible to control, for each JavaScript Stack, how that stack treats its JavaScript libraries.
+The default is to aggregate the libraries and minimize them, but there are now options to aggregate
+them without minimizing, or to leave them as individual files (neither aggregating, nor minimizing).
+
 ## FormGroup Mixin
 
 This new mixin for Field components adds the outer `<div class="form-group">` and `<label>` elements for a Field
@@ -100,7 +104,7 @@ mode (but disabled in production mode):
 
 ## T5Dashboard Page
 
-The T5 Dashboard is a new page the consolidates Tapestry 5.4's PageCatalog and ServiceStatus pages.
+The T5 Dashboard is a new page the consolidates Tapestry 5.3's PageCatalog and ServiceStatus pages.
 The page is itself extensible, allowing libraries or applications to add their own tabs.
 
 ## tapestry-test deprecated

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
index c136fb2..c06ed59 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/TapestryConstants.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2014 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -60,4 +58,14 @@ public class TapestryConstants
      * @since 5.4
      */
     public static final String RESPONSE_RENDERER = "tapestry.response-renderer";
+
+    /**
+     * Name of a {@link org.apache.tapestry5.services.Request} attribute, used
+     * to disable JavaScript minimization during asset requests.
+     *
+     * @see org.apache.tapestry5.services.javascript.JavaScriptStack#getJavaScriptAggregationStrategy()
+     * @since 5.4
+     */
+    public static final String DISABLE_JAVASCRIPT_MINIMIZATION = "tapestry.disable-javascript-minimization";
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
index aa51c92..dfede36 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssembler.java
@@ -1,5 +1,3 @@
-// Copyright 2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -15,6 +13,7 @@
 package org.apache.tapestry5.internal.services.assets;
 
 import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.javascript.JavascriptAggregationStrategy;
 
 import java.io.IOException;
 
@@ -35,5 +34,5 @@ public interface JavaScriptStackAssembler
      * <p/>
      * Expects the {@linkplain org.apache.tapestry5.services.LocalizationSetter#setNonPersistentLocaleFromLocaleName(String) non-persistent locale} to be set before invoking!
      */
-    StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress) throws IOException;
+    StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress, JavascriptAggregationStrategy javascriptAggregationStrategy) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
index 0494d08..2c5200b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackAssemblerImpl.java
@@ -21,6 +21,7 @@ import org.apache.tapestry5.ioc.services.ThreadLocale;
 import org.apache.tapestry5.services.assets.*;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
+import org.apache.tapestry5.services.javascript.JavascriptAggregationStrategy;
 import org.apache.tapestry5.services.javascript.ModuleManager;
 
 import java.io.*;
@@ -51,6 +52,30 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
     private final Map<String, StreamableResource> cache = CollectionFactory.newCaseInsensitiveMap();
 
+    private class Parameters
+    {
+        final Locale locale;
+
+        final String stackName;
+
+        final boolean compress;
+
+        final JavascriptAggregationStrategy javascriptAggregationStrategy;
+
+        private Parameters(Locale locale, String stackName, boolean compress, JavascriptAggregationStrategy javascriptAggregationStrategy)
+        {
+            this.locale = locale;
+            this.stackName = stackName;
+            this.compress = compress;
+            this.javascriptAggregationStrategy = javascriptAggregationStrategy;
+        }
+
+        Parameters disableCompress()
+        {
+            return new Parameters(locale, stackName, false, javascriptAggregationStrategy);
+        }
+    }
+
     // TODO: Support for aggregated CSS as well as aggregated JavaScript
 
     public JavaScriptStackAssemblerImpl(ThreadLocale threadLocale, ResourceChangeTracker resourceChangeTracker, StreamableResourceSource streamableResourceSource,
@@ -71,44 +96,44 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
         resourceChangeTracker.clearOnInvalidation(cache);
     }
 
-    public StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress) throws IOException
+    public StreamableResource assembleJavaScriptResourceForStack(String stackName, boolean compress, JavascriptAggregationStrategy javascriptAggregationStrategy) throws IOException
     {
         Locale locale = threadLocale.getLocale();
 
-        return assembleJavascriptResourceForStack(locale, stackName, compress);
+        return assembleJavascriptResourceForStack(new Parameters(locale, stackName, compress, javascriptAggregationStrategy));
     }
 
-    private StreamableResource assembleJavascriptResourceForStack(Locale locale, String stackName, boolean compress) throws IOException
+    private StreamableResource assembleJavascriptResourceForStack(Parameters parameters) throws IOException
     {
         String key =
                 String.format("%s[%s] %s",
-                        stackName,
-                        compress ? "COMPRESS" : "UNCOMPRESSED",
-                        locale.toString());
+                        parameters.stackName,
+                        parameters.compress ? "COMPRESS" : "UNCOMPRESSED",
+                        parameters.locale.toString());
 
         StreamableResource result = cache.get(key);
 
         if (result == null)
         {
-            result = assemble(locale, stackName, compress);
+            result = assemble(parameters);
             cache.put(key, result);
         }
 
         return result;
     }
 
-    private StreamableResource assemble(Locale locale, String stackName, boolean compress) throws IOException
+    private StreamableResource assemble(Parameters parameters) throws IOException
     {
-        if (compress)
+        if (parameters.compress)
         {
-            StreamableResource uncompressed = assembleJavascriptResourceForStack(locale, stackName, false);
+            StreamableResource uncompressed = assembleJavascriptResourceForStack(parameters.disableCompress());
 
             return new CompressedStreamableResource(uncompressed, checksumGenerator);
         }
 
-        JavaScriptStack stack = stackSource.getStack(stackName);
+        JavaScriptStack stack = stackSource.getStack(parameters.stackName);
 
-        return assembleStreamableForStack(locale.toString(), stackName, stack.getJavaScriptLibraries(), stack.getModules());
+        return assembleStreamableForStack(parameters.locale.toString(), parameters, stack.getJavaScriptLibraries(), stack.getModules());
     }
 
     interface StreamableReader
@@ -197,9 +222,10 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
         }
     }
 
-    private StreamableResource assembleStreamableForStack(String localeName, String stackName, List<Asset> libraries, List<String> moduleNames) throws IOException
+    private StreamableResource assembleStreamableForStack(String localeName, Parameters parameters,
+                                                          List<Asset> libraries, List<String> moduleNames) throws IOException
     {
-        Assembly assembly = new Assembly(String.format("'%s' JavaScript stack, for locale %s, resources=", stackName, localeName));
+        Assembly assembly = new Assembly(String.format("'%s' JavaScript stack, for locale %s, resources=", parameters.stackName, localeName));
 
         for (Asset library : libraries)
         {
@@ -222,7 +248,7 @@ public class JavaScriptStackAssemblerImpl implements JavaScriptStackAssembler
 
         StreamableResource streamable = assembly.finish();
 
-        if (minificationEnabled)
+        if (minificationEnabled && parameters.javascriptAggregationStrategy.enablesMinimize())
         {
             return resourceMinimizer.minimize(streamable);
         }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackMinimizeDisabler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackMinimizeDisabler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackMinimizeDisabler.java
new file mode 100644
index 0000000..03d7205
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/JavaScriptStackMinimizeDisabler.java
@@ -0,0 +1,67 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.services.assets;
+
+import org.apache.tapestry5.TapestryConstants;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.services.Request;
+import org.apache.tapestry5.services.assets.ResourceDependencies;
+import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.assets.StreamableResourceProcessing;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
+import org.apache.tapestry5.services.javascript.JavaScriptStack;
+import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
+
+import java.io.IOException;
+
+/**
+ * Attempts to match resources against a {@link org.apache.tapestry5.services.javascript.JavaScriptStack}, and
+ * possibly disabled minimization based on the stack.
+ *
+ * @since 5.4
+ */
+public class JavaScriptStackMinimizeDisabler extends DelegatingSRS
+{
+
+    private final JavaScriptStackSource javaScriptStackSource;
+
+    private final Request request;
+
+    public JavaScriptStackMinimizeDisabler(StreamableResourceSource delegate, JavaScriptStackSource javaScriptStackSource, Request request)
+    {
+        super(delegate);
+
+        this.javaScriptStackSource = javaScriptStackSource;
+        this.request = request;
+    }
+
+
+    @Override
+    public StreamableResource getStreamableResource(Resource baseResource, StreamableResourceProcessing processing, ResourceDependencies dependencies) throws IOException
+    {
+        JavaScriptStack stack = javaScriptStackSource.findStackForJavaScriptLibrary(baseResource);
+
+        if (stack != null && !stack.getJavaScriptAggregationStrategy().enablesMinimize())
+        {
+            request.setAttribute(TapestryConstants.DISABLE_JAVASCRIPT_MINIMIZATION, true);
+        }
+
+        try
+        {
+            return delegate.getStreamableResource(baseResource, processing, dependencies);
+        } finally
+        {
+            request.setAttribute(TapestryConstants.DISABLE_JAVASCRIPT_MINIMIZATION, null);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
index c883986..2c44691 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StackAssetRequestHandler.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -22,6 +20,7 @@ import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Response;
 import org.apache.tapestry5.services.assets.AssetRequestHandler;
 import org.apache.tapestry5.services.assets.StreamableResource;
+import org.apache.tapestry5.services.javascript.JavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
 import org.slf4j.Logger;
 
@@ -96,7 +95,9 @@ public class StackAssetRequestHandler implements AssetRequestHandler
             checksum = checksum.substring(1);
         }
 
-        if (stackSource.findStack(stackName) == null)
+        final JavaScriptStack stack = stackSource.findStack(stackName);
+
+        if (stack == null)
         {
             logger.warn(String.format("JavaScript stack '%s' not found.", stackName));
             return false;
@@ -110,13 +111,14 @@ public class StackAssetRequestHandler implements AssetRequestHandler
 
         StreamableResource resource =
                 tracker.perform(String.format("Assembling JavaScript asset stack '%s' (%s)",
-                        stackName, localeName),
+                                stackName, localeName),
                         new IOOperation<StreamableResource>()
                         {
                             public StreamableResource perform() throws IOException
                             {
 
-                                return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed);
+                                return javaScriptStackAssembler.assembleJavaScriptResourceForStack(stackName, compressed,
+                                        stack.getJavaScriptAggregationStrategy());
 
                             }
                         });

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
index ebb7af0..c7eccf7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackPathConstructorImpl.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -78,13 +76,13 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
 
         // When combine scripts is true, we want to build the virtual aggregated JavaScript ... but only
         // if there is more than one library asset, or any modules.
-        if (combineScripts)
+        if (combineScripts && stack.getJavaScriptAggregationStrategy().enablesCombine())
         {
             boolean needsVirtual = (assets.size() > 1) || (!stack.getModules().isEmpty());
 
             if (needsVirtual)
             {
-                return combinedStackURL(stackName);
+                return combinedStackURL(stackName, stack);
             }
         }
 
@@ -98,11 +96,12 @@ public class JavaScriptStackPathConstructorImpl implements JavaScriptStackPathCo
         return F.flow(assets).map(toPath).toList();
     }
 
-    private List<String> combinedStackURL(String stackName)
+    private List<String> combinedStackURL(String stackName, JavaScriptStack stack)
     {
         try
         {
-            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported());
+            StreamableResource assembled = assembler.assembleJavaScriptResourceForStack(stackName, compressionAnalyzer.isGZipSupported(),
+                    stack.getJavaScriptAggregationStrategy());
 
             String path = String.format("%s/%s.js",
                     threadLocale.getLocale(),

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackSourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackSourceImpl.java
index a859a4d..702929f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackSourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptStackSourceImpl.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -14,7 +12,9 @@
 
 package org.apache.tapestry5.internal.services.javascript;
 
+import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.util.AvailableValues;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
@@ -54,4 +54,21 @@ public class JavaScriptStackSourceImpl implements JavaScriptStackSource
     {
         return F.flow(configuration.keySet()).sort().toList();
     }
+
+    @Override
+    public JavaScriptStack findStackForJavaScriptLibrary(Resource resource)
+    {
+        for (JavaScriptStack stack : configuration.values())
+        {
+            for (Asset libraryAsset : stack.getJavaScriptLibraries())
+            {
+                if (libraryAsset.getResource().equals(resource))
+                {
+                    return stack;
+                }
+            }
+        }
+
+        return null;
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
index aa98e5b..77ff436 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/AssetsModule.java
@@ -24,6 +24,7 @@ import org.apache.tapestry5.ioc.services.FactoryDefaults;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
 import org.apache.tapestry5.services.*;
 import org.apache.tapestry5.services.assets.*;
+import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
 import org.apache.tapestry5.services.messages.ComponentMessagesSource;
 
 import java.util.Map;
@@ -146,6 +147,19 @@ public class AssetsModule
         return new CSSURLRewriter(delegate, tracker, assetSource, checksumGenerator, strictCssUrlRewriting);
     }
 
+    @Decorate(id = "DisableMinificationForStacks", serviceInterface = StreamableResourceSource.class)
+    @Order("before:Minification")
+    public StreamableResourceSource setupDisableMinizationByJavaScriptStack(StreamableResourceSource delegate,
+                                                                            @Symbol(SymbolConstants.MINIFICATION_ENABLED)
+                                                                            boolean enabled,
+                                                                            JavaScriptStackSource javaScriptStackSource,
+                                                                            Request request)
+    {
+        return enabled
+                ? new JavaScriptStackMinimizeDisabler(delegate, javaScriptStackSource, request)
+                : null;
+    }
+
     /**
      * Adds content types:
      * <dl>

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java
index fccb2de..881f43c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java
@@ -1,5 +1,3 @@
-// Copyright 2011-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -55,6 +53,8 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
 
     private final String initialization;
 
+    private final JavascriptAggregationStrategy strategy;
+
     private final Predicate<StackExtension> by(final StackExtensionType type)
     {
         return new Predicate<StackExtension>()
@@ -96,6 +96,15 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
         ;
     };
 
+    private final Mapper<String, JavascriptAggregationStrategy> stringToStrategy = new Mapper<String, JavascriptAggregationStrategy>()
+    {
+        @Override
+        public JavascriptAggregationStrategy map(String name)
+        {
+            return JavascriptAggregationStrategy.valueOf(name);
+        }
+    };
+
     public ExtensibleJavaScriptStack(AssetSource assetSource, List<StackExtension> configuration)
     {
         this.assetSource = assetSource;
@@ -115,6 +124,27 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
                 .toList();
 
         initialization = initializations.isEmpty() ? null : InternalUtils.join(initializations, "\n");
+
+        strategy = toStrategy(extensions);
+    }
+
+    private JavascriptAggregationStrategy toStrategy(Flow<StackExtension> extensions)
+    {
+        List<JavascriptAggregationStrategy> values = extensions.filter(by(StackExtensionType.AGGREGATION_STRATEGY)).map(extractValue).map(stringToStrategy).toList();
+
+        switch (values.size())
+        {
+            case 0:
+                return JavascriptAggregationStrategy.COMBINE_AND_MINIMIZE;
+
+            case 1:
+
+                return values.get(0);
+
+            default:
+                throw new IllegalStateException(String.format("Could not handle %d contribution(s) of JavaScriptAggregation Strategy. There should be at most one.",
+                        values.size()));
+        }
     }
 
     public List<String> getStacks()
@@ -141,4 +171,10 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
     {
         return modules;
     }
+
+    @Override
+    public JavascriptAggregationStrategy getJavaScriptAggregationStrategy()
+    {
+        return strategy;
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
index d4f7042..f9f6285 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2014 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -76,6 +74,14 @@ public interface JavaScriptStack
     List<String> getModules();
 
     /**
+     * Identifies how to aggregate JavaScript within the stack.
+     * The default is {@link org.apache.tapestry5.services.javascript.JavascriptAggregationStrategy#COMBINE_AND_MINIMIZE}.
+     *
+     * @since 5.4
+     */
+    JavascriptAggregationStrategy getJavaScriptAggregationStrategy();
+
+    /**
      * Returns static JavaScript initialization for the stack. This block of JavaScript code will be added to the
      * page that imports the stack. The code executes outside of any other function (i.e., the code is not deferred
      * until the DOM is loaded). As with the other methods, if localization is a factor, the result of this method

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStackSource.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStackSource.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStackSource.java
index f0b0684..f4f2f35 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStackSource.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStackSource.java
@@ -1,5 +1,3 @@
-// Copyright 2010-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -14,6 +12,7 @@
 
 package org.apache.tapestry5.services.javascript;
 
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
 import org.apache.tapestry5.ioc.util.UnknownValueException;
 
@@ -50,4 +49,14 @@ public interface JavaScriptStackSource
      * @since 5.2.1
      */
     List<String> getStackNames();
+
+    /**
+     * Attempts to find the stack containing the indicated JavaScript library.
+     *
+     * @param resource identifies a potential JavaScript Library
+     * @return the stack if found, or null
+     * @since 5.4
+     * @see JavaScriptStack#getJavaScriptLibraries()
+     */
+    JavaScriptStack findStackForJavaScriptLibrary(Resource resource);
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavascriptAggregationStrategy.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavascriptAggregationStrategy.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavascriptAggregationStrategy.java
new file mode 100644
index 0000000..9526e92
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavascriptAggregationStrategy.java
@@ -0,0 +1,42 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.services.javascript;
+
+/**
+ * Used with {@link org.apache.tapestry5.services.javascript.JavaScriptStack} to identify how libraries and modules
+ * within the stack can be aggregated.
+ *
+ * @since 5.4
+ */
+public enum JavascriptAggregationStrategy
+{
+    /**
+     * The default strategy is to combine all the assets and minimize them together.
+     */
+    COMBINE_AND_MINIMIZE,
+
+    /**
+     * Alternately, the assets can be combined, but not minimized (because some resources
+     * do not support minimization).
+     */
+    COMBINE_ONLY,
+
+    /**
+     * The assets are not combined or minimized at all.
+     */
+    DO_NOTHING;
+
+    public boolean enablesCombine() { return this != DO_NOTHING; }
+
+    public boolean enablesMinimize() { return this == COMBINE_AND_MINIMIZE; }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java
index 386cd9c..9c7b130 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java
@@ -1,5 +1,3 @@
-// Copyright 2011 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -84,4 +82,14 @@ public class StackExtension
         return new StackExtension(StackExtensionType.STACK, name);
     }
 
+    /**
+     * Convenience for defining the {@link JavaScriptStack#getJavaScriptAggregationStrategy()}.
+     *
+     * @since 5.4
+     */
+    public static StackExtension javascriptAggregation(JavascriptAggregationStrategy strategy)
+    {
+        return new StackExtension(StackExtensionType.AGGREGATION_STRATEGY, strategy.name());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
index 6bb1c47..4f0ade3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java
@@ -1,5 +1,3 @@
-// Copyright 2011-2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -73,6 +71,14 @@ public enum StackExtensionType
     MODULE,
 
     /**
+     * Overrides the {@linkplain JavaScriptStack#getJavaScriptAggregationStrategy() JavaScript aggregation strategy}
+     * for the stack. The value is string name of an {@link org.apache.tapestry5.services.javascript.JavascriptAggregationStrategy} value.
+     *
+     * @since 5.4
+     */
+    AGGREGATION_STRATEGY,
+
+    /**
      * Extra JavaScript initialization (rarely used). No symbol expansion takes place.
      *
      * @see JavaScriptSupport#addScript(String, Object...)

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
index 849b691..4475bec 100644
--- a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/AbstractMinimizer.java
@@ -55,6 +55,11 @@ public abstract class AbstractMinimizer implements ResourceMinimizer
     @Override
     public StreamableResource minimize(final StreamableResource input) throws IOException
     {
+        if (!isEnabled(input))
+        {
+            return input;
+        }
+
         long startNanos = System.nanoTime();
 
         final ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
@@ -106,4 +111,14 @@ public abstract class AbstractMinimizer implements ResourceMinimizer
      * @return stream of minimized content
      */
     protected abstract InputStream doMinimize(StreamableResource resource) throws IOException;
+
+    /**
+     * Determines if the resource can be minimized.
+     *
+     * @return true, subclasses may override
+     */
+    protected boolean isEnabled(StreamableResource resource)
+    {
+        return true;
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
index edd484f..766c171 100644
--- a/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
+++ b/tapestry-webresources/src/main/java/org/apache/tapestry5/internal/webresources/GoogleClosureMinimizer.java
@@ -1,5 +1,3 @@
-// Copyright 2013 The Apache Software Foundation
-//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
@@ -17,9 +15,11 @@ package org.apache.tapestry5.internal.webresources;
 import com.google.javascript.jscomp.*;
 import com.google.javascript.jscomp.Compiler;
 import org.apache.commons.io.IOUtils;
+import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.ioc.OperationTracker;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.assets.AssetChecksumGenerator;
 import org.apache.tapestry5.services.assets.StreamableResource;
 import org.slf4j.Logger;
@@ -38,14 +38,23 @@ public class GoogleClosureMinimizer extends AbstractMinimizer
 {
     private final List<SourceFile> EXTERNS = Collections.emptyList();
 
+    private final Request request;
+
     static
     {
         Compiler.setLoggingLevel(Level.SEVERE);
     }
 
-    public GoogleClosureMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator)
+    public GoogleClosureMinimizer(Logger logger, OperationTracker tracker, AssetChecksumGenerator checksumGenerator, Request request)
     {
         super(logger, tracker, checksumGenerator, "text/javascript");
+        this.request = request;
+    }
+
+    @Override
+    protected boolean isEnabled(StreamableResource resource)
+    {
+        return request.getAttribute(TapestryConstants.DISABLE_JAVASCRIPT_MINIMIZATION) == null;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/accbb537/tapestry-webresources/src/test/java/t5/webresources/services/AppModule.java
----------------------------------------------------------------------
diff --git a/tapestry-webresources/src/test/java/t5/webresources/services/AppModule.java b/tapestry-webresources/src/test/java/t5/webresources/services/AppModule.java
index 362d647..953fa07 100644
--- a/tapestry-webresources/src/test/java/t5/webresources/services/AppModule.java
+++ b/tapestry-webresources/src/test/java/t5/webresources/services/AppModule.java
@@ -11,6 +11,7 @@ import org.apache.tapestry5.services.Core;
 import org.apache.tapestry5.services.compatibility.Compatibility;
 import org.apache.tapestry5.services.compatibility.Trait;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
+import org.apache.tapestry5.services.javascript.JavascriptAggregationStrategy;
 import org.apache.tapestry5.services.javascript.StackExtension;
 import org.apache.tapestry5.services.javascript.StackExtensionType;
 import org.apache.tapestry5.webresources.modules.WebResourcesModule;
@@ -38,6 +39,8 @@ public class AppModule
     @Core
     public static void overrideBootstrapCSS(OrderedConfiguration<StackExtension> configuration)
     {
+        // configuration.add("ForTestingOnly", StackExtension.javascriptAggregation(JavascriptAggregationStrategy.DO_NOTHING));
+
         configuration.override("bootstrap.css",
                 new StackExtension(StackExtensionType.STYLESHEET, "context:bootstrap/less/bootstrap.less"), "before:tapestry.css");
     }