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 2012/07/06 02:38:00 UTC

[5/16] git commit: Allow non-AMD libraries to be accessed - ModuleManager now has configuration of ShimModule to define resources, dependencies and exports - ResourceTransformer now defines content type of transformed resources - StreamableResourceSource

Allow non-AMD libraries to be accessed
- ModuleManager now has configuration of ShimModule to define resources, dependencies and exports
- ResourceTransformer now defines content type of transformed resources
- StreamableResourceSource uses the transformed stream content type after transforming
- Documented new limitations of InitializationPriority due to asychronous behavior of RequireJS
- Extended core/pageinit module with new exported functions for different initializations
- JavaScriptSupport.addScript() now routed through an client-side initialization function based on eval()


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

Branch: refs/heads/5.4-js-rewrite
Commit: e4503a4b8e4499a7d5efe6cf833bd122f6e81c5a
Parents: 949f9fb
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Thu Jul 5 16:33:59 2012 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Thu Jul 5 16:33:59 2012 -0700

----------------------------------------------------------------------
 54_RELEASE_NOTES.txt                               |    3 +
 .../tapestry5/corelib/modulejs/pageinit.coffee     |   33 ++++-
 .../java/org/apache/tapestry5/SymbolConstants.java |    2 +-
 .../internal/services/DocumentLinkerImpl.java      |   32 +----
 .../assets/StreamableResourceSourceImpl.java       |    2 +-
 .../JavaScriptWrapperResourceTransformer.java      |    5 +
 .../services/javascript/ModuleManagerImpl.java     |   59 +++++--
 .../services/assets/ResourceTransformer.java       |    7 +
 .../javascript/InitializationPriority.java         |   25 ++-
 .../services/javascript/JavaScriptModule.java      |   10 +-
 .../services/javascript/ModuleManager.java         |   10 +-
 .../tapestry5/services/javascript/ShimModule.java  |   58 +++++++
 .../apache/tapestry5/corelib/modulejs/domReady.js  |  125 +++++++++++++++
 .../resources/org/apache/tapestry5/underscore.jsw  |    4 -
 .../services/DocumentLinkerImplTest.groovy         |   35 +---
 15 files changed, 317 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/54_RELEASE_NOTES.txt
----------------------------------------------------------------------
diff --git a/54_RELEASE_NOTES.txt b/54_RELEASE_NOTES.txt
index 6e42455..f2de7a0 100644
--- a/54_RELEASE_NOTES.txt
+++ b/54_RELEASE_NOTES.txt
@@ -9,6 +9,9 @@ JavaScript Libraries (including stacks) are being replaced with modules. Note th
 with RequireJS, which may mean that global values exported by the libraries are not visible; you should explicitly
 attach properties to the global JavaScript window object, rather than assume that the context (this) is the window.
 
+The interface org.apache.tapestry5.services.assets.ResourceTransformer has had a new method added:
+getTransformedContentType().
+
 Bugs fixed (in 5.4-js-rewrite branch):
 
 TAP5-1965: Replace use of Request.getContextPath() with a symbol define at application startup

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/pageinit.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/pageinit.coffee b/tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/pageinit.coffee
index abf8cf2..7c7a3b8 100644
--- a/tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/pageinit.coffee
+++ b/tapestry-core/src/main/coffeescript/org/apache/tapestry5/corelib/modulejs/pageinit.coffee
@@ -19,9 +19,8 @@
 # The module name may also indicate the function exported by the module, as a suffix following a colon:
 # e.g., "my/module:myfunc".
 # Any additional values in the initializer are passed to the function. The context of the function (this) is null.
-define ->
+define ["_", "core/console"], (_, console) ->
   invokeInitializer = (qualifiedName, initArguments...) ->
-
     [moduleName, functionName] = qualifiedName.split ':'
 
     require [moduleName], (moduleLib) ->
@@ -29,8 +28,34 @@ define ->
       fn.apply null, initArguments
 
 
-  # Exported function passed a list of initializers.
-  initialize : (inits) ->
+  # Passed a list of initializers, executes each initializer in order. Due to asynchronous loading
+  # of modules, the exact order in which initializer functions are invoked is not predictable.
+  initialize: (inits) ->
     # apply will split the first value from the rest for us, implicitly.
     invokeInitializer.apply null, init for init in inits
 
+  # Pre-loads a number of scripts in order. When the last script is loaded,
+  # invokes the callback (with no parameters).
+  loadScripts: (scripts, callback) ->
+    reducer = (callback, script) -> ->
+      console.debug "Loading script #{script}"
+      require [script], callback
+
+    finalCallback = _.reduceRight scripts, reducer, callback
+
+    finalCallback.call(null)
+
+  # Loads all the scripts, in order. It then executes the immediate initializations.
+  # After that, it waits for the DOM to be ready and executes the other initializations.
+  loadScriptsAndInitialize: (scripts, immediateInits, otherInits) ->
+    this.loadScripts scripts, ->
+      this.initialize immediateInits
+      require ["core/domReady!"], -> this.initialize otherInits
+
+  evalJavaScript: (js) ->
+    console.debug "Evaluating: #{js}"
+    eval js
+
+
+
+

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index 07229bb..bcfa90b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -338,7 +338,7 @@ public class SymbolConstants
     public static final String ASSET_URL_FULL_QUALIFIED = "tapestry.asset-url-fully-qualified";
 
     /**
-     * Prefix to be used for all asset paths, used to recognize which requests are for assets. This value
+     * Prefix to be used for all resource paths, used to recognize which requests are for assets. This value
      * is appended to the context path and the (optional {@linkplain #APPLICATION_FOLDER application folder}.
      * Its default is "assets".  It may contain slashes, but should not begin or end with one.
      *

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
index e906680..1f529c5 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
@@ -30,8 +30,6 @@ public class DocumentLinkerImpl implements DocumentLinker
 {
     private final List<String> scriptURLs = CollectionFactory.newList();
 
-    private final Map<InitializationPriority, StringBuilder> priorityToScript = CollectionFactory.newMap();
-
     private final Map<InitializationPriority, List<JSONArray>> priorityToModuleInit = CollectionFactory.newMap();
 
     private final List<StylesheetLink> includedStylesheets = CollectionFactory.newList();
@@ -80,20 +78,7 @@ public class DocumentLinkerImpl implements DocumentLinker
 
     public void addScript(InitializationPriority priority, String script)
     {
-
-        StringBuilder builder = priorityToScript.get(priority);
-
-        if (builder == null)
-        {
-            builder = new StringBuilder();
-            priorityToScript.put(priority, builder);
-        }
-
-        builder.append(script);
-
-        builder.append("\n");
-
-        hasScriptsOrInitializations = true;
+        addInitialization(priority, "core/pageinit", "evalJavaScript", new JSONArray().put(script));
     }
 
     @Override
@@ -232,7 +217,7 @@ public class DocumentLinkerImpl implements DocumentLinker
             body.element("script", "type", "text/javascript", "src", scriptURL);
         }
 
-        if (priorityToScript.isEmpty() && priorityToModuleInit.isEmpty())
+        if (priorityToModuleInit.isEmpty())
         {
             return;
         }
@@ -244,8 +229,7 @@ public class DocumentLinkerImpl implements DocumentLinker
         for (InitializationPriority p : InitializationPriority.values())
         {
             if (p != InitializationPriority.IMMEDIATE && !wrapped
-                    && (priorityToScript.containsKey(p) ||
-                    priorityToModuleInit.containsKey(p)))
+                    && priorityToModuleInit.containsKey(p))
             {
 
                 block.append("Tapestry.onDOMLoaded(function() {\n");
@@ -254,8 +238,6 @@ public class DocumentLinkerImpl implements DocumentLinker
             }
 
             addModuleInits(block, priorityToModuleInit.get(p));
-
-            addDirectScriptInitialization(block, priorityToScript.get(p));
         }
 
         if (wrapped)
@@ -288,14 +270,6 @@ public class DocumentLinkerImpl implements DocumentLinker
         block.append("]);\n});\n");
     }
 
-    private void addDirectScriptInitialization(StringBuilder block, StringBuilder content)
-    {
-        if (content == null)
-            return;
-
-        block.append(content);
-    }
-
     private static Element createTemporaryContainer(Element headElement, String existingElementName, String newElementName)
     {
         Element existingScript = headElement.find(existingElementName);

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
index 92e886e..b0bc641 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/StreamableResourceSourceImpl.java
@@ -68,7 +68,7 @@ public class StreamableResourceSourceImpl implements StreamableResourceSource
 
         transformed.close();
 
-        String contentType = contentTypeAnalyzer.getContentType(baseResource);
+        String contentType = rt == null ? contentTypeAnalyzer.getContentType(baseResource) : rt.getTransformedContentType();
 
         boolean compressable = compressionAnalyzer.isCompressable(contentType);
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptWrapperResourceTransformer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptWrapperResourceTransformer.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptWrapperResourceTransformer.java
index 74502ad..e94f0c7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptWrapperResourceTransformer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/JavaScriptWrapperResourceTransformer.java
@@ -38,6 +38,11 @@ public class JavaScriptWrapperResourceTransformer implements ResourceTransformer
         this.streamableResourceSource = streamableResourceSource;
     }
 
+    public String getTransformedContentType()
+    {
+        return "text/javascript";
+    }
+
     @Override
     public InputStream transform(Resource source, ResourceDependencies dependencies) throws IOException
     {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
index 6f57358..7827151 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/ModuleManagerImpl.java
@@ -23,11 +23,15 @@ import org.apache.tapestry5.func.Worker;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.ComponentClassResolver;
 import org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.javascript.ModuleManager;
+import org.apache.tapestry5.services.javascript.ShimModule;
 
 import java.util.Comparator;
 import java.util.List;
@@ -39,13 +43,13 @@ public class ModuleManagerImpl implements ModuleManager
 
     private final Asset requireJS;
 
-    private final Map<String, Resource> configuration;
-
     // Library names, sorted by order of descending length.
     private final List<String> libraryNames;
 
     private final Map<String, List<String>> libraryNameToPackageNames = CollectionFactory.newMap();
 
+    private final Map<String, Resource> shimModuleNameToResource = CollectionFactory.newMap();
+
     private final Resource classpathRoot;
 
     // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null.
@@ -54,14 +58,13 @@ public class ModuleManagerImpl implements ModuleManager
     public ModuleManagerImpl(AssetPathConstructor constructor, final ComponentClassResolver resolver, AssetSource assetSource,
                              @Path("${" + SymbolConstants.REQUIRE_JS + "}")
                              Asset requireJS,
-                             Map<String, Resource> configuration)
+                             Map<String, ShimModule> configuration,
+                             @Symbol(SymbolConstants.COMPACT_JSON)
+                             boolean compactJSON)
     {
         this.requireJS = requireJS;
-        this.configuration = configuration;
-        String baseURL = constructor.constructAssetPath("module-root", "");
 
-        requireConfig = String.format("require.config({baseUrl:\"%s\"});\n",
-                baseURL);
+        this.requireConfig = buildRequireJSConfig(constructor.constructAssetPath("module-root", ""), compactJSON, configuration);
 
         classpathRoot = assetSource.resourceForPath("");
 
@@ -87,6 +90,39 @@ public class ModuleManagerImpl implements ModuleManager
         libraryNameToPackageNames.put("app", resolver.getPackagesForLibrary(""));
     }
 
+    private String buildRequireJSConfig(String baseURL, boolean compactJSON, Map<String, ShimModule> configuration)
+    {
+        JSONObject shims = new JSONObject();
+        JSONObject config = new JSONObject().put("baseUrl", baseURL).put("shim", shims);
+
+        for (String name : configuration.keySet())
+        {
+            ShimModule module = configuration.get(name);
+
+            shimModuleNameToResource.put(name, module.resource);
+
+            JSONObject shim = new JSONObject();
+
+            if (module.dependencies != null && !module.dependencies.isEmpty())
+            {
+                for (String dep : module.dependencies)
+                {
+                    shim.accumulate("deps", dep);
+                }
+            }
+
+            if (InternalUtils.isNonBlank(module.exports))
+            {
+                shim.put("exports", module.exports);
+            }
+
+            shims.put(name, shim);
+        }
+
+        return String.format("require.config(%s);\n",
+                config.toString(compactJSON));
+    }
+
     @PostInjection
     public void setupInvalidation(ResourceChangeTracker tracker)
     {
@@ -112,7 +148,6 @@ public class ModuleManagerImpl implements ModuleManager
             cache.put(moduleName, resource);
         }
 
-
         // We're treating classpathRoot as a placeholder for null.
 
         return resource == classpathRoot ? null : resource;
@@ -120,16 +155,10 @@ public class ModuleManagerImpl implements ModuleManager
 
     private Resource resolveModuleNameToResource(String moduleName)
     {
-        Resource resource = configuration.get(moduleName);
+        Resource resource = shimModuleNameToResource.get(moduleName);
 
         if (resource != null)
         {
-            if (!resource.exists())
-            {
-                throw new RuntimeException(String.format("Resource %s (mapped as module '%s') does not exist.",
-                        resource, moduleName));
-            }
-
             return resource;
         }
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
index fb51c01..4eb517b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/assets/ResourceTransformer.java
@@ -30,6 +30,13 @@ import java.io.InputStream;
 public interface ResourceTransformer
 {
     /**
+     * Returns the MIME type of a transformed stream.
+     *
+     * @since 5.4
+     */
+    String getTransformedContentType();
+
+    /**
      * Read the source input stream and provide a new input stream of the transformed content.
      *
      * @param source

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/InitializationPriority.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/InitializationPriority.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/InitializationPriority.java
index d2d94b6..608efed 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/InitializationPriority.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/InitializationPriority.java
@@ -16,24 +16,35 @@ package org.apache.tapestry5.services.javascript;
 
 /**
  * Sets the priority for JavaScript initialization scripting. InitializationPriority allows coarse-grained control
- * over the order in which initialization occurs on the client. The default is normally {@link #NORMAL}.
- * 
+ * over the order in which initialization occurs on the client. The default is normally {@link #NORMAL}. Starting in 5.4,
+ * these values have less meaning, as the {@linkplain JavaScriptSupport#require(String) dynamic loading of modules} may
+ * have unexpected effects on the exact order in which initialization occurs.
+ *
  * @since 5.2.0
  */
 public enum InitializationPriority
 {
     /**
-     * Provided JavaScript will be executed immediately (it is not deferred until the page loads). In an Ajax
-     * update, IMMEDIATE code executed after the DOM is updated and before EARLY.
+     * Provided JavaScript will be executed immediately (it is not deferred until the page loads). Execution occur via
+     * JavaScript's {@code eval}, and occurs once all {@linkplain JavaScriptSupport#importJavaScriptLibrary(org.apache.tapestry5.Asset) JavaScript libraries}
+     * (but not modules) for the page have been loaded.
+     * <p/>
+     * In an Ajax update, IMMEDIATE code is executed after the DOM is updated and before EARLY.
      */
     IMMEDIATE,
 
-    /** Execution is deferred until the page loads. All early execution occurs before {@link #NORMAL}. */
+    /**
+     * Execution is deferred until the page loads. All early execution occurs before {@link #NORMAL}.
+     */
     EARLY,
 
-    /** Execution is deferred until the page loads. This is the typical priority. */
+    /**
+     * Execution is deferred until the page loads. This is the typical priority.
+     */
     NORMAL,
 
-    /** Execution is deferred until the page loads. Execution occurs after {@link #NORMAL}. */
+    /**
+     * Execution is deferred until the page loads. Execution occurs after {@link #NORMAL}.
+     */
     LATE
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
index 2de3a82..d8a0d79 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptModule.java
@@ -14,8 +14,10 @@
 
 package org.apache.tapestry5.services.javascript;
 
+import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.RenderSupport;
+import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.DocumentLinker;
 import org.apache.tapestry5.internal.services.RenderSupportImpl;
@@ -23,8 +25,10 @@ import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl;
 import org.apache.tapestry5.internal.services.javascript.*;
 import org.apache.tapestry5.ioc.MappedConfiguration;
 import org.apache.tapestry5.ioc.OrderedConfiguration;
+import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.services.SymbolSource;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONObject;
@@ -227,9 +231,11 @@ public class JavaScriptModule
     }
 
     @Contribute(ModuleManager.class)
-    public static void setupExtraModules(MappedConfiguration<String, Object> configuration)
+    public static void setupBaseModuleShims(MappedConfiguration<String, Object> configuration,
+                                            @Inject @Path("classpath:org/apache/tapestry5/underscore_1_3_3.js")
+                                            Resource underscore)
     {
-        configuration.add("_", "classpath:org/apache/tapestry5/underscore.jsw");
+        configuration.add("_", new ShimModule(underscore, null, "_"));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
index d534907..b7208ff 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ModuleManager.java
@@ -22,13 +22,12 @@ import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
  * Responsible for managing access to the JavaScript modules.
  * <p/>
  * The configuration of the service allows overrides of the default search path; the configuration keys
- * are module names, and the configuration values are the resources for those module names. This is used to give
- * commonly-used modules a short module name (e.g., "_" for <a href="http://underscore.js">Underscore.js</a>),
- * <em>OR</em> to allow selective monkey-patching of default modules.
+ * are module names, and the configuration values are the {@link ShimModule} definitions for those module names.
+ * This is primarily used to wrap non-AMD compliant libraries for use with RequireJS (via contributed {@link ShimModule}s).
  *
  * @since 5.4
  */
-@UsesMappedConfiguration(Resource.class)
+@UsesMappedConfiguration(ShimModule.class)
 public interface ModuleManager
 {
     /**
@@ -44,6 +43,9 @@ public interface ModuleManager
 
     /**
      * Given a module name (which may be a path of names separated by slashes), locates the corresponding {@link Resource}.
+     * First checks for {@linkplain ShimModule contributed shim modules}, then searches for possible matches among the
+     * {@linkplain org.apache.tapestry5.services.ComponentClassResolver#getLibraryNames() defined library names}.  As a special
+     * case, the folder name "app" is mapped to the application's package.
      *
      * @param moduleName
      *         name of module to locate

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ShimModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ShimModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ShimModule.java
new file mode 100644
index 0000000..9cd1150
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ShimModule.java
@@ -0,0 +1,58 @@
+// Copyright 2012 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
+//
+// 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;
+
+import org.apache.tapestry5.ioc.Resource;
+
+import java.util.List;
+
+/**
+ * Used to define a <a href="http://requirejs.org/docs/api.html#config-shim">module shim</a>, used to adapt non-AMD JavaScript libraries
+ * to operate like proper modules.  This information is used to build up a list of dependencies for the contributed JavaScript module,
+ * and to identify the resource to be streamed to the client.
+ * <p/>
+ * Instances of this class are contributed to the {@link ModuleManager} service;  the contribution key is the module name
+ * (typically, a single word).
+ * <p/>
+ * Tapestry contributes a single module, {@code _} for the <a href="http://underscore.js.org">Underscore</a> JavaScript library.
+ *
+ * @since 5.4
+ */
+public final class ShimModule
+{
+    /**
+     * The resource for this shim module.
+     */
+    public final Resource resource;
+
+    /**
+     * The names of other shim modules that should be loaded before this shim module.
+     */
+    public final List<String> dependencies;
+
+    /**
+     * Optional (but desirable) value exported by the shim module.
+     */
+    public final String exports;
+
+    public ShimModule(Resource resource, List<String> dependencies, String exports)
+    {
+        assert resource != null;
+
+        this.resource = resource;
+        this.dependencies = dependencies;
+        this.exports = exports;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/modulejs/domReady.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/modulejs/domReady.js b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/modulejs/domReady.js
new file mode 100644
index 0000000..6a79772
--- /dev/null
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/corelib/modulejs/domReady.js
@@ -0,0 +1,125 @@
+/**
+ * @license RequireJS domReady 2.0.0 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/requirejs/domReady for details
+ */
+/*jslint */
+/*global require: false, define: false, requirejs: false,
+  window: false, clearInterval: false, document: false,
+  self: false, setInterval: false */
+
+
+define(function () {
+    'use strict';
+
+    var isBrowser = typeof window !== "undefined" && window.document,
+        isPageLoaded = !isBrowser,
+        doc = isBrowser ? document : null,
+        readyCalls = [],
+        isTop, testDiv, scrollIntervalId;
+
+    function runCallbacks(callbacks) {
+        var i;
+        for (i = 0; i < callbacks.length; i++) {
+            callbacks[i](doc);
+        }
+    }
+
+    function callReady() {
+        var callbacks = readyCalls;
+
+        if (isPageLoaded) {
+            //Call the DOM ready callbacks
+            if (callbacks.length) {
+                readyCalls = [];
+                runCallbacks(callbacks);
+            }
+        }
+    }
+
+    /**
+     * Sets the page as loaded.
+     */
+    function pageLoaded() {
+        if (!isPageLoaded) {
+            isPageLoaded = true;
+            if (scrollIntervalId) {
+                clearInterval(scrollIntervalId);
+            }
+
+            callReady();
+        }
+    }
+
+    if (isBrowser) {
+        if (document.addEventListener) {
+            //Standards. Hooray! Assumption here that if standards based,
+            //it knows about DOMContentLoaded.
+            document.addEventListener("DOMContentLoaded", pageLoaded, false);
+            window.addEventListener("load", pageLoaded, false);
+        } else if (window.attachEvent) {
+            window.attachEvent("onload", pageLoaded);
+
+            testDiv = document.createElement('div');
+            try {
+                isTop = window.frameElement === null;
+            } catch(e) {}
+
+            //DOMContentLoaded approximation that uses a doScroll, as found by
+            //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/,
+            //but modified by other contributors, including jdalton
+            if (testDiv.doScroll && isTop && window.external) {
+                scrollIntervalId = setInterval(function () {
+                    try {
+                        testDiv.doScroll();
+                        pageLoaded();
+                    } catch (e) {}
+                }, 30);
+            }
+        }
+
+        //Check if document already complete, and if so, just trigger page load
+        //listeners. Latest webkit browsers also use "interactive", and
+        //will fire the onDOMContentLoaded before "interactive" but not after
+        //entering "interactive" or "complete". More details:
+        //http://dev.w3.org/html5/spec/the-end.html#the-end
+        //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded
+        if (document.readyState === "complete" ||
+            document.readyState === "interactive") {
+            pageLoaded();
+        }
+    }
+
+    /** START OF PUBLIC API **/
+
+    /**
+     * Registers a callback for DOM ready. If DOM is already ready, the
+     * callback is called immediately.
+     * @param {Function} callback
+     */
+    function domReady(callback) {
+        if (isPageLoaded) {
+            callback(doc);
+        } else {
+            readyCalls.push(callback);
+        }
+        return domReady;
+    }
+
+    domReady.version = '2.0.0';
+
+    /**
+     * Loader Plugin API method
+     */
+    domReady.load = function (name, req, onLoad, config) {
+        if (config.isBuild) {
+            onLoad(null);
+        } else {
+            domReady(onLoad);
+        }
+    };
+
+    /** END OF PUBLIC API **/
+
+    return domReady;
+});

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/main/resources/org/apache/tapestry5/underscore.jsw
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/underscore.jsw b/tapestry-core/src/main/resources/org/apache/tapestry5/underscore.jsw
deleted file mode 100644
index c9a287d..0000000
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/underscore.jsw
+++ /dev/null
@@ -1,4 +0,0 @@
-define(function (require, exports, module) {
-    @include("underscore_1_3_3.js")
-    module.exports = _.noConflict();
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/e4503a4b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
index 121b365..3b592e4 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
@@ -103,9 +103,12 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         check document, '''
 <?xml version="1.0"?>
 <html><body><p>Ready to be updated with scripts.</p><!--MODULE-MANAGER-INITIALIZATION--><script src="foo.js" type="text/javascript"/><script src="bar/baz.js" type="text/javascript"/><script type="text/javascript">Tapestry.onDOMLoaded(function() {
-pageInitialization();
+require(["core/pageinit"], function (pageinit) {
+  pageinit.initialize([["core/pageinit:evalJavaScript","pageInitialization();"]]);
+});
 });
-</script></body></html>'''
+</script></body></html>
+'''
 
         verify()
     }
@@ -203,8 +206,10 @@ pageInitialization();
         linker.updateDocument(document)
 
         check document, '''
-<html><body><p>Ready to be updated with scripts.</p><!--MODULE-MANAGER-INITIALIZATION--><script type="text/javascript">doSomething();
-doSomethingElse();
+<html><body><p>Ready to be updated with scripts.</p><!--MODULE-MANAGER-INITIALIZATION--><script type="text/javascript">require(["core/pageinit"], function (pageinit) {
+  pageinit.initialize([["core/pageinit:evalJavaScript","doSomething();"],
+  ["core/pageinit:evalJavaScript","doSomethingElse();"]]);
+});
 </script></body></html>
 '''
 
@@ -237,28 +242,6 @@ doSomethingElse();
     }
 
     @Test
-    void script_written_raw() throws Exception {
-        Document document = new Document()
-
-        document.newRootElement("html").element("body").element("p").text("Ready to be updated with scripts.")
-
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(mockModuleManager(), true, "1.2.3", true)
-
-        replay()
-
-        linker.addScript(InitializationPriority.IMMEDIATE, "for (var i = 0; i < 5; i++)  { doIt(i); }")
-
-        linker.updateDocument(document)
-
-        check document, '''
-<html><body><p>Ready to be updated with scripts.</p><!--MODULE-MANAGER-INITIALIZATION--><script type="text/javascript">for (var i = 0; i < 5; i++)  { doIt(i); }
-</script></body></html>
-'''
-
-        verify()
-    }
-
-    @Test
     void non_asset_script_link_disables_aggregation() throws Exception {
         Document document = new Document()