You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2025/08/09 11:39:00 UTC

(tapestry-5) branch master updated: TAP5-2810 Adding a Require.js-less mode (#54)

This is an automated email from the ASF dual-hosted git repository.

thiagohp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/master by this push:
     new 976caa7f6 TAP5-2810 Adding a Require.js-less mode (#54)
976caa7f6 is described below

commit 976caa7f6610920963a370083ea56676fb4a6612
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Sat Aug 9 08:38:56 2025 -0300

    TAP5-2810 Adding a Require.js-less mode (#54)
    
    TAP5-2810: Require.js-less mode
---
 5_10_RELEASE_NOTES.md                              |  18 +-
 build.gradle                                       |   9 +-
 .../beanvalidator/modules/BeanValidatorModule.java |  26 ++-
 .../corelib/base/AbstractComponentEventLink.java   |   8 +-
 .../tapestry5/corelib/base/AbstractField.java      |   6 +-
 .../tapestry5/corelib/components/AjaxFormLoop.java |  10 +-
 .../tapestry5/corelib/components/Alerts.java       |   6 +-
 .../tapestry5/corelib/components/DateField.java    |  10 +-
 .../tapestry5/corelib/components/DevTool.java      |  14 +-
 .../tapestry5/corelib/components/Errors.java       |   5 +-
 .../corelib/components/ExceptionDisplay.java       |   8 +-
 .../apache/tapestry5/corelib/components/Form.java  |   6 +-
 .../tapestry5/corelib/components/FormFragment.java |  20 +-
 .../tapestry5/corelib/components/Graphviz.java     |   6 +-
 .../apache/tapestry5/corelib/components/Grid.java  |   8 +-
 .../apache/tapestry5/corelib/components/Label.java |   6 +-
 .../tapestry5/corelib/components/LinkSubmit.java   |  24 ++-
 .../tapestry5/corelib/components/LocalDate.java    |  13 +-
 .../tapestry5/corelib/components/Palette.java      |  24 ++-
 .../corelib/components/ProgressiveDisplay.java     |  12 +-
 .../tapestry5/corelib/components/Select.java       |  49 ++---
 .../tapestry5/corelib/components/Submit.java       |  22 ++-
 .../tapestry5/corelib/components/TimeInterval.java |  13 +-
 .../apache/tapestry5/corelib/components/Tree.java  |  36 +++-
 .../apache/tapestry5/corelib/components/Zone.java  |  23 ++-
 .../tapestry5/corelib/mixins/Autocomplete.java     |  28 ++-
 .../apache/tapestry5/corelib/mixins/Confirm.java   |   8 +-
 .../tapestry5/corelib/mixins/TriggerFragment.java  |  10 +-
 .../tapestry5/corelib/mixins/ZoneRefresh.java      |   9 +-
 .../internal/alerts/AlertManagerImpl.java          |  20 +-
 .../internal/services/DocumentLinkerImpl.java      |  41 +++--
 .../internal/services/EsModuleInitsManager.java    |  93 +++++++++-
 .../services/PartialMarkupDocumentLinker.java      |  14 +-
 .../internal/services/ajax/BaseInitialization.java |  10 +
 .../services/ajax/EsModuleInitializationImpl.java  |  33 +++-
 .../internal/services/ajax/EsShimManagerImpl.java  |  66 +++++++
 .../services/ajax/JavaScriptSupportImpl.java       |  48 ++++-
 .../services/ajax/RequireJsModeHelper.java         |  41 +++++
 .../services/ajax/RequireJsModeHelperImpl.java     |  49 +++++
 .../services/javascript/EsModuleManagerImpl.java   | 165 ++++++++++-------
 .../services/javascript/EsShimDispatcher.java      | 141 +++++++++++++++
 .../translator/NumericTranslatorSupportImpl.java   |  20 +-
 .../apache/tapestry5/modules/JavaScriptModule.java | 201 +++++++++++++++++----
 .../apache/tapestry5/modules/TapestryModule.java   |   7 +-
 .../services/javascript/EsModuleManager.java       |  54 +++++-
 .../tapestry5/services/javascript/EsShim.java      | 189 +++++++++++++++++++
 .../services/javascript/EsShimManager.java         |  49 +++++
 .../javascript/ExtensibleJavaScriptStack.java      |  18 +-
 .../services/javascript/JavaScriptStack.java       |   8 +
 .../services/javascript/JavaScriptSupport.java     |   8 +
 .../services/javascript/StackExtension.java        |  10 +
 .../services/javascript/StackExtensionType.java    |   7 +
 .../tapestry5/validator/CheckboxValidator.java     |  10 +-
 .../java/org/apache/tapestry5/validator/Email.java |   9 +-
 .../java/org/apache/tapestry5/validator/Max.java   |   9 +-
 .../org/apache/tapestry5/validator/MaxLength.java  |   9 +-
 .../java/org/apache/tapestry5/validator/Min.java   |  10 +-
 .../org/apache/tapestry5/validator/MinLength.java  |   9 +-
 .../org/apache/tapestry5/validator/Regexp.java     |   9 +-
 .../org/apache/tapestry5/validator/Required.java   |   9 +-
 .../assets/es-modules/{t5 => }/underscore.js       |   0
 .../META-INF/assets/tapestry5/moment-2.15.1.js     |   2 +-
 .../META-INF/assets/tapestry5/underscore-esm.js    |   5 -
 .../resources/org/apache/tapestry5/core.properties |   2 +-
 .../t5/beanvalidator/beanvalidator-validation.ts   |   5 +-
 .../main/typescript/src/t5/core/autocomplete.ts    |   6 +-
 .../src/main/typescript/src/t5/core/fields.ts      |   2 +-
 .../main/typescript/src/t5/core/form-fragment.ts   |  11 +-
 .../typescript/src/t5/core/messages-es-module.ts   |   4 +
 .../src/main/typescript/src/t5/core/pageinit.ts    |  94 +++++++---
 .../src/main/typescript/src/t5/core/select.ts      |   4 +-
 .../typescript/src/t5/core/t53-compatibility.ts    |  56 ++++++
 .../src/main/typescript/src/t5/core/typeahead.ts   |   2 +-
 .../src/main/typescript/src/t5/core/validation.ts  |   6 +-
 .../src/main/typescript/src/t5/core/zone.ts        |   2 +-
 .../app1/ModuleConfigurationCallbackTests.groovy   |  13 +-
 .../services/DocumentLinkerImplTest.groovy         |  59 ++++--
 .../ajax/JavaScriptSupportAutofocusTests.groovy    |   2 +-
 .../services/ajax/JavaScriptSupportImplTest.groovy |  34 ++--
 .../tapestry5/integration/app1/App1TestCase.java   |   8 +
 .../tapestry5/integration/app1/AssetTests.java     |   8 +-
 .../tapestry5/integration/app1/EsModuleTests.java  |   1 -
 .../integration/app1/components/Border.java        |  14 +-
 .../integration/app1/pages/ClientConsoleDemo.java  |  12 +-
 .../integration/app1/pages/EsModuleDemo.java       |   2 +-
 .../pages/ModuleConfigurationCallbackDemo.java     |   2 +
 .../integration/app1/pages/ModuleInitDemo.java     |  10 +-
 .../app1/pages/MultiZoneUpdateDemo.java            |  12 +-
 .../integration/app1/pages/PaletteDemo.java        |   9 +-
 .../integration/app1/pages/PaletteGroupedDemo.java |  11 +-
 .../integration/app1/pages/PublishEventDemo.java   |  33 +++-
 .../app1/pages/ValidateInErrorEvent.java           |  12 +-
 .../integration/app1/services/AppModule.java       |   7 +-
 .../javascript/EsModuleManagerImplTest.java        |  39 ++--
 .../META-INF/assets/ExpressionInJsFunction.js      |  11 +-
 .../assets/es-modules/app/multi-zone-update.js     |   5 +
 .../META-INF/assets/es-modules/app/test-support.js |  14 ++
 .../assets/es-modules/client-console-demo.js       |   6 +
 .../META-INF/assets/es-modules/palette-demo.js     |  14 ++
 .../assets/es-modules/publish-event-demo.js        |  23 +++
 .../assets/es-modules/validate-in-error.js         |   8 +
 .../src/test/resources/META-INF/assets/zonedemo.js |  22 ++-
 .../META-INF/modules/client-console-demo.js        |   8 +-
 .../resources/META-INF/modules/palette-demo.js     |   5 -
 .../integration/app1/pages/nested/AssetDemo.js     |  16 +-
 .../apache/tapestry5/upload/components/Upload.java |  18 +-
 106 files changed, 2010 insertions(+), 432 deletions(-)

diff --git a/5_10_RELEASE_NOTES.md b/5_10_RELEASE_NOTES.md
index d62d92163..4a942b709 100644
--- a/5_10_RELEASE_NOTES.md
+++ b/5_10_RELEASE_NOTES.md
@@ -17,16 +17,24 @@ Scratch pad for changes destined for the 5.10.0 release notes page.
 * `org.apache.tapestry5.services.javascript.ImportPlacement`
 * `org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback`
 * `org.apache.tapestry5.services.javascript.EsModuleManager`
+* `org.apache.tapestry5.services.javascript.ESWrapper`
 
 # Non-backward-compatible changes (but that probably won't cause problems)
 
 # Non-backward-compatible changes
 
-When using Require.js and AMD modules, from Tapestry 5.10.0 on,
-the previously returned objects, functions or values are now
-the `default` property of the object received from `require()`.
-This is a consequence we couldn't avoid from the CoffeeScript
-to JavaScript to TypeScript conversion.
+* When using Require.js and AMD modules, from Tapestry 5.10.0 on,
+  the previously returned objects, functions or values are now
+  the `default` property of the object received from `require()`.
+  This is a consequence we couldn't avoid from the CoffeeScript
+  to JavaScript to TypeScript conversion.
+  
+
+# Notes about Require.js disabled mode
+* Underscore.js, jQuery and Require.js are not included in the default stack 
+  (i.e. the set of JavaScript files which are included in pages by default).
+  If you need to use Underscore.js or jQuery, they're automatically available for   
+  import as `underscore` and `jquery`, respectively.
 
 
 # Overall notes
diff --git a/build.gradle b/build.gradle
index a80b69637..bde9ee18d 100755
--- a/build.gradle
+++ b/build.gradle
@@ -538,12 +538,13 @@ task combinedJacocoReport(type:JacocoReport){
 }
 
 task continuousIntegration {
-    // tapestry-javadoc doesn't work with Java 8 anymore. That's why it's only added if != 8.
-    def dependants = [subprojects.build, // jQuery and Require.js enabled
-//            'tapestry-core:testWithJqueryAndRequireJsDisabled',
-//            'tapestry-core:testWithPrototypeAndRequireJsEnabled', 
+    def dependants = [
             'tapestry-core:testWithPrototypeAndRequireJsDisabled',
+            'tapestry-core:testWithJqueryAndRequireJsDisabled',
+            subprojects.build, // jQuery and Require.js enabled
+            'tapestry-core:testWithPrototypeAndRequireJsEnabled', 
             combinedJacocoReport]
+    // tapestry-javadoc doesn't work with Java 8 anymore. That's why it's only added if != 8.
     if (JavaVersion.current() != JavaVersion.VERSION_1_8) {
         dependants << aggregateJavadoc
     }
diff --git a/tapestry-beanvalidator/src/main/java/org/apache/tapestry5/beanvalidator/modules/BeanValidatorModule.java b/tapestry-beanvalidator/src/main/java/org/apache/tapestry5/beanvalidator/modules/BeanValidatorModule.java
index 2f93c789b..e068bb514 100644
--- a/tapestry-beanvalidator/src/main/java/org/apache/tapestry5/beanvalidator/modules/BeanValidatorModule.java
+++ b/tapestry-beanvalidator/src/main/java/org/apache/tapestry5/beanvalidator/modules/BeanValidatorModule.java
@@ -14,6 +14,7 @@
 package org.apache.tapestry5.beanvalidator.modules;
 
 import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.beanvalidator.*;
 import org.apache.tapestry5.commons.Configuration;
 import org.apache.tapestry5.commons.MappedConfiguration;
@@ -21,6 +22,7 @@ import org.apache.tapestry5.commons.OrderedConfiguration;
 import org.apache.tapestry5.internal.beanvalidator.*;
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.Local;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
 import org.apache.tapestry5.ioc.services.ThreadLocale;
 import org.apache.tapestry5.services.FieldValidatorDefaultSource;
@@ -92,14 +94,20 @@ public class BeanValidatorModule
     }
 
     public static void contributeClientConstraintDescriptorSource(final JavaScriptSupport javaScriptSupport,
-                                                                  final Configuration<ClientConstraintDescriptor> configuration)
+                                                                  final Configuration<ClientConstraintDescriptor> configuration,
+                                                                  final @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled)
     {
+        
+        final Runnable importJs = requireJsEnabled ?
+                () -> javaScriptSupport.require(MODULE_NAME) :
+                () -> javaScriptSupport.importEsModule(MODULE_NAME);
+        
         configuration.add(new BaseCCD(Max.class, "value")
         {
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require("t5/core/validation");
+                importJs.run();
                 writer.attributes(
                         "data-validate", true,
                         "data-validate-max", attributes.get("value"),
@@ -113,7 +121,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require("t5/core/validation");
+                importJs.run();
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
                         "data-validate-min", attributes.get("value"),
@@ -127,7 +135,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require("t5/core/validation");
+                importJs.run();
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
                         "data-optionality", "required",
@@ -140,7 +148,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require(MODULE_NAME);
+                importJs.run();
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
                         "data-optionality", "prohibited",
@@ -153,7 +161,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require(MODULE_NAME);
+                importJs.run();
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
                         "data-validate-regexp", attributes.get("regexp"),
@@ -166,7 +174,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require(MODULE_NAME);
+                importJs.run();
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
                         "data-range-message", message);
@@ -192,7 +200,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require("t5/core/validation");
+                importJs.run();
 
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
@@ -206,7 +214,7 @@ public class BeanValidatorModule
             @Override
             public void applyClientValidation(MarkupWriter writer, String message, Map<String, Object> attributes)
             {
-                javaScriptSupport.require("t5/core/validation");
+                importJs.run();
 
                 writer.attributes(
                         DataConstants.VALIDATION_ATTRIBUTE, true,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractComponentEventLink.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractComponentEventLink.java
index 64ac711af..4e27a3f6b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractComponentEventLink.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractComponentEventLink.java
@@ -20,8 +20,8 @@ import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.http.Link;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
 /**
  * Base class for link-generating components that are based on a component event request. Such events have an event
@@ -57,8 +57,8 @@ public abstract class AbstractComponentEventLink extends AbstractLink
     @Inject
     private Request request;
 
-    @Environmental
-    private JavaScriptSupport javaScriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     void beginRender(MarkupWriter writer)
     {
@@ -72,7 +72,7 @@ public abstract class AbstractComponentEventLink extends AbstractLink
 
         if (async)
         {
-            javaScriptSupport.require("t5/core/zone");
+            requireJsModeHelper.importModule("t5/core/zone");
             writer.attributes("data-async-trigger", true);
         }
     }
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java
index 654917abd..22e991cae 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractField.java
@@ -22,6 +22,7 @@ import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.internal.BeanValidationContext;
 import org.apache.tapestry5.internal.InternalComponentResources;
 import org.apache.tapestry5.internal.services.FormControlNameManager;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -154,6 +155,9 @@ public abstract class AbstractField implements Field
 
     @Environmental
     protected JavaScriptSupport javaScriptSupport;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Environmental
     protected ValidationTracker validationTracker;
@@ -296,7 +300,7 @@ public abstract class AbstractField implements Field
 
         if (error != null)
         {
-            javaScriptSupport.require("t5/core/fields").invoke("showValidationError").with(assignedClientId, error);
+            requireJsModeHelper.importModule("t5/core/fields").invoke("showValidationError").with(assignedClientId, error);
         }
     }
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
index 789ca01eb..72eaded6b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/AjaxFormLoop.java
@@ -20,6 +20,7 @@ import org.apache.tapestry5.corelib.internal.AjaxFormLoopContext;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.http.Link;
 import org.apache.tapestry5.internal.services.RequestConstants;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.ComponentDefaultProvider;
@@ -62,7 +63,6 @@ import java.util.Iterator;
  */
 @Events(
         {EventConstants.ADD_ROW, EventConstants.REMOVE_ROW})
-@Import(module = "t5/core/ajaxformloop")
 @SupportsInformalParameters
 public class AjaxFormLoop
 {
@@ -139,6 +139,9 @@ public class AjaxFormLoop
 
     @Inject
     private ComponentResources resources;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Environmental
     private FormSupport formSupport;
@@ -167,7 +170,7 @@ public class AjaxFormLoop
 
     @Inject
     private DeprecationWarning deprecationWarning;
-
+    
     void pageLoaded()
     {
         deprecationWarning.ignoredComponentParameters(resources, "show");
@@ -325,6 +328,9 @@ public class AjaxFormLoop
 
     void setupRender(MarkupWriter writer)
     {
+        
+        requireJsModeHelper.importModule("t5/core/ajaxformloop");
+
         pushContext();
 
         iterator = source == null ? Collections.EMPTY_LIST.iterator() : source.iterator();
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Alerts.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Alerts.java
index cf6e06400..6893fc912 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Alerts.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Alerts.java
@@ -20,6 +20,7 @@ import org.apache.tapestry5.annotations.*;
 import org.apache.tapestry5.corelib.base.BaseClientElement;
 import org.apache.tapestry5.http.Link;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.compatibility.DeprecationWarning;
@@ -63,6 +64,9 @@ public class Alerts extends BaseClientElement
 
     @Inject
     private Request request;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     void onPageLoaded()
     {
@@ -120,7 +124,7 @@ public class Alerts extends BaseClientElement
 
         for (Alert alert : storage.getAlerts())
         {
-            javaScriptSupport.require("t5/core/alert").with(alert.toJSON());
+            requireJsModeHelper.importModule("t5/core/alert").with(alert.toJSON());
         }
 
         storage.dismissNonPersistent();
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java
index c986beb19..569ebfdc3 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DateField.java
@@ -20,6 +20,7 @@ import org.apache.tapestry5.annotations.RequestParameter;
 import org.apache.tapestry5.commons.Messages;
 import org.apache.tapestry5.corelib.base.AbstractField;
 import org.apache.tapestry5.dom.Element;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -49,8 +50,7 @@ import java.util.Locale;
  * @see TextField
  */
 // TODO: More testing; see https://issues.apache.org/jira/browse/TAPESTRY-1844
-@Import(stylesheet = "${tapestry.datepicker}/css/datepicker.css",
-        module = "t5/core/datefield")
+@Import(stylesheet = "${tapestry.datepicker}/css/datepicker.css")
 @Events(EventConstants.VALIDATE)
 public class DateField extends AbstractField
 {
@@ -127,6 +127,9 @@ public class DateField extends AbstractField
 
     @Inject
     private DeprecationWarning deprecationWarning;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     @Symbol(SymbolConstants.LENIENT_DATE_FORMAT)
@@ -225,6 +228,9 @@ public class DateField extends AbstractField
 
     void beginRender(MarkupWriter writer)
     {
+        
+        requireJsModeHelper.importModule("t5/core/datefield");
+        
         String value = validationTracker.getInput(this);
 
         if (value == null)
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
index 3c6f9f614..9c0ad42cc 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/DevTool.java
@@ -14,6 +14,7 @@ package org.apache.tapestry5.corelib.components;
 
 import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.alerts.AlertManager;
 import org.apache.tapestry5.annotations.Component;
 import org.apache.tapestry5.annotations.Environmental;
@@ -94,6 +95,10 @@ public class DevTool
 
     @Inject
     private ReloadHelper reloadHelper;
+    
+    @Inject
+    @Symbol(SymbolConstants.REQUIRE_JS_ENABLED)
+    private boolean requireJsEnabled;
 
     public String getZoneElement()
     {
@@ -122,7 +127,14 @@ public class DevTool
     {
         if (enabled)
         {
-            javaScriptSupport.importStack("core").require("bootstrap/dropdown");
+            if (requireJsEnabled)
+            {
+                javaScriptSupport.importStack("core").require("bootstrap/dropdown");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("bootstrap/dropdown");
+            }
         }
 
         return enabled;
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
index 7438dfd76..71c2cb6f2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Errors.java
@@ -24,6 +24,7 @@ import org.apache.tapestry5.ValidationTracker;
 import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.commons.util.CollectionFactory;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.ComponentOverride;
@@ -79,7 +80,7 @@ public class Errors
     private String closeButtonCssClass;
     
     @Inject
-    private JavaScriptSupport javaScriptSupport;
+    private RequireJsModeHelper requireJsModeHelper;
 
     boolean beginRender(MarkupWriter writer)
     {
@@ -146,7 +147,7 @@ public class Errors
      * @see ComponentOverride
      */
     protected void setUpJavaScript() {
-        javaScriptSupport.require("bootstrap/alert");
+        requireJsModeHelper.importModule("bootstrap/alert");
     }
     
 }
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ExceptionDisplay.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ExceptionDisplay.java
index c1afa247a..b510d4375 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ExceptionDisplay.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ExceptionDisplay.java
@@ -17,6 +17,7 @@ package org.apache.tapestry5.corelib.components;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Primary;
 import org.apache.tapestry5.ioc.services.ExceptionAnalysis;
@@ -34,7 +35,7 @@ import java.util.List;
  * @tapestrydoc
  * @see org.apache.tapestry5.ioc.services.ExceptionAnalyzer
  */
-@Import(stylesheet = "ExceptionDisplay.css", module = "t5/core/exception-display")
+@Import(stylesheet = "ExceptionDisplay.css")
 public class ExceptionDisplay
 {
     /**
@@ -63,12 +64,17 @@ public class ExceptionDisplay
     @Inject
     @Primary
     private StackTraceElementAnalyzer frameAnalyzer;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     void setupRender()
     {
         ExceptionAnalysis analysis = analyzer.analyze(exception);
 
         stack = analysis.getExceptionInfos();
+        
+        requireJsModeHelper.importModule("t5/core/exception-display");
     }
 
     public Object getPropertyValue()
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
index 3d0fe289b..5d8b8222c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
@@ -33,6 +33,7 @@ import org.apache.tapestry5.internal.FormsRequirePostException;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.FormControlNameManager;
 import org.apache.tapestry5.internal.services.HeartbeatImpl;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.AutofocusValidationDecorator;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
@@ -238,6 +239,9 @@ public class Form implements ClientElement, FormValidationControl
     @Environmental
     private JavaScriptSupport javascriptSupport;
 
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
     @Inject
     private Request request;
 
@@ -394,7 +398,7 @@ public class Form implements ClientElement, FormValidationControl
 
         if (async)
         {
-            javascriptSupport.require("t5/core/zone");
+            requireJsModeHelper.importModule("t5/core/zone");
             writer.attributes("data-async-trigger", true);
         }
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
index 972fde06d..c4046648b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/FormFragment.java
@@ -12,10 +12,13 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.ComponentAction;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.HeartbeatDeferred;
-import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
@@ -23,6 +26,7 @@ import org.apache.tapestry5.corelib.internal.FormSupportAdapter;
 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
 import org.apache.tapestry5.corelib.mixins.TriggerFragment;
 import org.apache.tapestry5.dom.Element;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.ClientDataEncoder;
 import org.apache.tapestry5.services.Environment;
@@ -54,7 +58,6 @@ import org.slf4j.Logger;
  * @see Form
  */
 @SupportsInformalParameters
-@Import(module = "t5/core/form-fragment")
 public class FormFragment implements ClientElement
 {
     /**
@@ -122,9 +125,12 @@ public class FormFragment implements ClientElement
 
     @Inject
     private Environment environment;
-
+    
     @Environmental
     private JavaScriptSupport javascriptSupport;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     private ComponentResources resources;
@@ -143,7 +149,7 @@ public class FormFragment implements ClientElement
 
     @Inject
     private ClientDataEncoder clientDataEncoder;
-
+    
     String defaultElement()
     {
         return resources.getElementName("div");
@@ -170,6 +176,8 @@ public class FormFragment implements ClientElement
         }
 
         resources.renderInformalParameters(writer);
+        
+        requireJsModeHelper.importModule("t5/core/form-fragment");
 
         if (!visible)
         {
@@ -177,7 +185,7 @@ public class FormFragment implements ClientElement
 
             if (!alwaysSubmit)
             {
-                javascriptSupport.require("t5/core/form-fragment").invoke("hide").with(clientId);
+                requireJsModeHelper.importModule("t5/core/form-fragment").invoke("hide").with(clientId);
             }
         }
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java
index 0f34cd3ec..d735c60db 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Graphviz.java
@@ -21,6 +21,7 @@ import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.commons.Messages;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -58,6 +59,9 @@ public class Graphviz
     @Environmental
     private JavaScriptSupport javaScriptSupport;
     
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
     @Inject
     private AjaxResponseRenderer ajaxResponseRenderer;
     
@@ -88,7 +92,7 @@ public class Graphviz
         // TODO import https://cdn.jsdelivr.net/npm/@hpcc-js/wasm/dist/graphviz.js 
         // if Require.js is disabled @hpcc-js/wasm
         
-        javaScriptSupport.require("t5/core/graphviz").with(cachedValue, id, showDownloadLink);
+        requireJsModeHelper.importModule("t5/core/graphviz").with(cachedValue, id, showDownloadLink);
         
         if (showDownloadLink)
         {
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java
index 79e120b79..bb5e5c3ee 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Grid.java
@@ -22,12 +22,12 @@ import org.apache.tapestry5.corelib.data.GridPagerPosition;
 import org.apache.tapestry5.grid.*;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.internal.bindings.AbstractBinding;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.ComponentDefaultProvider;
 import org.apache.tapestry5.services.ComponentEventResultProcessor;
 import org.apache.tapestry5.services.FormSupport;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -234,8 +234,8 @@ public class Grid implements GridModel, ClientElement
     @Inject
     private BeanModelSource modelSource;
 
-    @Environmental
-    private JavaScriptSupport javaScriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Component(parameters =
             {"index=inherit:columnIndex", "lean=inherit:lean", "overrides=overrides", "zone=zone"})
@@ -591,7 +591,7 @@ public class Grid implements GridModel, ClientElement
 
         if (inPlace && zone == null)
         {
-            javaScriptSupport.require("t5/core/zone");
+            requireJsModeHelper.importModule("t5/core/zone");
 
             writer.element("div", "data-container-type", "zone");
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java
index c64853a38..529b027c5 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Label.java
@@ -19,6 +19,7 @@ import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -67,6 +68,9 @@ public class Label
 
     @Inject
     private JavaScriptSupport javaScriptSupport;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
@@ -120,7 +124,7 @@ public class Label
             // TAP5-2500
             String warningText = "The Label component " + resources.getCompleteId()
               + " is linked to a Field that failed to return a clientId. The 'for' attibute will not be rendered.";
-            javaScriptSupport.require("t5/core/console").invoke("warn").with(warningText);
+            requireJsModeHelper.importModule("t5/core/console").invoke("warn").with(warningText);
         }
         
         String id = clientId != null ? clientId : javaScriptSupport.allocateClientId(fieldId + "-label");
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LinkSubmit.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LinkSubmit.java
index 4ab44ed18..66141a074 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LinkSubmit.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LinkSubmit.java
@@ -14,10 +14,20 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.ComponentAction;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.TrackableComponentEventCallback;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.corelib.SubmitMode;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.json.JSONArray;
@@ -34,7 +44,6 @@ import org.apache.tapestry5.services.javascript.JavaScriptSupport;
  */
 @SupportsInformalParameters
 @Events(EventConstants.SELECTED + " by default, may be overridden")
-@Import(module = "t5/core/forms")
 public class LinkSubmit implements ClientElement
 {
     /**
@@ -94,13 +103,17 @@ public class LinkSubmit implements ClientElement
 
     @Inject
     private Request request;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings("rawtypes")
     @Environmental
     private TrackableComponentEventCallback eventCallback;
 
     private String clientId;
 
+    @SuppressWarnings("serial")
     private static class ProcessSubmission implements ComponentAction<LinkSubmit>
     {
         private final String clientId;
@@ -143,6 +156,9 @@ public class LinkSubmit implements ClientElement
     {
         if (!disabled)
         {
+            
+            requireJsModeHelper.importModule("t5/core/forms");
+            
             clientId = javascriptSupport.allocateClientId(resources);
 
             formSupport.store(this, new ProcessSubmission(clientId));
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LocalDate.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LocalDate.java
index 797bc5fab..f9f0f33b1 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LocalDate.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/LocalDate.java
@@ -12,17 +12,16 @@
 
 package org.apache.tapestry5.corelib.components;
 
+import java.util.Date;
+
 import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.MarkupWriter;
-import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.DateUtilities;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-
-import java.util.Date;
 
 /**
  * Used to present a date, formatted in the time zone of the client browser.
@@ -62,8 +61,8 @@ public class LocalDate
     @Inject
     ComponentResources resources;
 
-    @Environmental
-    JavaScriptSupport javaScriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     DateUtilities dateUtilities;
@@ -81,7 +80,7 @@ public class LocalDate
 
             writer.end();
 
-            javaScriptSupport.require("t5/core/localdate");
+            requireJsModeHelper.importModule("t5/core/localdate");
         }
 
         // Skip the body regardless.
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java
index 69313c37b..955a3840b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Palette.java
@@ -12,19 +12,31 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
+import java.util.Collection;
+
+import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.Binding;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.Block;
+import org.apache.tapestry5.ComponentParameterConstants;
+import org.apache.tapestry5.FieldValidator;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.Renderable;
+import org.apache.tapestry5.SelectModel;
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.ValidationException;
+import org.apache.tapestry5.ValueEncoder;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.corelib.base.AbstractField;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.SelectModelRenderer;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.services.compatibility.DeprecationWarning;
 
-import java.util.Collection;
-
 /**
  * Multiple selection component. Generates a UI consisting of two &lt;select&gt; elements configured for multiple
  * selection; the one on the left is the list of "available" elements, the one on the right is "selected". Elements can
@@ -160,7 +172,6 @@ public class Palette extends AbstractField
      * @since 5.2.0
      */
     @Parameter(defaultPrefix = BindingConstants.VALIDATE)
-    @SuppressWarnings("unchecked")
     private FieldValidator<Object> validate;
 
     @Inject
@@ -169,6 +180,9 @@ public class Palette extends AbstractField
 
     @Inject
     private DeprecationWarning deprecationWarning;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     void pageLoaded() {
         deprecationWarning.ignoredComponentParameters(resources, "select", "moveUp", "moveDown", "deselect");
@@ -247,7 +261,7 @@ public class Palette extends AbstractField
 
         // The client side just need to know the id of the selected (right column) select;
         // it can take it from there.
-        javaScriptSupport.require("t5/core/palette").with(clientId);
+        requireJsModeHelper.importModule("t5/core/palette").with(clientId);
     }
 
     /**
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ProgressiveDisplay.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ProgressiveDisplay.java
index 69da03e0f..726f4cd6e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ProgressiveDisplay.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/ProgressiveDisplay.java
@@ -18,6 +18,7 @@ import org.apache.tapestry5.annotations.Events;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.http.Link;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.compatibility.DeprecationWarning;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -63,9 +64,12 @@ public class ProgressiveDisplay
 
     @Inject
     private ComponentResources resources;
-
+    
     @Environmental
-    private JavaScriptSupport jsSupport;
+    private JavaScriptSupport javaScriptSupport;
+
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Environmental
     private TrackableComponentEventCallback eventCallback;
@@ -91,7 +95,7 @@ public class ProgressiveDisplay
 
     Block beginRender(MarkupWriter writer)
     {
-        String clientId = jsSupport.allocateClientId(resources);
+        String clientId = javaScriptSupport.allocateClientId(resources);
         String elementName = resources.getElementName("div");
 
         writer.element(elementName, "id", clientId, "data-container-type", "zone");
@@ -99,7 +103,7 @@ public class ProgressiveDisplay
 
         Link link = resources.createEventLink(EventConstants.ACTION, context);
 
-        jsSupport.require("t5/core/zone").invoke("deferredZoneUpdate").with(clientId, link.toURI());
+        requireJsModeHelper.importModule("t5/core/zone").invoke("deferredZoneUpdate").with(clientId, link.toURI());
 
         // Return the placeholder for the full content. That will render instead of the main body
         // of the component.
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java
index 1c15c0438..76e61e874 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Select.java
@@ -12,9 +12,29 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
-import org.apache.tapestry5.beanmodel.services.*;
+import java.util.List;
+
+import org.apache.tapestry5.Binding;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ComponentParameterConstants;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.FieldValidationSupport;
+import org.apache.tapestry5.FieldValidator;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.OptionGroupModel;
+import org.apache.tapestry5.OptionModel;
+import org.apache.tapestry5.SelectModel;
+import org.apache.tapestry5.SelectModelVisitor;
+import org.apache.tapestry5.ValidationException;
+import org.apache.tapestry5.ValidationTracker;
+import org.apache.tapestry5.ValueEncoder;
+import org.apache.tapestry5.annotations.BeforeRenderTemplate;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Mixin;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.RequestParameter;
 import org.apache.tapestry5.commons.Messages;
 import org.apache.tapestry5.commons.services.TypeCoercer;
 import org.apache.tapestry5.corelib.base.AbstractField;
@@ -26,6 +46,7 @@ import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.internal.AbstractEventContext;
 import org.apache.tapestry5.internal.InternalComponentResources;
 import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.CaptureResultCallback;
 import org.apache.tapestry5.internal.util.SelectModelRenderer;
 import org.apache.tapestry5.ioc.annotations.Inject;
@@ -34,12 +55,8 @@ import org.apache.tapestry5.services.FieldValidatorDefaultSource;
 import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.services.ValueEncoderFactory;
 import org.apache.tapestry5.services.ValueEncoderSource;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 import org.apache.tapestry5.util.EnumSelectModel;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * Select an item from a list of values, using an [X]HTML &lt;select&gt; element on the client side. Any validation
  * decorations will go around the entire &lt;select&gt; element.
@@ -180,12 +197,11 @@ public class Select extends AbstractField
     private FormSupport formSupport;
 
     @Inject
-    private JavaScriptSupport javascriptSupport;
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     private TypeCoercer typeCoercer;
 
-    @SuppressWarnings("unused")
     @Mixin
     private RenderDisabled renderDisabled;
 
@@ -196,8 +212,6 @@ public class Select extends AbstractField
         return TapestryInternalUtils.isEqual(clientValue, selectedClientValue);
     }
 
-    @SuppressWarnings(
-            {"unchecked"})
     @Override
     protected void processSubmission(String controlName)
     {
@@ -260,7 +274,7 @@ public class Select extends AbstractField
 
         if (this.zone != null)
         {
-            javaScriptSupport.require("t5/core/select");
+            requireJsModeHelper.importModule("t5/core/select");
 
             Link link = resources.createEventLink(CHANGE_EVENT, context);
 
@@ -391,17 +405,6 @@ public class Select extends AbstractField
         return false;
     }
 
-    private static <T> List<T> orEmpty(List<T> list)
-    {
-        if (list == null)
-        {
-            return Collections.emptyList();
-        }
-
-        return list;
-    }
-
-    @SuppressWarnings("unchecked")
     ValueEncoder defaultEncoder()
     {
         return defaultProvider.defaultValueEncoder("value", resources);
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Submit.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Submit.java
index b96db2ca4..9df3c97d8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Submit.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Submit.java
@@ -14,10 +14,21 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
+import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ClientElement;
+import org.apache.tapestry5.ComponentAction;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.TrackableComponentEventCallback;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.corelib.SubmitMode;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.Holder;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -36,7 +47,6 @@ import org.apache.tapestry5.services.javascript.JavaScriptSupport;
  */
 @SupportsInformalParameters
 @Events(EventConstants.SELECTED + " by default, may be overridden")
-@Import(module="t5/core/forms")
 public class Submit implements ClientElement
 {
     /**
@@ -120,6 +130,9 @@ public class Submit implements ClientElement
     @SuppressWarnings("unchecked")
     @Environmental
     private TrackableComponentEventCallback eventCallback;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     private String clientId;
 
@@ -150,6 +163,9 @@ public class Submit implements ClientElement
 
     void beginRender(MarkupWriter writer)
     {
+        
+        requireJsModeHelper.importModule("t5/core/forms");
+        
         clientId = javascriptSupport.allocateClientId(resources);
 
         String name = formSupport.allocateControlName(resources.getId());
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/TimeInterval.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/TimeInterval.java
index 01d3482da..45fec9e13 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/TimeInterval.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/TimeInterval.java
@@ -12,16 +12,16 @@
 
 package org.apache.tapestry5.corelib.components;
 
+import java.util.Date;
+
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.MarkupWriter;
-import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.annotations.SupportsInformalParameters;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.DateUtilities;
 
-import java.util.Date;
-
 /**
  * Used to present an interval value using Moment.js's from() or fromNow() functions. The interval
  * is determined in terms of a start and end date; either (but not both) may be omitted, in which
@@ -40,7 +40,6 @@ import java.util.Date;
  * @since 5.4
  */
 @SupportsInformalParameters
-@Import(module = "t5/core/time-interval")
 public class TimeInterval
 {
     /**
@@ -65,6 +64,9 @@ public class TimeInterval
 
     @Inject
     ComponentResources resources;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     DateUtilities dateUtilities;
@@ -76,6 +78,9 @@ public class TimeInterval
 
     boolean beginRender(MarkupWriter writer)
     {
+        
+        requireJsModeHelper.importModule("t5/core/time-interval");
+        
         writer.element(resources.getElementName("span"),
                 // Trigger the client-side logic:
                 "data-timeinterval", "true",
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java
index 7167574a4..a7a670236 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Tree.java
@@ -12,13 +12,25 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
+import java.util.List;
+
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.Block;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.annotations.RequestParameter;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Flow;
 import org.apache.tapestry5.func.Worker;
 import org.apache.tapestry5.http.Link;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.CaptureResultCallback;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONObject;
@@ -26,9 +38,11 @@ import org.apache.tapestry5.runtime.RenderCommand;
 import org.apache.tapestry5.runtime.RenderQueue;
 import org.apache.tapestry5.services.Heartbeat;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-import org.apache.tapestry5.tree.*;
-
-import java.util.List;
+import org.apache.tapestry5.tree.DefaultTreeExpansionModel;
+import org.apache.tapestry5.tree.TreeExpansionModel;
+import org.apache.tapestry5.tree.TreeModel;
+import org.apache.tapestry5.tree.TreeNode;
+import org.apache.tapestry5.tree.TreeSelectionModel;
 
 /**
  * A component used to render a recursive tree structure, with expandable/collapsable/selectable nodes. The data that is displayed
@@ -45,9 +59,8 @@ import java.util.List;
  * @since 5.3
  */
 @SuppressWarnings(
-        {"rawtypes", "unchecked", "unused"})
+        {"rawtypes", "unchecked"})
 @Events({EventConstants.NODE_SELECTED, EventConstants.NODE_UNSELECTED})
-@Import(module = "t5/core/tree")
 public class Tree
 {
     /**
@@ -143,6 +156,14 @@ public class Tree
 
     @Environmental
     private Heartbeat heartbeat;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
+    void beginRender()
+    {
+        requireJsModeHelper.importModule("t5/core/tree");
+    }
 
     /**
      * Renders a single node (which may be the last within its containing node).
@@ -160,6 +181,7 @@ public class Tree
     {
         return new RenderCommand()
         {
+            @SuppressWarnings("deprecation")
             public void render(MarkupWriter writer, RenderQueue queue)
             {
                 // Inform the component's container about what value is being rendered
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java
index b4e9b0282..276cf8d82 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java
@@ -12,14 +12,24 @@
 
 package org.apache.tapestry5.corelib.components;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
-import org.apache.tapestry5.beanmodel.services.*;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.Block;
+import org.apache.tapestry5.ClientBodyElement;
+import org.apache.tapestry5.ComponentAction;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.QueryParameterConstants;
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.annotations.BeginRender;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.SupportsInformalParameters;
 import org.apache.tapestry5.corelib.internal.ComponentActionSink;
 import org.apache.tapestry5.corelib.internal.FormSupportAdapter;
 import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.services.RequestConstants;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.json.JSONObject;
@@ -68,7 +78,6 @@ import org.slf4j.Logger;
  * @see FormFragment
  */
 @SupportsInformalParameters
-@Import(module = "t5/core/zone")
 public class Zone implements ClientBodyElement
 {
     /**
@@ -144,6 +153,9 @@ public class Zone implements ClientBodyElement
 
     @Inject
     private HiddenFieldLocationRules rules;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     private String clientId;
 
@@ -175,6 +187,9 @@ public class Zone implements ClientBodyElement
 
     void beginRender(MarkupWriter writer)
     {
+        
+        requireJsModeHelper.importModule("t5/core/zone");
+        
         clientId = resources.isBound("id") ? idParameter : javascriptSupport.allocateClientId(resources);
 
         Element e = writer.element(elementName,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
index 8d72839fd..097fe7951 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Autocomplete.java
@@ -12,11 +12,27 @@
 
 package org.apache.tapestry5.corelib.mixins;
 
-import org.apache.tapestry5.*;
-import org.apache.tapestry5.annotations.*;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ComponentEventCallback;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.EventContext;
+import org.apache.tapestry5.Field;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.annotations.Environmental;
+import org.apache.tapestry5.annotations.Events;
+import org.apache.tapestry5.annotations.Import;
+import org.apache.tapestry5.annotations.InjectContainer;
+import org.apache.tapestry5.annotations.MixinAfter;
+import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.RequestParameter;
 import org.apache.tapestry5.commons.services.TypeCoercer;
 import org.apache.tapestry5.http.Link;
 import org.apache.tapestry5.internal.AbstractEventContext;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.Holder;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONArray;
@@ -24,9 +40,6 @@ import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.compatibility.DeprecationWarning;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A mixin for a text field that allows for autocompletion of text fields. This is based on
  * Twttter <a href="http://twitter.github.io/typeahead.js/">typeahead.js</a> version 0.10.5.
@@ -61,6 +74,9 @@ public class Autocomplete
 
     @Environmental
     private JavaScriptSupport jsSupport;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     private TypeCoercer coercer;
@@ -129,7 +145,7 @@ public class Autocomplete
         JSONObject spec = new JSONObject("id", field.getClientId(),
                 "url", link.toString()).put("minChars", minChars).put("limit", maxSuggestions);
 
-        jsSupport.require("t5/core/autocomplete").with(spec);
+        requireJsModeHelper.importModule("t5/core/autocomplete").with(spec);
     }
 
     Object onAutocomplete(final EventContext context, @RequestParameter("t:input")
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Confirm.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Confirm.java
index f67d203e8..f41245338 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Confirm.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/Confirm.java
@@ -17,6 +17,8 @@ import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.MixinAfter;
 import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
 /**
@@ -47,8 +49,8 @@ public class Confirm
     @Parameter("false")
     private boolean disabled;
 
-    @Environmental
-    private JavaScriptSupport javaScriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
     
     /*
      * The CSS class for the ok button
@@ -72,7 +74,7 @@ public class Confirm
     {
         if (!disabled)
         {
-            javaScriptSupport.require("t5/core/confirm-click");
+            requireJsModeHelper.importModule("t5/core/confirm-click");
 
             writer.attributes("data-confirm-title", title,
                     "data-confirm-message", message,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/TriggerFragment.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/TriggerFragment.java
index 2d497b53e..6055316ee 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/TriggerFragment.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/TriggerFragment.java
@@ -17,13 +17,13 @@ package org.apache.tapestry5.corelib.mixins;
 import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.ClientElement;
 import org.apache.tapestry5.Field;
-import org.apache.tapestry5.annotations.Environmental;
 import org.apache.tapestry5.annotations.HeartbeatDeferred;
 import org.apache.tapestry5.annotations.InjectContainer;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.corelib.components.FormFragment;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONObject;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
 /**
  * A mixin that can be applied to a {@link org.apache.tapestry5.corelib.components.Checkbox} or
@@ -53,8 +53,8 @@ public class TriggerFragment
     @Parameter
     private boolean invert;
 
-    @Environmental
-    private JavaScriptSupport javascriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     @HeartbeatDeferred
     void beginRender()
@@ -73,6 +73,6 @@ public class TriggerFragment
             spec.put("invert", true);
         }
 
-        javascriptSupport.require("t5/core/form-fragment").invoke("linkTrigger").with(spec);
+        requireJsModeHelper.importModule("t5/core/form-fragment").invoke("linkTrigger").with(spec);
     }
 }
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/ZoneRefresh.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/ZoneRefresh.java
index 372618c5b..e2e02fb7f 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/ZoneRefresh.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/mixins/ZoneRefresh.java
@@ -19,6 +19,7 @@ import org.apache.tapestry5.annotations.InjectContainer;
 import org.apache.tapestry5.annotations.Parameter;
 import org.apache.tapestry5.corelib.components.Zone;
 import org.apache.tapestry5.http.Link;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.internal.util.CaptureResultCallback;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -57,17 +58,17 @@ public class ZoneRefresh
     private Zone zone;
 
     @Inject
-    private JavaScriptSupport javaScriptSupport;
+    private RequireJsModeHelper requireJsModeHelper;
 
     @Inject
     private ComponentResources resources;
 
     //For testing purpose
-    ZoneRefresh(Object[] context, ComponentResources resources, JavaScriptSupport javaScriptSupport, Zone zone)
+    ZoneRefresh(Object[] context, ComponentResources resources, RequireJsModeHelper requireJsModeHelper, Zone zone)
     {
         this.context = context;
         this.resources = resources;
-        this.javaScriptSupport = javaScriptSupport;
+        this.requireJsModeHelper = requireJsModeHelper;
         this.zone = zone;
     }
 
@@ -76,7 +77,7 @@ public class ZoneRefresh
     {
         Link link = resources.createEventLink("zoneRefresh", context);
 
-        javaScriptSupport.require("t5/core/zone-refresh").with(zone.getClientId(), period, link.toString());
+        requireJsModeHelper.importModule("t5/core/zone-refresh").with(zone.getClientId(), period, link.toString());
     }
 
     Object onZoneRefresh(EventContext eventContext)
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/alerts/AlertManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/alerts/AlertManagerImpl.java
index 63ace7cf5..29f43d597 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/alerts/AlertManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/alerts/AlertManagerImpl.java
@@ -14,10 +14,13 @@
 
 package org.apache.tapestry5.internal.alerts;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.alerts.*;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.PerThreadValue;
 import org.apache.tapestry5.ioc.services.PerthreadManager;
+import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.ApplicationStateManager;
 import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
 import org.apache.tapestry5.services.ajax.JavaScriptCallback;
@@ -32,12 +35,17 @@ public class AlertManagerImpl implements AlertManager
     private final AjaxResponseRenderer ajaxResponseRenderer;
 
     private final PerThreadValue<Boolean> needAlertStorageCleanup;
+    
+    private final boolean requireJsEnabled;
 
-    public AlertManagerImpl(ApplicationStateManager asm, Request request, AjaxResponseRenderer ajaxResponseRenderer, PerthreadManager perThreadManager)
+    public AlertManagerImpl(ApplicationStateManager asm, Request request, 
+            AjaxResponseRenderer ajaxResponseRenderer, PerthreadManager perThreadManager,
+            @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled)
     {
         this.asm = asm;
         this.request = request;
         this.ajaxResponseRenderer = ajaxResponseRenderer;
+        this.requireJsEnabled = requireJsEnabled;
 
         needAlertStorageCleanup = perThreadManager.createValue();
     }
@@ -89,7 +97,15 @@ public class AlertManagerImpl implements AlertManager
         {
             public void run(JavaScriptSupport javascriptSupport)
             {
-                javascriptSupport.require("t5/core/alert").with(alert.toJSON());
+                final JSONObject json = alert.toJSON();
+                if (requireJsEnabled)
+                {
+                    javascriptSupport.require("t5/core/alert").with(json);
+                }
+                else
+                {
+                    javascriptSupport.importEsModule("t5/core/alert").with(json);
+                }
             }
         });
 
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 8814e7faf..8c17f5ca2 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
@@ -24,6 +24,7 @@ import org.apache.tapestry5.services.javascript.ModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.ModuleManager;
 import org.apache.tapestry5.services.javascript.StylesheetLink;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -38,7 +39,7 @@ public class DocumentLinkerImpl implements DocumentLinker
 
     private final ModuleInitsManager initsManager = new ModuleInitsManager();
     
-    private final EsModuleInitsManager esModulesinitsManager = new EsModuleInitsManager();
+    private final EsModuleInitsManager esModulesInitsManager = new EsModuleInitsManager();
 
     private final List<ModuleConfigurationCallback> moduleConfigurationCallbacks = CollectionFactory.newList();
     
@@ -53,6 +54,8 @@ public class DocumentLinkerImpl implements DocumentLinker
     private final boolean omitGeneratorMetaTag, enablePageloadingMask;
 
     private final String tapestryBanner;
+    
+    private final boolean requireJsEnabled;
 
     // Initially false; set to true when a scriptURL or any kind of initialization is added.
     private boolean hasScriptsOrInitializations;
@@ -66,12 +69,14 @@ public class DocumentLinkerImpl implements DocumentLinker
      * @param tapestryVersion
      */
     public DocumentLinkerImpl(ModuleManager moduleManager, EsModuleManager esModuleManager,
-            boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion)
+            boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion,
+            boolean requireJsEnabled)
     {
         this.moduleManager = moduleManager;
         this.esModuleManager = esModuleManager;
         this.omitGeneratorMetaTag = omitGeneratorMetaTag;
         this.enablePageloadingMask = enablePageloadingMask;
+        this.requireJsEnabled = requireJsEnabled;
 
         tapestryBanner = "Apache Tapestry Framework (version " + tapestryVersion + ')';
     }
@@ -149,15 +154,15 @@ public class DocumentLinkerImpl implements DocumentLinker
 
             addElementBefore(head, existingMeta, "meta", "name", "generator", "content", tapestryBanner);
         }
-
-        addScriptElements(root);
         
-        final List<EsModuleInitialization> esModuleInits = esModulesinitsManager.getInits();
-        if (isHtmlRoot && !esModuleInits.isEmpty())
+        final List<EsModuleInitialization> imports = esModulesInitsManager.getImports();
+        if (isHtmlRoot && !imports.isEmpty())
         {
             esModuleManager.writeImportMap(root.find("head"), esModuleConfigurationCallbacks);
-            esModuleManager.writeImports(root, esModuleInits);
+            esModuleManager.writeImports(root, imports);
         }
+
+        addScriptElements(root);
         
     }
 
@@ -252,8 +257,11 @@ public class DocumentLinkerImpl implements DocumentLinker
 
             script.moveToTop(body);
         }
-
-        moduleManager.writeConfiguration(body, moduleConfigurationCallbacks);
+        
+        if (requireJsEnabled)
+        {
+            moduleManager.writeConfiguration(body, moduleConfigurationCallbacks);
+        }
 
         // Write the core libraries, which includes RequireJS:
 
@@ -265,8 +273,17 @@ public class DocumentLinkerImpl implements DocumentLinker
         }
 
         // Write the initialization at this point.
-
-        moduleManager.writeInitialization(body, libraryURLs, initsManager.getSortedInits());
+        if (requireJsEnabled)
+        {
+            moduleManager.writeInitialization(body, libraryURLs, initsManager.getSortedInits());
+            
+            // Libraries were already added in the line above.
+            esModuleManager.writeInitialization(body, Collections.emptyList(), esModulesInitsManager.getInitsAsJsonArrayList());
+        }
+        else
+        {
+            esModuleManager.writeInitialization(body, libraryURLs, esModulesInitsManager.getInitsAsJsonArrayList());
+        }
     }
 
     private static Element createTemporaryContainer(Element headElement, String existingElementName, String newElementName)
@@ -337,7 +354,7 @@ public class DocumentLinkerImpl implements DocumentLinker
     public void addEsModuleInitialization(EsModuleInitialization initialization) 
     {
         assert initialization != null;
-        esModulesinitsManager.add(initialization);
+        esModulesInitsManager.add(initialization);
         hasScriptsOrInitializations = true;
     }
     
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EsModuleInitsManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EsModuleInitsManager.java
index e3fc767fe..c03bd6be2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EsModuleInitsManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EsModuleInitsManager.java
@@ -25,37 +25,116 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
 import org.apache.tapestry5.commons.util.CollectionFactory;
 import org.apache.tapestry5.internal.services.ajax.EsModuleInitializationImpl;
+import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.services.javascript.EsModuleInitialization;
 
 public class EsModuleInitsManager
 {
     private final Set<String> modules = CollectionFactory.newSet();
     
+    private final List<EsModuleInitialization> imports = CollectionFactory.newList();
+    
     private final List<EsModuleInitialization> initializations = CollectionFactory.newList();
-
+    
     public void add(EsModuleInitialization initialization)
     {
         assert initialization != null;
 
-        // We ignore a module being added again.
-        final String moduleName = ((EsModuleInitializationImpl) initialization).getModuleId();
-        if (!modules.contains(moduleName))
+        // We avoid having the same module being imported more than twice.
+        // Also notice non-pure inits (i.e. ones having a function name or 
+        // both) are added both to the imports, so they can have
+        // <script type="module"> added for them, and to initializations,
+        // the function call will be made by t5/core/pageinit.
+        final EsModuleInitializationImpl init = (EsModuleInitializationImpl) initialization;
+        final String moduleName = init.getModuleName();
+        final boolean alreadyPresent = modules.contains(moduleName);
+        if (!init.isPure())
         {
             initializations.add(initialization);
+        }
+        if (!alreadyPresent)
+        {
+            imports.add(initialization);
             modules.add(moduleName);
         }
     }
 
     /**
-     * Returns all previously added inits.
+     * Returns all imports.
+     */
+    public List<EsModuleInitialization> getImports()
+    {
+        return imports;
+    }
+    
+    /**
+     * Returns all inits (pure imports and initializations) as a JSONArray list.
      */
-    public List<EsModuleInitialization> getInits()
+    public List<JSONArray> getAllInitsAsJsonArrayList()
     {
-        return initializations;
+        List<JSONArray> list;
+        if (!imports.isEmpty() || !initializations.isEmpty())
+        {
+            list = new ArrayList<>(imports.size() + initializations.size());
+            list.addAll(toJSONArray(imports));
+            list.addAll(toJSONArray(initializations));
+        }
+        else
+        {
+            list = Collections.emptyList();
+        }
+        return list;
     }
+    
+    /**
+     * Returns all previously added inits as a JSONArray list.
+     */
+    public List<JSONArray> getInitsAsJsonArrayList()
+    {
+        return toJSONArray(initializations);
+    }
+
+    private List<JSONArray> toJSONArray(final List<EsModuleInitialization> inits) {
+        List<JSONArray> list;
+        if (!inits.isEmpty()) 
+        {
+            list = new ArrayList<>(inits.size());
+            for (EsModuleInitialization init : inits) 
+            {
+                final EsModuleInitializationImpl initImpl = (EsModuleInitializationImpl) init;
+                final JSONArray arguments = initImpl.getArguments();
+                final JSONArray initArray = new JSONArray();
+                
+                String moduleName = initImpl.getModuleName();
+                if (initImpl.getFunctionName() != null)
+                {
+                    moduleName = moduleName + ":" + initImpl.getFunctionName();
+                }
+                
+                initArray.add(moduleName);
+                
+                if (arguments != null)
+                {
+                    initArray.addAll(arguments);
+                }
+                
+                list.add(initArray);
+                
+            }
+        }
+        else
+        {
+            list = Collections.emptyList();
+        }
+        return list;
+    }
+
 }
+
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
index 9ccf81629..c8fda5397 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PartialMarkupDocumentLinker.java
@@ -24,6 +24,7 @@ import org.apache.tapestry5.services.javascript.InitializationPriority;
 import org.apache.tapestry5.services.javascript.ModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.StylesheetLink;
 
+import java.util.ArrayList;
 import java.util.List;
 
 public class PartialMarkupDocumentLinker implements DocumentLinker
@@ -34,6 +35,8 @@ public class PartialMarkupDocumentLinker implements DocumentLinker
 
     private final ModuleInitsManager initsManager = new ModuleInitsManager();
     
+    private final EsModuleInitsManager esModulesinitsManager = new EsModuleInitsManager();
+    
     public void addCoreLibrary(String libraryURL)
     {
         notImplemented("addCoreLibrary");
@@ -84,7 +87,7 @@ public class PartialMarkupDocumentLinker implements DocumentLinker
     @Override
     public void addEsModuleInitialization(EsModuleInitialization initialization) 
     {
-        notImplemented("addEsModuleInitialization");
+        esModulesinitsManager.add(initialization);
     }
 
     /**
@@ -106,10 +109,15 @@ public class PartialMarkupDocumentLinker implements DocumentLinker
         }
 
         List<?> inits = initsManager.getSortedInits();
+        final List<?> esModuleInits = esModulesinitsManager.getAllInitsAsJsonArrayList();
 
-        if (inits.size() > 0)
+        if (inits.size() > 0 || esModuleInits.size() > 0)
         {
-            reply.in(InternalConstants.PARTIAL_KEY).put("inits", JSONArray.from(inits));
+            List<Object> allInits = new ArrayList<>(inits.size() + esModuleInits.size());
+            allInits.addAll(inits);
+            allInits.addAll(esModuleInits);
+            reply.in(InternalConstants.PARTIAL_KEY).put("inits", JSONArray.from(allInits));
         }
+        
     }
 }
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java
index 589a8850a..7714dc461 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java
@@ -1,3 +1,13 @@
+// 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
 package org.apache.tapestry5.internal.services.ajax;
 
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java
index 3b3bfeb4a..663b422f2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java
@@ -1,9 +1,20 @@
+// 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
 package org.apache.tapestry5.internal.services.ajax;
 
 import java.util.Collections;
 import java.util.Map;
 
 import org.apache.tapestry5.commons.util.CollectionFactory;
+import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.services.javascript.EsModuleInitialization;
 import org.apache.tapestry5.services.javascript.ImportPlacement;
 
@@ -12,9 +23,9 @@ public class EsModuleInitializationImpl extends BaseInitialization<EsModuleIniti
     
     private Map<String, String> attributes;
     private ImportPlacement placement = ImportPlacement.BODY_BOTTOM;
-    private Object[] arguments;
+    private JSONArray arguments;
     
-    EsModuleInitializationImpl(String moduleName) 
+    public EsModuleInitializationImpl(String moduleName) 
     {
         super(moduleName);
     }
@@ -35,7 +46,7 @@ public class EsModuleInitializationImpl extends BaseInitialization<EsModuleIniti
         return null;
     }
 
-    public String getModuleId() {
+    public String getModuleName() {
         return moduleName;
     }
 
@@ -56,12 +67,24 @@ public class EsModuleInitializationImpl extends BaseInitialization<EsModuleIniti
     @Override
     public void with(Object... arguments) 
     {
-        this.arguments = arguments;
+        assert arguments != null;
+        this.arguments = new JSONArray(arguments);
     }
     
-    public Object[] getArguments() 
+    public JSONArray getArguments() 
     {
         return arguments;
     }
     
+    public boolean isPure() 
+    {
+        return functionName == null && arguments == null;
+    }
+
+    @Override
+    public String toString() 
+    {
+        return "ESInit[moduleName=" + moduleName + ", functionName=" + functionName + ", arguments=" + arguments + "]";
+    }
+    
 }
\ No newline at end of file
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java
new file mode 100644
index 000000000..f8a8c0dd8
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java
@@ -0,0 +1,66 @@
+// 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.ajax;
+
+import java.util.Map;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.commons.Resource;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.services.PathConstructor;
+import org.apache.tapestry5.services.javascript.EsShimManager;
+
+public class EsShimManagerImpl implements EsShimManager
+{
+    
+    private static final String ES_SUBPATH = "es-shims";
+    
+    private final Map<String, Resource> shims;
+    
+    private final PathConstructor pathConstructor;
+    
+    private final String assetPrefix;
+    
+    public EsShimManagerImpl(Map<String, Resource> shims,
+            PathConstructor pathConstructor,
+            @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
+            String assetPrefix) 
+    {
+        super();
+        this.shims = shims;
+        this.assetPrefix = assetPrefix;
+        this.pathConstructor = pathConstructor;
+    }
+
+    /**
+     * Returns the shims as a (module name, module resource) map.
+     * @return a {@code Map<String, Resource>}
+     */
+    @Override
+    public Map<String, Resource> getShims()
+    {
+        return shims;
+    }
+    
+    @Override
+    public String getRequestPrefix(boolean compress)
+    {
+        return pathConstructor.constructDispatchPath(assetPrefix + "/" + (compress ? ES_SUBPATH + ".gz" : ES_SUBPATH) + "/");
+    }
+
+    @Override
+    public String getUrl(String moduleName) {
+        return getRequestPrefix(true) + moduleName + ".js";
+    }
+    
+}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
index 5cf4dbd3b..f9f5e2b23 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java
@@ -22,16 +22,19 @@ import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.BooleanHook;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.FieldFocusPriority;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.util.CollectionFactory;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Worker;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.DocumentLinker;
 import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.services.javascript.AbstractInitialization;
 import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.EsModuleInitialization;
 import org.apache.tapestry5.services.javascript.Initialization;
@@ -44,6 +47,8 @@ import org.apache.tapestry5.services.javascript.StylesheetLink;
 
 public class JavaScriptSupportImpl implements JavaScriptSupport
 {
+    private static final String PAGEINIT_MODULE_NAME = "t5/core/pageinit";
+
     private final IdAllocator idAllocator;
 
     private final DocumentLinker linker;
@@ -77,11 +82,14 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     private String focusFieldId;
 
     private Map<String, String> libraryURLToStackName, moduleNameToStackName;
+    
+    private final boolean requireJsEnabled;
 
     public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
-                                 JavaScriptStackPathConstructor stackPathConstructor, BooleanHook suppressCoreStylesheetsHook)
+                                 JavaScriptStackPathConstructor stackPathConstructor, BooleanHook suppressCoreStylesheetsHook,
+                                 @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled)
     {
-        this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false, suppressCoreStylesheetsHook);
+        this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false, suppressCoreStylesheetsHook, requireJsEnabled);
     }
 
     /**
@@ -105,7 +113,8 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
      */
     public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
                                  JavaScriptStackPathConstructor stackPathConstructor, IdAllocator idAllocator, boolean partialMode,
-                                 BooleanHook suppressCoreStylesheetsHook)
+                                 BooleanHook suppressCoreStylesheetsHook,
+                                 @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled)
     {
         this.linker = linker;
         this.idAllocator = idAllocator;
@@ -113,6 +122,7 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
         this.stackPathConstructor = stackPathConstructor;
         this.partialMode = partialMode;
         this.suppressCoreStylesheetsHook = suppressCoreStylesheetsHook;
+        this.requireJsEnabled = requireJsEnabled;
 
         // In partial mode, assume that the infrastructure stack is already present
         // (from the original page render).
@@ -126,10 +136,21 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
     public void commit()
     {
         
+        final String pageInitModuleName = PAGEINIT_MODULE_NAME;
         // TODO make no Require.js version of this
         if (focusFieldId != null)
         {
-            require("t5/core/pageinit").invoke("focus").with(focusFieldId);
+            final AbstractInitialization<?> initialization;
+            
+            if (requireJsEnabled)
+            {
+                initialization = require(pageInitModuleName);
+            }
+            else
+            {
+                initialization = importEsModule(pageInitModuleName);
+            }
+            initialization.invoke("focus").with(focusFieldId);
         }
 
         F.flow(stylesheetLinks).each(new Worker<StylesheetLink>()
@@ -217,7 +238,16 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
 
         if (partialMode)
         {
-            require("t5/core/pageinit").invoke("evalJavaScript").with(newScript);
+            final AbstractInitialization<?> initialization;
+            if (requireJsEnabled)
+            {
+                initialization = require(PAGEINIT_MODULE_NAME);
+            }
+            else
+            {
+                initialization = importEsModule(PAGEINIT_MODULE_NAME);
+            }
+            initialization.invoke("evalJavaScript").with(newScript);
         } else
         {
             linker.addScript(priority, newScript);
@@ -450,8 +480,6 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
         
         assert InternalUtils.isNonBlank(moduleName);
         
-        // TODO import core libraries (jQuery, Prototype/Scriptaculous/Underscore)
-
         EsModuleInitialization init = new EsModuleInitializationImpl(moduleName);
         if (!esModulesImported.contains(moduleName))
         {
@@ -467,4 +495,10 @@ public class JavaScriptSupportImpl implements JavaScriptSupport
         linker.addEsModuleConfigurationCallback(callback);
     }
 
+    @Override
+    public boolean isRequireJsEnabled() 
+    {
+        return requireJsEnabled;
+    }
+
 }
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelper.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelper.java
new file mode 100644
index 000000000..e752c8b20
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelper.java
@@ -0,0 +1,41 @@
+// 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.ajax;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.services.javascript.AbstractInitialization;
+import org.apache.tapestry5.services.javascript.JavaScriptSupport;
+
+/**
+ * Internal service to encapsulate the logic of calling 
+ * {@linkplain JavaScriptSupport#require(String)} or 
+ * {@linkplain JavaScriptSupport#importEsModule(String)}
+ * depending on the value of {@linkplain SymbolConstants#REQUIRE_JS_ENABLED}.
+ * Should only be used inside Tapestry itself. In user projects, 
+ * explicit calls to the {@linkplain JavaScriptSupport} method should be used
+ * instead, since they'll be using just one mode (i.e. Require.js enabled or not).
+ * @since 5.10.0
+ */
+public interface RequireJsModeHelper
+{
+
+    /**
+     * Returns either {@linkplain JavaScriptSupport#require(String)} or 
+     * {@linkplain JavaScriptSupport#importEsModule(String)} depending on the
+     * value of the {@linkplain SymbolConstants#REQUIRE_JS_ENABLED} configuration
+     * symbol value.
+
+     * @param moduleName the module name or id.
+     * @return an {@linkplain AbstractInitialization} instance.
+     */
+    AbstractInitialization<?> importModule(String moduleName);
+}
\ No newline at end of file
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelperImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelperImpl.java
new file mode 100644
index 000000000..d3406b4b7
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/RequireJsModeHelperImpl.java
@@ -0,0 +1,49 @@
+// 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.ajax;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.services.javascript.AbstractInitialization;
+import org.apache.tapestry5.services.javascript.JavaScriptSupport;
+
+public class RequireJsModeHelperImpl implements RequireJsModeHelper
+{
+    
+    private final JavaScriptSupport javaScriptSupport;
+    
+    private final boolean requireJsEnabled;
+
+    public RequireJsModeHelperImpl(
+            final JavaScriptSupport javaScriptSupport, 
+            @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) final boolean requireJsEnabled) 
+    {
+        this.javaScriptSupport = javaScriptSupport;
+        this.requireJsEnabled = requireJsEnabled;
+    }
+
+    /**
+     * Returns either {@linkplain JavaScriptSupport#require(String)} or 
+     * {@linkplain JavaScriptSupport#importEsModule(String)} depending on the
+     * value of the {@linkplain SymbolConstants#REQUIRE_JS_ENABLED} configuration
+     * symbol value.
+
+     * @param moduleName the module name or id.
+     * @return an {@linkplain AbstractInitialization} instance.
+     */
+    public AbstractInitialization<?> importModule(String moduleName)
+    {
+        return requireJsEnabled ? 
+                javaScriptSupport.require(moduleName) :
+                javaScriptSupport.importEsModule(moduleName);
+    }
+}
\ No newline at end of file
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java
index f65cc7a49..6c5f17a47 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java
@@ -17,6 +17,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.SymbolConstants;
@@ -24,28 +25,28 @@ import org.apache.tapestry5.commons.util.AvailableValues;
 import org.apache.tapestry5.commons.util.CollectionFactory;
 import org.apache.tapestry5.commons.util.UnknownValueException;
 import org.apache.tapestry5.dom.Element;
-import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.ajax.EsModuleInitializationImpl;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.ClasspathMatcher;
 import org.apache.tapestry5.ioc.services.ClasspathScanner;
+import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.json.JSONCollection;
 import org.apache.tapestry5.json.JSONLiteral;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.AssetSource;
+import org.apache.tapestry5.services.Core;
 import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.EsModuleInitialization;
 import org.apache.tapestry5.services.javascript.EsModuleManager;
 import org.apache.tapestry5.services.javascript.ImportPlacement;
+import org.apache.tapestry5.services.javascript.JavaScriptStack;
 
 public class EsModuleManagerImpl implements EsModuleManager
 {
 
-    private static final String GENERIC_IMPORTED_VARIABLE = "m";
-
     /**
      * Name of the JSON object property containing the imports in an import map.
      */
@@ -54,12 +55,14 @@ public class EsModuleManagerImpl implements EsModuleManager
     private static final String CLASSPATH_ROOT = "META-INF/assets/es-modules/";
 
     private final boolean compactJSON;
-
-    private final boolean productionMode;
+    
+    private final boolean requireJsEnabled;
 
     private final Set<String> extensions;
     
     private final AssetSource assetSource;
+    
+    private final List<EsModuleInitialization> coreStackInits;
 
     // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null.
     private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
@@ -73,23 +76,35 @@ public class EsModuleManagerImpl implements EsModuleManager
     private final List<EsModuleConfigurationCallback> baseCallbacks;
     
     private final List<EsModuleConfigurationCallback> globalPerRequestCallbacks;
-
+    
+    private final String infraProvider;
+    
     public EsModuleManagerImpl(
                              List<EsModuleManagerContribution> contributions,
                              AssetSource assetSource,
                              StreamableResourceSource streamableResourceSource,
                              @Symbol(SymbolConstants.COMPACT_JSON)
                              boolean compactJSON,
-                             @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
-                             boolean productionMode,
+                             @Symbol(SymbolConstants.REQUIRE_JS_ENABLED)
+                             boolean requireJsEnabled,
+                             @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
+                             String infraProvider,                             
                              ClasspathScanner classpathScanner,
-                             ResourceChangeTracker resourceChangeTracker)
+                             ResourceChangeTracker resourceChangeTracker,
+                             @Core JavaScriptStack javaScriptStack)
     {
         this.compactJSON = compactJSON;
+        this.requireJsEnabled = requireJsEnabled;
         this.assetSource = assetSource;
         this.classpathScanner = classpathScanner;
-        this.productionMode = productionMode;
         this.resourceChangeTracker = resourceChangeTracker;
+        this.infraProvider = infraProvider;
+        
+        final List<String> coreStackEsModules = javaScriptStack.getEsModules();
+        
+        coreStackInits = coreStackEsModules.stream()
+                .map(i -> new EsModuleInitializationImpl(i))
+                .collect(Collectors.toList());
         
         baseCallbacks = new ArrayList<>();
         globalPerRequestCallbacks = new ArrayList<>();
@@ -151,12 +166,22 @@ public class EsModuleManagerImpl implements EsModuleManager
             final Set<String> scan = classpathScanner.scan(CLASSPATH_ROOT, matcher);
             for (String file : scan) 
             {
-                String id = file.replace(CLASSPATH_ROOT, "");
-                id = id.substring(0, id.lastIndexOf('.'));
+                String moduleName = file.replace(CLASSPATH_ROOT, "");
+                moduleName = moduleName.substring(0, moduleName.lastIndexOf('.'));
+                
+                if (moduleName.startsWith("t5/core/t5-core-dom-"))
+                {
+                    // They're treated specially.
+                    continue;
+                }
+                else if (moduleName.equals("t5/core/dom"))
+                {
+                    file = file.replace("t5/core/dom", "t5/core/t5-core-dom-" + infraProvider);
+                }
                             
                 final Asset asset = assetSource.getClasspathAsset(file);
                 resourceChangeTracker.trackResource(asset.getResource());
-                imports.put(id, asset.toClientURL());
+                imports.put(moduleName, asset.toClientURL());
             }
         } catch (IOException e) 
         {
@@ -189,15 +214,18 @@ public class EsModuleManagerImpl implements EsModuleManager
         Element head = null;
         ImportPlacement placement;
         EsModuleInitializationImpl init;
-        String functionName;
-        Object[] arguments;
         
-        for (EsModuleInitialization i : inits) 
+        List<EsModuleInitialization> allInits = new ArrayList<>(coreStackInits.size() + inits.size());
+        allInits.addAll(coreStackInits);
+        allInits.addAll(inits);
+        
+        for (EsModuleInitialization i : allInits) 
         {
             
             init = (EsModuleInitializationImpl) i;
-            final String moduleId = init.getModuleId();
-            // Making sure the user doesn't shoot heir own foot
+            final String moduleId = init.getModuleName();
+            
+            // Making sure the user doesn't shoot their own foot
             final String url = cache.get(moduleId);
             if (url == null)
             {
@@ -211,6 +239,14 @@ public class EsModuleManagerImpl implements EsModuleManager
                 if (head == null) 
                 {
                     head = root.find("head");
+                    // I think it's quite ugly, but HTML doesn't require
+                    // <head>, <body> nor even <html> itself. In this case,
+                    // if <head> isn't found, we just add the element to 
+                    // the root element.
+                    if (head == null)
+                    {
+                        head = root;
+                    }
                 }
                 script = head.element("script");
             }
@@ -218,6 +254,14 @@ public class EsModuleManagerImpl implements EsModuleManager
                 if (body == null)
                 {
                     body = root.find("body");
+                    // I think it's quite ugly, but HTML doesn't require
+                    // <head>, <body> nor even <html> itself. In this case,
+                    // if <body> isn't found, we just add the element to 
+                    // the root element.
+                    if (body == null)
+                    {
+                        body = root;
+                    }
                 }
                 if (placement.equals(ImportPlacement.BODY_BOTTOM)) {
                     script = body.element("script");
@@ -234,73 +278,64 @@ public class EsModuleManagerImpl implements EsModuleManager
             
             writeAttributes(script, init);
             script.attribute("src", url);
+            script.attribute("data-module-id", moduleId);
             
-            functionName = init.getFunctionName();
-            arguments = init.getArguments();
-            
-            if (!productionMode)
-            {
-                script.attribute("data-module-id", moduleId);
-                final Element log = script.element("script", "type", "text/javascript");
-                log.text(String.format("console.debug('Imported ES module %s');", moduleId));
-                log.moveBefore(script);
-            }
-            
-            // If we have not only the import, but also an automatic function call
-            if (arguments != null || functionName != null)
+        }
+        
+    }
+    
+    @Override
+    public void writeInitialization(Element body, List<String> libraryURLs, List<JSONArray> inits)
+    {
+
+        final boolean noInits = !requireJsEnabled && libraryURLs.isEmpty() && inits.isEmpty();
+
+        if (noInits)
+        {
+            body.forceAttributes("data-page-initialized", "true");
+            Element script = body.element("script", "type", "text/javascript");
+            script.raw("document.querySelector(\"body > div.pageloading-mask\").remove()");        
+        }
+        else
+        {
+            if (!requireJsEnabled || !libraryURLs.isEmpty() || !inits.isEmpty())
             {
-                final Element moduleFunctionCall = script.element("script");
-                
-                moduleFunctionCall.moveAfter(script);
-                
-                final String moduleFunctionCallFormat = 
-                        "import %s from '%s';\n"
-                        + "%s(%s);";
-                
-                final String importName = functionName != null ? functionName : GENERIC_IMPORTED_VARIABLE;
-                final String importDeclaration = functionName != null ? 
-                        "{ " + functionName + " }": 
-                            GENERIC_IMPORTED_VARIABLE;
-                
-                moduleFunctionCall.text(String.format(moduleFunctionCallFormat, 
-                        importDeclaration, moduleId, importName,
-                        convertToJsFunctionParameters(arguments, compactJSON)));
-                
-                writeAttributes(moduleFunctionCall, init);
-                
-                // Avoiding duplicated ids
-                final String id = moduleFunctionCall.getAttribute("id");
-                if (id != null)
-                {
-                    moduleFunctionCall.forceAttributes("id", id + "-function-call");
-                }
-                
+                Element element = body.element("script", "type", "module", "id", "__tapestry-es-module-pageinit__");
+        
+                element.raw(String.format("import pageinit from \"t5/core/pageinit\";\npageinit(%s, %s, false);",
+                        convert(libraryURLs), convert(inits)));
             }
             
         }
         
     }
     
-    static String convertToJsFunctionParameters(Object[] arguments, boolean compactJSON)
+    private String convert(List<?> input)
+    {
+        return new JSONArray().putAll(input).toString(compactJSON);
+    }
+
+
+    static String convertToJsFunctionParameters(JSONArray arguments, boolean compactJSON)
     {
         String result;
-        if (arguments == null || arguments.length == 0)
+        if (arguments == null || arguments.size() == 0)
         {
             result = "";
         }
-        else if (arguments.length == 1)
+        else if (arguments.size() == 1)
         {
-            result = convertToJsFunctionParameter(arguments[0], compactJSON);
+            result = convertToJsFunctionParameter(arguments.get(0), compactJSON);
         }
         else {
             StringBuilder builder = new StringBuilder();
-            for (int i = 0; i < arguments.length; i++) 
+            for (int i = 0; i < arguments.size(); i++) 
             {
                 if (i > 0)
                 {
                     builder.append(", ");
                 }
-                builder.append(convertToJsFunctionParameter(arguments[i], compactJSON));
+                builder.append(convertToJsFunctionParameter(arguments.get(i), compactJSON));
             }
             result = builder.toString();
         }
@@ -311,9 +346,9 @@ public class EsModuleManagerImpl implements EsModuleManager
     {
         String result;
         
-        if (object == null)
+        if (object == null || object == JSONObject.NULL)
         {
-            result = null;
+            result = "null";
         }
         else if (object instanceof String || object instanceof JSONLiteral)
         {
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java
new file mode 100644
index 000000000..03ec9874a
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java
@@ -0,0 +1,141 @@
+// 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.javascript;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tapestry5.commons.Resource;
+import org.apache.tapestry5.http.services.Dispatcher;
+import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.http.services.Response;
+import org.apache.tapestry5.internal.services.AssetDispatcher;
+import org.apache.tapestry5.internal.services.ResourceStreamer;
+import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
+import org.apache.tapestry5.ioc.IOOperation;
+import org.apache.tapestry5.ioc.OperationTracker;
+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.EsShim;
+import org.apache.tapestry5.services.javascript.EsShimManager;
+
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * Handler contributed to {@link AssetDispatcher} with key "es-shims". 
+ * It interprets the extra path as a module name and serves the ES shim
+ * for it.
+ *
+ * @see EsShim
+ */
+public class EsShimDispatcher implements Dispatcher
+{
+    
+    private final ResourceStreamer streamer;
+
+    private final OperationTracker tracker;
+    
+    private final String requestPrefix;
+
+    private final boolean compress;
+    
+    private Map<String, StreamableResource> shimMap;
+
+    public EsShimDispatcher(EsShimManager esShimManager,
+                            StreamableResourceSource streamableResourceSource,
+                            ResourceChangeTracker resourceChangeTracker,
+                            ResourceStreamer streamer,
+                            OperationTracker tracker,
+                            boolean compress)
+    {
+        this.streamer = streamer;
+        this.tracker = tracker;
+        this.compress = compress;
+        
+        final Map<String, Resource> shims = esShimManager.getShims();
+        this.shimMap = new HashMap<>(shims.size());
+        
+        try
+        {
+            for (String moduleName : shims.keySet())
+            {
+                shimMap.put(moduleName, streamableResourceSource.getStreamableResource(
+                        shims.get(moduleName), StreamableResourceProcessing.COMPRESSION_ENABLED, resourceChangeTracker));
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+
+        requestPrefix = esShimManager.getRequestPrefix(compress);
+    }
+
+    public boolean dispatch(Request request, Response response) throws IOException
+    {
+        String path = request.getPath();
+
+        if (path.startsWith(requestPrefix))
+        {
+            String extraPath = path.substring(requestPrefix.length());
+
+            if (!handleModuleRequest(extraPath, response))
+            {
+                response.sendError(HttpServletResponse.SC_NOT_FOUND, String.format("No ES module shim for path '%s'.", extraPath));
+            }
+
+            return true;
+        }
+
+        return false;
+
+    }
+    
+    private boolean handleModuleRequest(String extraPath, Response response) throws IOException
+    {
+        int dotx = extraPath.lastIndexOf('.');
+
+        if (dotx < 0)
+        {
+            return false;
+        }
+
+        if (!extraPath.substring(dotx + 1).equals("js"))
+        {
+            return false;
+        }
+
+        final String moduleName = extraPath.substring(0, dotx);
+
+        return tracker.perform(String.format("Streaming %s %s",
+                compress ? "compressed module" : "module",
+                moduleName), new IOOperation<Boolean>()
+        {
+            public Boolean perform() throws IOException
+            {
+                StreamableResource resource = shimMap.get(moduleName);
+
+                if (resource != null)
+                {
+                    final String checksum = resource.getChecksum();
+                    return streamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS);
+                }
+
+                return false;
+            }
+        });
+    }
+
+}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/translator/NumericTranslatorSupportImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/translator/NumericTranslatorSupportImpl.java
index 2ba6a1b3a..ab036a5a8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/translator/NumericTranslatorSupportImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/translator/NumericTranslatorSupportImpl.java
@@ -14,12 +14,6 @@
 
 package org.apache.tapestry5.internal.translator;
 
-import org.apache.tapestry5.commons.services.TypeCoercer;
-import org.apache.tapestry5.commons.util.CollectionFactory;
-import org.apache.tapestry5.dom.Element;
-import org.apache.tapestry5.ioc.services.ThreadLocale;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.text.DecimalFormat;
@@ -29,22 +23,28 @@ import java.text.ParseException;
 import java.util.Locale;
 import java.util.Set;
 
+import org.apache.tapestry5.commons.services.TypeCoercer;
+import org.apache.tapestry5.commons.util.CollectionFactory;
+import org.apache.tapestry5.dom.Element;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.services.ThreadLocale;
+
 public class NumericTranslatorSupportImpl implements NumericTranslatorSupport
 {
     private final TypeCoercer typeCoercer;
 
     private final ThreadLocale threadLocale;
 
-    private final JavaScriptSupport javascriptSupport;
+    private final RequireJsModeHelper requireJsModeHelper;
 
     private final Set<Class> integerTypes = CollectionFactory.newSet();
 
     public NumericTranslatorSupportImpl(TypeCoercer typeCoercer, ThreadLocale threadLocale,
-                                        JavaScriptSupport javascriptSupport)
+                                        RequireJsModeHelper requireJsModeHelper)
     {
         this.typeCoercer = typeCoercer;
         this.threadLocale = threadLocale;
-        this.javascriptSupport = javascriptSupport;
+        this.requireJsModeHelper = requireJsModeHelper;
 
         Class[] integerTypes =
                 {Byte.class, Short.class, Integer.class, Long.class, BigInteger.class};
@@ -60,7 +60,7 @@ public class NumericTranslatorSupportImpl implements NumericTranslatorSupport
     {
         String translation = isIntegerType(type) ? "integer" : "numeric";
 
-        javascriptSupport.require("t5/core/validation");
+        requireJsModeHelper.importModule("t5/core/validation");
 
         element.attributes("data-validation", "true",
                 "data-translation", translation,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
index acaa25334..f069ff639 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java
@@ -28,11 +28,15 @@ import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.services.DocumentLinker;
 import org.apache.tapestry5.internal.services.ResourceStreamer;
+import org.apache.tapestry5.internal.services.ajax.EsShimManagerImpl;
 import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelperImpl;
 import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
 import org.apache.tapestry5.internal.services.javascript.AddBrowserCompatibilityStyles;
 import org.apache.tapestry5.internal.services.javascript.ConfigureHTMLElementFilter;
 import org.apache.tapestry5.internal.services.javascript.EsModuleManagerImpl;
+import org.apache.tapestry5.internal.services.javascript.EsShimDispatcher;
 import org.apache.tapestry5.internal.services.javascript.Internal;
 import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor;
 import org.apache.tapestry5.internal.services.javascript.JavaScriptStackSourceImpl;
@@ -48,6 +52,7 @@ import org.apache.tapestry5.ioc.services.FactoryDefaults;
 import org.apache.tapestry5.ioc.services.SymbolProvider;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.ComponentOverride;
 import org.apache.tapestry5.services.Core;
 import org.apache.tapestry5.services.Environment;
@@ -58,12 +63,15 @@ import org.apache.tapestry5.services.MarkupRendererFilter;
 import org.apache.tapestry5.services.PartialMarkupRenderer;
 import org.apache.tapestry5.services.PartialMarkupRendererFilter;
 import org.apache.tapestry5.services.PathConstructor;
+import org.apache.tapestry5.services.assets.StreamableResourceSource;
 import org.apache.tapestry5.services.compatibility.Compatibility;
 import org.apache.tapestry5.services.compatibility.Trait;
 import org.apache.tapestry5.services.javascript.AMDWrapper;
 import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.EsModuleManager;
 import org.apache.tapestry5.services.javascript.EsModuleManager.EsModuleManagerContribution;
+import org.apache.tapestry5.services.javascript.EsShim;
+import org.apache.tapestry5.services.javascript.EsShimManager;
 import org.apache.tapestry5.services.javascript.ExtensibleJavaScriptStack;
 import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
@@ -100,6 +108,8 @@ public class JavaScriptModule
         binder.bind(JavaScriptStackSource.class, JavaScriptStackSourceImpl.class);
         binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Core.class).withId("CoreJavaScriptStack");
         binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Internal.class).withId("InternalJavaScriptStack");
+        binder.bind(RequireJsModeHelper.class, RequireJsModeHelperImpl.class);
+        binder.bind(EsShimManager.class, EsShimManagerImpl.class);
     }
 
     /**
@@ -126,15 +136,15 @@ public class JavaScriptModule
     /**
      * The core JavaScriptStack has a number of entries:
      * <dl>
-     * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library</dd>
+     * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library (if Require.js is enabled)</dd>
      * <dt>scriptaculous.js, effects.js</dt> <dd>Optional JavaScript libraries in compatibility mode (see {@link Trait#SCRIPTACULOUS})</dd>
      * <dt>t53-compatibility.js</dt> <dd>Optional JavaScript library (see {@link Trait#INITIALIZERS})</dd>
-     * <dt>underscore-library, underscore-module</dt>
+     * <dt>underscore-library, underscore-module (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dt>
      * <dt>The Underscore JavaScript library, and the shim that allows underscore to be injected</dt>
      * <dt>t5/core/init</dt> <dd>Optional module related to t53-compatibility.js</dd>
-     * <dt>jquery-library</dt> <dd>The jQuery library</dd>
-     * <dt>jquery-noconflict</dt> <dd>Switches jQuery to no-conflict mode (only present when the infrastructure is "prototype").</dd>
-     * <dt>jquery</dt> <dd>A module shim that allows jQuery to be injected (and also switches jQuery to no-conflict mode)</dd>
+     * <dt>jquery-library</dt> <dd>The jQuery library (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dd>
+     * <dt>jquery-noconflict</dt> <dd>Switches jQuery to no-conflict mode (only present when the infrastructure is "prototype" and Require.js is enabled).</dd>
+     * <dt>jquery</dt> <dd>A module shim that allows jQuery to be injected (and also switches jQuery to no-conflict mode) (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dd>
      * <dt>bootstrap.css, tapestry.css, exception-frame.css, tapestry-console.css, tree.css</dt>
      * <dd>CSS files</dd>
      * <dt>t5/core/[...]</dt>
@@ -150,10 +160,14 @@ public class JavaScriptModule
     public static void setupCoreJavaScriptStack(OrderedConfiguration<StackExtension> configuration,
                                                 Compatibility compatibility,
                                                 @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
-                                                String provider)
+                                                String provider,
+                                                @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled)
     {
-        configuration.add("requirejs", StackExtension.library(ROOT + "/require.js"));
-        configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.7.js"));
+        if (requireJsEnabled)
+        {
+            configuration.add("requirejs", StackExtension.library(ROOT + "/require.js"));
+            configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.7.js"));
+        }
 
         if (provider.equals("prototype"))
         {
@@ -171,18 +185,32 @@ public class JavaScriptModule
 
         if (compatibility.enabled(Trait.INITIALIZERS))
         {
-            add(configuration, StackExtensionType.LIBRARY, ROOT + "/t53-compatibility.js");
-            configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init"));
+            if (requireJsEnabled)
+            {
+                add(configuration, StackExtensionType.LIBRARY, ROOT + "/t53-compatibility.js");
+                configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init"));
+            }
+            else
+            {
+                add(configuration, StackExtensionType.ES_MODULE, "t5/core/t53-compatibility");
+                add(configuration, StackExtensionType.ES_MODULE, "t5/core/init");
+            }
         }
 
-        configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery.js"));
+        if (requireJsEnabled)
+        {
+            configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery.js"));
+        }
 
-        if (provider.equals("prototype"))
+        if (provider.equals("prototype") && requireJsEnabled)
         {
             configuration.add("jquery-noconflict", StackExtension.library(ROOT + "/jquery-noconflict.js"));
         }
 
-        add(configuration, StackExtensionType.MODULE, "jquery");
+        if (requireJsEnabled)
+        {
+            add(configuration, StackExtensionType.MODULE, "jquery");
+        }
         
         add(configuration, StackExtensionType.STYLESHEET, "${" + SymbolConstants.FONT_AWESOME_ROOT + "}/css/font-awesome.css");
 
@@ -280,6 +308,9 @@ public class JavaScriptModule
                                               JavaScriptStackSource javaScriptStackSource,
                                               JavaScriptStackPathConstructor javaScriptStackPathConstructor,
                                               LocalizationSetter localizationSetter,
+                                              EsShimManager esShimManager,
+                                              StreamableResourceSource streamableResourceSource,
+                                              ResourceChangeTracker resourceChangeTracker,
                                               @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
                                               String modulePathPrefix,
                                               @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
@@ -296,6 +327,15 @@ public class JavaScriptModule
                     javaScriptStackSource, javaScriptStackPathConstructor, localizationSetter, modulePathPrefix,
                     assetPathPrefix, true),
                 "after:Modules", "before:ComponentEvent");
+        
+        configuration.add("EsShims", new EsShimDispatcher(esShimManager, streamableResourceSource, resourceChangeTracker, 
+                resourceStreamer, tracker, false),
+                "before:Asset", "before:ComponentEvent");
+        
+        configuration.add("CompressedEsShims", new EsShimDispatcher(esShimManager, streamableResourceSource, resourceChangeTracker, 
+                resourceStreamer, tracker, true),
+                "after:EsShims", "before:Asset", "before:ComponentEvent");
+
     }
 
     /**
@@ -312,7 +352,8 @@ public class JavaScriptModule
     public void exposeJavaScriptSupportForFullPageRenders(OrderedConfiguration<MarkupRendererFilter> configuration,
                                                           final JavaScriptStackSource javascriptStackSource,
                                                           final JavaScriptStackPathConstructor javascriptStackPathConstructor,
-                                                          final Request request)
+                                                          final Request request,
+                                                          @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) final boolean requireJsEnabled)
     {
 
         final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
@@ -324,7 +365,7 @@ public class JavaScriptModule
                 DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
 
                 JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
-                        javascriptStackPathConstructor, suppressCoreStylesheetsHook);
+                        javascriptStackPathConstructor, suppressCoreStylesheetsHook, requireJsEnabled);
 
                 environment.push(JavaScriptSupport.class, support);
 
@@ -353,7 +394,9 @@ public class JavaScriptModule
 
                                                             final JavaScriptStackPathConstructor javascriptStackPathConstructor,
 
-                                                            final Request request)
+                                                            final Request request,
+                                                            
+                                                            @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) final boolean requireJsEnabled)
     {
         final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
 
@@ -379,7 +422,7 @@ public class JavaScriptModule
                 DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
 
                 JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
-                        javascriptStackPathConstructor, idAllocator, true, suppressCoreStylesheetsHook);
+                        javascriptStackPathConstructor, idAllocator, true, suppressCoreStylesheetsHook, requireJsEnabled);
 
                 environment.push(JavaScriptSupport.class, support);
 
@@ -513,29 +556,123 @@ public class JavaScriptModule
         }
     }
     
-    @Contribute(EsModuleManager.class)
-    public static void setupApplicationCatalogEsModules(OrderedConfiguration<EsModuleManagerContribution> configuration,
-                                                        LocalizationSetter localizationSetter,
-                                                        ComponentMessagesSource messagesSource,
-                                                        ResourceChangeTracker resourceChangeTracker,
-                                                        @Symbol(SymbolConstants.COMPACT_JSON) boolean compactJSON)
+    @Contribute(EsShimManager.class)
+    public static void setupBaseEsShims(
+            MappedConfiguration<String, Resource> configuration,
+            @Path("${tapestry.asset.root}/bootstrap/js/transition.js")
+            Resource transition,
+            @Path("${tapestry.asset.root}/bootstrap4/js/bootstrap-util.js")
+            Resource bootstrapUtil,
+            Compatibility compatibility,
+            AssetSource assetSource,
+            LocalizationSetter localizationSetter,
+            ComponentMessagesSource messagesSource,
+            ResourceChangeTracker resourceChangeTracker,
+            @Symbol(SymbolConstants.COMPACT_JSON) boolean compactJSON)
     {
         
-        EsModuleConfigurationCallback callback = jsonObject -> {
+        final Resource jQuery = assetSource.getClasspathAsset("/META-INF/assets/tapestry5/jquery.js")
+                .getResource();
+        configuration.add("jquery", new EsShim(jQuery)
+                .defaultExport("jQuery.noConflict()")
+                .getResource());
+        
+        final Resource moment = assetSource.getClasspathAsset("/META-INF/assets/tapestry5//moment-2.15.1.js")
+                .getResource();
+        configuration.add("t5/core/moment", new EsShim(moment)
+                .defaultExport("moment")
+                .getResource());
         
-            for (Locale locale : localizationSetter.getSupportedLocales())
+        if (compatibility.enabled(Trait.BOOTSTRAP_3))
+        {
+            final String[] modules = new String[]{"affix", "alert", "button", "carousel", "collapse", "dropdown", "modal",
+                    "scrollspy", "tab", "tooltip"};
+            addBootstrap3EsShims(configuration, modules, transition);
+
+            Resource popover = transition.forFile("popover.js");
+
+            final String popoverModuleName = "bootstrap/popover";
+            configuration.add(popoverModuleName,
+                    new EsShim(popover)
+                        .importModule("jquery")
+                        .importModule("bootstrap/tooltip")
+                        .getResource());
+        }
+
+        if (compatibility.enabled(Trait.BOOTSTRAP_4))
+        {
+            final String bootstrapUtilModuleName = "bootstrap/bootstrap-util";
+            configuration.add(bootstrapUtilModuleName, 
+                    new EsShim(bootstrapUtil)
+                        .getResource());
+            
+            final String popperModuleName = "bootstrap/popper";
+            final Resource popper = bootstrapUtil.forFile("popper.js");
+            configuration.add(popperModuleName, 
+                    new EsShim(popper).getResource());
+            
+            for (String name : new String[]{"alert", "button", "carousel", "collapse", "dropdown", "modal",
+                    "scrollspy", "tab", "tooltip"})
             {
-                MessageCatalogResource resource = new MessageCatalogResource(false, locale, messagesSource, resourceChangeTracker, compactJSON);
-    
-                jsonObject.put("t5/core/messages/" + locale.toString(), resource.toURL());
+                Resource lib = bootstrapUtil.forFile(name + ".js");
+                if (lib.exists())
+                {
+                    final String moduleName = "bootstrap/" + name;
+                    configuration.add(moduleName, 
+                            new EsShim(lib)
+                                .importModule(bootstrapUtilModuleName)
+                                .importModule(popperModuleName)
+                                .getResource());
+                }
             }
-            
-        };
+        }
+
+        // Just the minimum to have alerts and AJAX validation working when Bootstrap
+        // is completely disabled
+        if (!compatibility.enabled(Trait.BOOTSTRAP_3) && !compatibility.enabled(Trait.BOOTSTRAP_4))
+        {
+            final String[] modules = new String[]{"alert", "dropdown", "collapse"};
+            addBootstrap3EsShims(configuration, modules, transition);
+        }
         
-        configuration.add("ApplicationCatalog", EsModuleManagerContribution.base(callback));
+        for (Locale locale : localizationSetter.getSupportedLocales())
+        {
+            MessageCatalogResource resource = new MessageCatalogResource(true, locale, messagesSource, resourceChangeTracker, compactJSON);
+            configuration.add("t5/core/messages/" + locale.toString(),
+                    new EsShim(resource).getResource());
+        }
         
     }
-    
+
+    @Contribute(EsModuleManager.class)
+    public static void setupBaseEsModules(
+            OrderedConfiguration<EsModuleManagerContribution> configuration,
+            EsShimManager esShimManager)
+    {
+        for (String moduleName : esShimManager.getShims().keySet())
+        {
+            configuration.add(moduleName, EsModuleManagerContribution.base(
+                    c -> EsModuleConfigurationCallback.setImport(c, moduleName, esShimManager.getUrl(moduleName))));
+        }
+    }
+
+    private static void addBootstrap3EsShims(
+            MappedConfiguration<String, Resource> configuration, 
+            String[] modules, 
+            Resource reference) 
+    {
+        for (String module : modules) 
+        {
+            final String moduleId = "bootstrap/" + module;
+            final Resource resource = reference.forFile(module + ".js");
+            if (resource.exists())
+            {
+                configuration.add(moduleId, new EsShim(resource)
+                        .importModule("jquery")
+                        .getResource());
+            }
+        }
+    }
 
     /**
      * Contributes 'ConfigureHTMLElement', which writes the attributes into the HTML tag to describe locale, etc.
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index 73a69419e..0709fd572 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -1810,6 +1810,9 @@ public final class TapestryModule
 
                                          @Symbol(SymbolConstants.ENABLE_PAGELOADING_MASK)
                                          final boolean enablePageloadingMask,
+                                         
+                                         @Symbol(SymbolConstants.REQUIRE_JS_ENABLED)
+                                         final boolean requireJsEnabled,
 
                                          final ValidationDecoratorFactory validationDecoratorFactory)
     {
@@ -1817,7 +1820,9 @@ public final class TapestryModule
         {
             public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
             {
-                DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, esModuleManager, omitGeneratorMeta, enablePageloadingMask, tapestryVersion);
+                DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, 
+                        esModuleManager, omitGeneratorMeta, enablePageloadingMask, 
+                        tapestryVersion, requireJsEnabled);
 
                 environment.push(DocumentLinker.class, linker);
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java
index 70a3b00b0..0d97c0024 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java
@@ -16,6 +16,7 @@ import java.util.List;
 
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
+import org.apache.tapestry5.json.JSONArray;
 import org.apache.tapestry5.services.javascript.EsModuleManager.EsModuleManagerContribution;
 
 /**
@@ -45,15 +46,29 @@ public interface EsModuleManager
     /**
      * Invoked by the internal {@link org.apache.tapestry5.internal.services.DocumentLinker} service to write the 
      * ES module imports (as per {@link JavaScriptSupport#importEsModule(String)} into the page. 
-     * this occurs after the ES module infrastructure
+     * This occurs after the ES module infrastructure
      * has been written into the page, along with the core libraries.
      *
      * @param root
      *         {@code <root>} element of the page.
-     * @param inits
-     *         specify initialization on the page, based on loading modules, extacting functions from modules, and invoking those functions
+     * @param imports
+     *         imported modules as {@linkplain EsModuleInitialization} instances.
+     */
+    void writeImports(Element root, List<EsModuleInitialization> imports);
+    
+
+    /**
+     * Invoked by the internal {@link org.apache.tapestry5.internal.services.DocumentLinker} service to write the 
+     * calls to {@code t5/core/pageinit} module.
+     * this occurs after the ES module infrastructure
+     * has been written into the page, along with the core libraries.
+     *
+     * @param body {@code body} element of the page.
+     * @param libraryURLs URLs of the JS files to be included in the page.
+     * @param inits a list of {@linkplain} JSONArray} instances containing the 
+     * module initializations.
      */
-    void writeImports(Element root, List<EsModuleInitialization> inits);
+    void writeInitialization(Element body, List<String> libraryURLs, List<JSONArray> inits);
     
     /**
      * Encapsulates a contribution to {@linkplain EsModuleManager}.
@@ -67,12 +82,16 @@ public interface EsModuleManager
         private final EsModuleConfigurationCallback callback;
         
         private final boolean isBase;
+        
+        // In case this is an ES shim contribution.
+        private final EsShim esWrapper;
 
-        EsModuleManagerContribution(EsModuleConfigurationCallback callback, boolean isBase) 
+        private EsModuleManagerContribution(EsModuleConfigurationCallback callback, boolean isBase, EsShim esWrapper) 
         {
             super();
             this.callback = callback;
             this.isBase = isBase;
+            this.esWrapper = esWrapper;
         }
         
         /**
@@ -83,9 +102,26 @@ public interface EsModuleManager
          */
         public static EsModuleManagerContribution base(EsModuleConfigurationCallback callback) 
         {
-            return new EsModuleManagerContribution(callback, true);
+            return new EsModuleManagerContribution(callback, true, null);
         }
 
+        /**
+         * Creates a base contribution (one that contributes a callback used 
+         * when creating the base import map to be used for all requests)
+         * which is also an ES module shim. 
+         * @param esWrapper an {@linkplain EsShim} instance. It cannot be null.
+         * @param callback an {@linkplain EsModuleConfigurationCallback} instance.
+         * @return a corresponding {@linkplain EsModuleManagerContribution}.
+         */
+        public static EsModuleManagerContribution base(EsShim esWrapper, EsModuleConfigurationCallback callback) 
+        {
+            if (esWrapper == null)
+            {
+                throw new IllegalArgumentException("Parameter esWrapper cannot be null");
+            }
+            return new EsModuleManagerContribution(callback, true, esWrapper);
+        }        
+        
         /**
          * Creates a global per-request contribution (one that contributes a callback used 
          * in all requests after the callbacks added through 
@@ -96,7 +132,7 @@ public interface EsModuleManager
          */
         public static EsModuleManagerContribution globalPerRequest(EsModuleConfigurationCallback callback) 
         {
-            return new EsModuleManagerContribution(callback, false);
+            return new EsModuleManagerContribution(callback, false, null);
         }
         
         public EsModuleConfigurationCallback getCallback() {
@@ -106,6 +142,10 @@ public interface EsModuleManager
         public boolean isBase() {
             return isBase;
         }
+        
+        public EsShim getEsWrapper() {
+            return esWrapper;
+        }
 
     }
 
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java
new file mode 100644
index 000000000..89a2cde2d
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java
@@ -0,0 +1,189 @@
+// 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 java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.tapestry5.commons.Resource;
+import org.apache.tapestry5.internal.util.VirtualResource;
+
+/**
+ * Used to wrap plain JavaScript libraries as ES modules. The underlying
+ * resource is transformed before it is sent to the client.
+ *
+ * @since 5.10.0
+ */
+public class EsShim 
+{
+
+    /**
+     * The underlying resource, usually a JavaScript library
+     */
+    private final Resource resource;
+
+    /**
+     * The modules that this module requires, the keys being module names and
+     * the values being the respective parameter names for the module's factory
+     * function.
+     */
+    private final Map<String, String> importConfig = new HashMap<String, String>();
+    
+    /**
+     * The default export.
+     */
+    private String defaultExport;
+
+    public EsShim(final Resource resource) 
+    {
+        this.resource = resource;
+    }
+
+    /**
+     * Adds a dependency on another module. 
+     * 
+     * @param moduleName
+     *            the name of the required module, e.g. <code>jQuery</code>
+     * @param variableName
+     *            the module's corresponding variable name
+     * @return this EsShim for further configuration
+     */
+    public EsShim importModule(final String moduleName,
+            final String variableName) 
+    {
+        importConfig.put(moduleName, variableName);
+        return this;
+    }
+
+    /**
+     * Adds a dependency on another module. The module will be loaded but not
+     * passed to a variable. This is useful for dependencies on other
+     * modules that do not actually return a value.
+     *
+     * @param moduleName
+     *            the name of the required module, e.g.
+     *            <code>bootstrap/transition</code>
+     * @return this EsShim for further configuration
+     */
+    public EsShim importModule(final String moduleName) 
+    {
+        importConfig.put(moduleName, null);
+        return this;
+    }
+    
+    /**
+     * Defines the default export of this module.
+     * @param defaultExport the variable or expression to be the 
+     * default export of the module
+     * @return this EsShim for further configuration
+     */
+    public EsShim defaultExport(final String defaultExport)
+    {
+        this.defaultExport = defaultExport;
+        return this;
+    }
+
+    /**
+     * Returns a virtual resource representing this shim.
+     * @return a {@linkplain Resource}.
+     */
+    public Resource getResource() 
+    {
+        return new ESModuleWrapperResource(resource, importConfig, defaultExport);
+    }
+
+    /**
+     * A virtual resource that wraps a plain JavaScript library as an ES
+     * module.
+     */
+    private final static class ESModuleWrapperResource extends VirtualResource 
+    {
+        private final Resource resource;
+        private final Map<String, String> importConfig;
+        private final String defaultExport;
+
+        public ESModuleWrapperResource(final Resource resource,
+                final Map<String, String> importConfig, 
+                final String defaultExport) 
+        {
+            this.resource = resource;
+            this.importConfig = importConfig;
+            this.defaultExport = defaultExport;
+        }
+
+        @Override
+        public InputStream openStream() throws IOException 
+        {
+            StringBuilder imports = new StringBuilder();
+            
+            for (String module : importConfig.keySet())
+            {
+                final String variableName = importConfig.get(module);
+                imports.append("import ");
+                if (variableName != null) 
+                {
+                    imports.append(variableName);
+                    imports.append(" from ");
+                }
+                imports.append("\"");
+                imports.append(module);
+                imports.append("\";\n");
+            }
+            
+            
+            // Vector since SequenceInputStream doesn't have a varargs constructor
+            // nor something better than an Enumeration when you have more than 
+            // 2 InputStreams to join.
+            Vector<InputStream> v = new Vector<>();
+            v.add(toInputStream(imports));
+            v.add(resource.openStream());
+            if (defaultExport != null)
+            {
+                v.add(toInputStream(String.format("\n export default %s;\n",
+                        defaultExport)));
+            }
+            
+            return new SequenceInputStream(v.elements());
+        }
+
+        @Override
+        public String getFile() 
+        {
+            return "generated-module-for-" + resource.getFile();
+        }
+
+        @Override
+        public URL toURL() 
+        {
+            return null;
+        }
+
+        @Override
+        public String toString() 
+        {
+            return "ES module wrapper for " + resource.toString();
+        }
+
+        private static InputStream toInputStream(final StringBuilder sb) 
+        {
+            return new ByteArrayInputStream(sb.toString().getBytes(UTF8));
+        }
+    }
+
+}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java
new file mode 100644
index 000000000..f9d5c5f13
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java
@@ -0,0 +1,49 @@
+// 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 java.util.Map;
+
+import org.apache.tapestry5.commons.Resource;
+import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
+
+/**
+ * Service managing the ES shims.
+ *
+ * @since 5.10.0
+ */
+@UsesMappedConfiguration(Resource.class)
+public interface EsShimManager
+{
+    
+    /**
+     * Returns the shims as a (module name, module resource) map.
+     * @return a {@code Map<String, Resource>}
+     */
+    Map<String, Resource> getShims();
+
+    /**
+     * Returns the request prefix to be used for ES shim URLs.
+     * @param compress a {@code boolean} to inform whether it's the compressed asset URL or not.
+     * @return the request prefix.
+     */
+    String getRequestPrefix(boolean compress);
+    
+    /**
+     * Returns the full URL of a module.
+     * @param moduleName a module name.
+     * @return its URL.
+     */
+    String getUrl(String moduleName);
+    
+}
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 8fa030fe5..0845431dd 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
@@ -12,6 +12,8 @@
 
 package org.apache.tapestry5.services.javascript;
 
+import java.util.List;
+
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.func.F;
 import org.apache.tapestry5.func.Flow;
@@ -23,8 +25,6 @@ import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.AssetSource;
 
-import java.util.List;
-
 /**
  * An extensible implementation of {@link JavaScriptStack} that can be used as the implementation of a service.
  * The contributions to the service are used to supply the libraries, stylesheets, and initialization for a
@@ -50,6 +50,8 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
     private final List<String> stacks;
 
     private final List<String> modules;
+    
+    private final List<String> esModules;
 
     private final String initialization;
 
@@ -116,6 +118,8 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
         stacks = extensions.filter(by(StackExtensionType.STACK)).map(extractValue).toList();
 
         modules = extensions.filter(by(StackExtensionType.MODULE)).map(extractValue).toList();
+        
+        esModules = extensions.filter(by(StackExtensionType.ES_MODULE)).map(extractValue).toList();
 
         stylesheets = extensions.filter(by(StackExtensionType.STYLESHEET)).map(extractValue).map(stringToAsset)
                 .map(assetToStylesheetLink).toList();
@@ -147,31 +151,41 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack
         }
     }
 
+    @Override
     public List<String> getStacks()
     {
         return stacks;
     }
 
+    @Override
     public List<Asset> getJavaScriptLibraries()
     {
         return libraries;
     }
 
+    @Override
     public List<StylesheetLink> getStylesheets()
     {
         return stylesheets;
     }
 
+    @Override
     public String getInitialization()
     {
         return initialization;
     }
 
+    @Override
     public List<String> getModules()
     {
         return modules;
     }
 
+    @Override
+    public List<String> getEsModules() {
+        return esModules;
+    }
+
     @Override
     public JavaScriptAggregationStrategy getJavaScriptAggregationStrategy()
     {
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 777c7a118..25555e396 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
@@ -72,6 +72,14 @@ public interface JavaScriptStack
      * @since 5.4
      */
     List<String> getModules();
+    
+    /**
+     * Returns a list of ES modules to be automatically included in all pages.
+     * 
+     * @see EsModuleManager
+     * @since 5.10.0
+     */
+    List<String> getEsModules();
 
     /**
      * Identifies how to aggregate JavaScript within the stack.
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptSupport.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptSupport.java
index 7624497b0..18fcedc4c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptSupport.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptSupport.java
@@ -291,5 +291,13 @@ public interface JavaScriptSupport
      * @since 5.10.0
      */
     void addEsModuleConfigurationCallback(EsModuleConfigurationCallback callback);
+    
+    /**
+     * Returns whether Require.js is enabled or not (i.e. the value of the 
+     * {@linkplain SymbolConstants#REQUIRE_JS_ENABLED} configuration symbol.
+     * 
+     * @since 5.10.0.
+     */
+    boolean isRequireJsEnabled();
 
 }
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 67cc22d7b..42c463f38 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
@@ -62,6 +62,16 @@ public class StackExtension
         return new StackExtension(StackExtensionType.MODULE, name);
     }
 
+    /**
+     * Convenience for defining an ES_MODULE.
+     *
+     * @since 5.10.0
+     */
+    public static StackExtension esModule(String name)
+    {
+        return new StackExtension(StackExtensionType.ES_MODULE, name);
+    }
+
     /**
      * Convenience for defining a STYLESHEET.
      *
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 a4f0d7b48..0dccf2e44 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
@@ -69,6 +69,13 @@ public enum StackExtensionType
      * @since 5.4
      */
     MODULE,
+    
+    /**
+     * An ES module to be automatically imported.
+     * 
+     * @since 5.10.0
+     */
+    ES_MODULE,
 
     /**
      * Overrides the {@linkplain JavaScriptStack#getJavaScriptAggregationStrategy() JavaScript aggregation strategy}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/CheckboxValidator.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/CheckboxValidator.java
index 4cdc1d521..79a8eb231 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/CheckboxValidator.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/CheckboxValidator.java
@@ -67,7 +67,15 @@ class CheckboxValidator extends AbstractValidator<Void, Object>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
+            
 
             writer.attributes(
                     DataConstants.VALIDATION_ATTRIBUTE, true,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java
index 07d84631c..2943ff21c 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Email.java
@@ -50,7 +50,14 @@ public class Email extends AbstractValidator<Void, String>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(
                     DataConstants.VALIDATION_ATTRIBUTE, true,
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Max.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Max.java
index add90ce4f..28d88d161 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Max.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Max.java
@@ -54,7 +54,14 @@ public class Max extends AbstractValidator<Long, Number>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true,
                     "data-validate-max", constraintValue.toString(),
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/MaxLength.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/MaxLength.java
index e52add886..1e424228b 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/MaxLength.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/MaxLength.java
@@ -49,7 +49,14 @@ public final class MaxLength extends AbstractValidator<Integer, String>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true,
                     "data-validate-max-length", constraintValue.toString(),
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Min.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Min.java
index b100047c0..4d2d158d7 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Min.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Min.java
@@ -51,7 +51,15 @@ public class Min extends AbstractValidator<Long, Number>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
+            
             writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true,
                     "data-validate-min", constraintValue.toString(),
                     "data-min-message", buildMessage(formatter, field, constraintValue));
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/MinLength.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/MinLength.java
index c82bcac96..27af8bc9d 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/MinLength.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/MinLength.java
@@ -49,7 +49,14 @@ public final class MinLength extends AbstractValidator<Integer, String>
     {
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true,
                     "data-validate-min-length", constraintValue.toString(),
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Regexp.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Regexp.java
index aa4c84657..3448df986 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Regexp.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Regexp.java
@@ -48,7 +48,14 @@ public class Regexp extends AbstractValidator<Pattern, String>
 
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(DataConstants.VALIDATION_ATTRIBUTE, true,
                     "data-validate-regexp", constraintValue.pattern(),
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Required.java b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Required.java
index 5410a71ab..05e7c1dcd 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/validator/Required.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/validator/Required.java
@@ -65,7 +65,14 @@ public final class Required extends AbstractValidator<Void, Object>
         
         if (formSupport.isClientValidationEnabled())
         {
-            javaScriptSupport.require("t5/core/validation");
+            if (javaScriptSupport.isRequireJsEnabled())
+            {
+                javaScriptSupport.require("t5/core/validation");
+            }
+            else
+            {
+                javaScriptSupport.importEsModule("t5/core/validation");
+            }
 
             writer.attributes(
                     DataConstants.VALIDATION_ATTRIBUTE, true,
diff --git a/tapestry-core/src/main/resources/META-INF/assets/es-modules/t5/underscore.js b/tapestry-core/src/main/resources/META-INF/assets/es-modules/underscore.js
similarity index 100%
rename from tapestry-core/src/main/resources/META-INF/assets/es-modules/t5/underscore.js
rename to tapestry-core/src/main/resources/META-INF/assets/es-modules/underscore.js
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/moment-2.15.1.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/moment-2.15.1.js
index 1388ae65e..e7c5ff5d7 100644
--- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/moment-2.15.1.js
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/moment-2.15.1.js
@@ -8,7 +8,7 @@
     typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
     typeof define === 'function' && define.amd ? define(factory) :
     global.moment = factory()
-}(this, function () { 'use strict';
+}(this || window, function () { 'use strict';
 
     var hookCallback;
 
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-esm.js b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-esm.js
deleted file mode 100644
index dd5899219..000000000
--- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/underscore-esm.js
+++ /dev/null
@@ -1,5 +0,0 @@
-//     Underscore.js 1.13.7
-//     https://underscorejs.org
-//     (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
-//     Underscore may be freely distributed under the MIT license.
-var VERSION="1.13.7",root="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},ArrayProto=Array.prototype,ObjProto=Object.prototype,SymbolProto="undefined"!=typeof Symbol?Symbol.prototype:null,push=ArrayProto.push,slice=ArrayProto.slice,toString=ObjProto.toString,hasOwnProperty=ObjProto.hasOwnProperty,supportsArrayBuffer="undefined"!=typeof ArrayBuffer,supportsDataView="undefined"!=typeof DataView,nativeIsAr [...]
\ No newline at end of file
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties b/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
index b699ea072..33c1b7f6f 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/core.properties
@@ -118,7 +118,7 @@ core-datefield-none=None
 private-no-validation-for-field=placeholder message
 
 private-core-page-initialization-template=\
-require(["t5/core/pageinit"], function(pi) { pi.default(%s, %s); });
+require(["t5/core/pageinit"], function(pi) { pi.default(%s, %s, true); });
 
 # BeanEditForm default className parameter default:
 private-core-components.beaneditform.class=well
diff --git a/tapestry-core/src/main/typescript/src/t5/beanvalidator/beanvalidator-validation.ts b/tapestry-core/src/main/typescript/src/t5/beanvalidator/beanvalidator-validation.ts
index eb407fca4..6ba83312d 100644
--- a/tapestry-core/src/main/typescript/src/t5/beanvalidator/beanvalidator-validation.ts
+++ b/tapestry-core/src/main/typescript/src/t5/beanvalidator/beanvalidator-validation.ts
@@ -24,14 +24,13 @@
 import _ from "underscore";
 import dom from "t5/core/dom"
 import events from "t5/core/events"
-import fields from "t5/core/fields"
+import "t5/core/fields"
 import utils from "t5/core/utils"
 import validation from "t5/core/validation"
 
-// Both lines below are used to force the TypeScript compiler to actually import t5/core/fields
+// Lines below ise used to force the TypeScript compiler to actually import t5/core/fields
 // and t5/core/validation even though they're not used directly here. This file uses events 
 // set up by the imported files.
-let workaround1 = fields;
 let workaround2 = validation;
 
 // @ts-ignore
diff --git a/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts b/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts
index 69238e9fa..520ea8c58 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/autocomplete.ts
@@ -22,11 +22,13 @@ import dom from "t5/core/dom";
 import _ from "underscore";
 import $ from "jquery"
 import utils from "t5/core/utils";
-import typeahead from "t5/core/typeahead";
+import "t5/core/typeahead";
 
 // Line below is used to force the TypeScript compiler to actually import t5/core/typeahead
 // as it's not used directly here. 
-let workaround = typeahead;
+// let workaround = typeahead;
+
+let Bloodhound = window.Bloodhound;
 
 export default function(spec) {
   const $field = $(document.getElementById(spec.id));
diff --git a/tapestry-core/src/main/typescript/src/t5/core/fields.ts b/tapestry-core/src/main/typescript/src/t5/core/fields.ts
index ba4c24005..f548d3695 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/fields.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/fields.ts
@@ -237,4 +237,4 @@ dom.onDocument(events.field.showValidationError, null, function(event: EventWrap
 
 });
 
-export default {findHelpBlocks, createHelpBlock, showValidationError};
\ No newline at end of file
+export {findHelpBlocks, createHelpBlock, showValidationError};
\ No newline at end of file
diff --git a/tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts b/tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts
index efe6cff8c..ef4931516 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/form-fragment.ts
@@ -19,7 +19,7 @@ import _ from "underscore";
 import dom from "t5/core/dom";
 import events from "t5/core/events";
 import forms from  "t5/core/forms";
-import { ElementWrapper, EventWrapper }from "t5/core/types";
+import { ElementWrapper, EventWrapper } from "t5/core/types";
 
 // Line below is used to force the TypeScript compiler to actually import t5/core/forms
 // as it's not used directly here. This file uses events set up by the imported files.
@@ -82,7 +82,7 @@ dom.onDocument(events.formfragment.changeVisibility, SELECTOR, function(event: E
 
 // When a FormFragment is initially rendered as hidden, then we need to do some
 // book-keeping on the client side.
-const hide = function(id: string) {
+export const hide = function(id: string) {
   const field = dom(id);
 
   return updateFields(field!, false);
@@ -93,7 +93,7 @@ const hide = function(id: string) {
 // * spec.triggerId - id of checkbox or radio button
 // * spec.fragmentId - id of FormFragment element
 // * spec.invert - (optional) if true, then checked trigger hides (not shows) the fragment
-const linkTrigger = function(spec: { triggerId: string | HTMLElement | null; fragmentId: string | HTMLElement | null; invert: boolean; }) {
+export const linkTrigger = function(spec: { triggerId: string | HTMLElement | null; fragmentId: string | HTMLElement | null; invert: boolean; }) {
   if (spec.triggerId == null) { throw new Error("Incomplete parameters, triggerId is null"); }
   if (spec.fragmentId == null) { throw new Error("Incomplete parameters, fragmentId is null"); }
   const trigger = dom(spec.triggerId);
@@ -118,7 +118,4 @@ const linkTrigger = function(spec: { triggerId: string | HTMLElement | null; fra
   } else {
     return trigger!.on("click", null, update);
   }
-};
-
-// Module exports:
-export default { linkTrigger, hide };
+};
\ No newline at end of file
diff --git a/tapestry-core/src/main/typescript/src/t5/core/messages-es-module.ts b/tapestry-core/src/main/typescript/src/t5/core/messages-es-module.ts
index afd3d4d8a..8b77d441f 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/messages-es-module.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/messages-es-module.ts
@@ -43,6 +43,10 @@ const locale = (document.documentElement.getAttribute("data-locale")) || "en";
 // @ts-ignore
 let messages: any = await import(`t5/core/messages/${locale}`);
 
+if (messages['default'] != null && typeof messages['default'] === 'object') {
+  messages = messages.default;
+}
+
 // Returns the application message catalog message for the given key. Returns
 // a placeholder if the key is not found.
 const get = function(key: string) {
diff --git a/tapestry-core/src/main/typescript/src/t5/core/pageinit.ts b/tapestry-core/src/main/typescript/src/t5/core/pageinit.ts
index 5100e68bd..8256e6c73 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/pageinit.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/pageinit.ts
@@ -21,10 +21,11 @@
  * Any additional values in the initializer are passed to the function. The context of the function (this) is null.
  */
 
-import _ from "underscore";
+import _, { partial } from "underscore";
 import console from "t5/core/console";
 import dom from "t5/core/dom";
 import events from "t5/core/events";
+import { data } from "jquery";
 
 let exports_: any;
 let pathPrefix: String | null = null;
@@ -35,6 +36,8 @@ const isOpera = Object.prototype.toString.call(window.opera) === '[object Opera]
 // @ts-ignore
 const isIE = !!window.attachEvent && !isOpera;
 
+const requireJsEnabled = "true" == document.querySelector("html")?.dataset['requireJsEnabled'];
+
 const rebuildURL = function(path: string) {
   if (path.match(/^https?:/)) { return path; }
 
@@ -106,18 +109,15 @@ const addStylesheets = function(newStylesheets: StylesheetLink) {
 
 };
 
-const invokeInitializer = function(tracker: () => any, qualifiedName: string, initArguments: any[]) {
+const invokeInitializer = function(tracker: () => any, qualifiedName: string, initArguments: any[], requireJsModule: boolean) {
   const [moduleName, functionName] = Array.from(qualifiedName.split(':'));
 
-  // @ts-ignore
-  return require([moduleName], function(moduleLib: any) {
+  function executeInitializer(moduleLib: any) {
 
-    // If it's an AMD module generated by TypeScript and it has a default export,
-    // it gets wrapped, so we try to unwrap it here.
-    if (moduleLib != null && moduleLib.__esModule && moduleLib["default"] != null) {
-      moduleLib = moduleLib["default"];
+    if (!requireJsModule && moduleLib['default'] != null && _.isFunction(moduleLib['default'])) {
+      moduleLib = moduleLib['default'];
     }
-  
+
     try {
       // Some modules export nothing but do some full-page initialization, such as adding
       // event handlers to the body.
@@ -151,17 +151,46 @@ const invokeInitializer = function(tracker: () => any, qualifiedName: string, in
     } finally {
       tracker();
     }
-  });
+    
+  }
+
+  if (requireJsModule) {
+
+    // @ts-ignore
+    return require([moduleName], function(moduleLib: any) {
+
+      // If it's an AMD module generated by TypeScript and it has a default export,
+      // it gets wrapped, so we try to unwrap it here.
+      if (moduleLib != null && moduleLib.__esModule && moduleLib["default"] != null) {
+        moduleLib = moduleLib["default"];
+      }
+
+      return executeInitializer(moduleLib);
+    
+    });
+
+  } else {
+
+    return import(moduleName).then(function(moduleLib: any) {
+      return executeInitializer(moduleLib);
+    });
+
+  }
 };
 
   // Pre-loads a number of libraries in order. When the last library is loaded,
   // invokes the callback (with no parameters).
-function loadLibraries(libraries: string[], callback: () => any) {
+function loadLibraries(libraries: string[], requireJsModules: boolean, callback: () => any) {
   // @ts-ignore
   const reducer = (callback, library) => (function() {
     console.debug(`Loading library ${library}`);
-    // @ts-ignore
-    return require([library], callback);
+    if (requireJsModules) {
+      // @ts-ignore
+      return require([library], callback);
+    }
+    else {
+      return import(library).then(callback);
+    }
   });
 
   const finalCallback = _.reduceRight(libraries, reducer, callback);
@@ -172,9 +201,9 @@ function loadLibraries(libraries: string[], callback: () => any) {
   // 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.
   // @ts-ignore
-function initialize(inits, callback) {
+function initialize(inits, requireJsModules, callback) {
   if (inits == null) { inits = []; }
-  console.debug(`Executing ${inits.length} inits`);
+  console.debug(`Executing ${inits.length} ${requireJsModules ? "AMD" : "ES module"} inits`);
   let callbackCountdown = inits.length + 1;
 
   // tracker gets invoked once after each require/callback, plus once extra
@@ -184,7 +213,7 @@ function initialize(inits, callback) {
     callbackCountdown--;
 
     if (callbackCountdown === 0) {
-      console.debug("All inits executed");
+      console.debug(`All ${requireJsModules ? "AMD" : "ES"} inits executed`);
       if (callback) { return callback(); }
     }
   };
@@ -195,12 +224,12 @@ function initialize(inits, callback) {
   // takes no parameters.
   for (var init of Array.from(inits)) {
     if (_.isString(init)) {
-      invokeInitializer(tracker, init, []);
+      invokeInitializer(tracker, init, [], requireJsModules);
     } else {
       // @ts-ignore
       var [qualifiedName, ...initArguments] = Array.from(init);
       // @ts-ignore
-      invokeInitializer(tracker, qualifiedName, initArguments);
+      invokeInitializer(tracker, qualifiedName, initArguments, requireJsModules);
     }
   }
 
@@ -215,12 +244,17 @@ function initialize(inits, callback) {
 //
 // This is the main export of the module; other functions are attached as properties.
 // @ts-ignore
-const loadLibrariesAndInitialize = function(libraries, inits) {
+const loadLibrariesAndInitialize = function(libraries, inits, requireJsModules) {
   console.debug(`Loading ${(libraries != null ? libraries.length : undefined) || 0} libraries`);
+
+  const dataAttribute = "data-page-initialized";
+  const partialValue = "partial";
+  const hasEsModulePageInit = document.querySelector("script[id='__tapestry-es-module-pageinit__']") != null;
+
   // @ts-ignore
-  return loadLibraries(libraries,
+  return loadLibraries(libraries, requireJsModules,
     // @ts-ignore
-    () => initialize(inits,
+    () => initialize(inits, requireJsModules,
       function() {
         // At this point, all libraries have been loaded, and all inits should have executed. Unless some of
         // the inits triggered Ajax updates (such as a core/ProgressiveDisplay component), then the page should
@@ -228,6 +262,18 @@ const loadLibrariesAndInitialize = function(libraries, inits) {
         // Later Ajax requests will cause the data-ajax-active attribute to be incremented (from 0)
         // and decremented (when the requests complete).
 
+        // If Require.js is enabled, this function may be called twice: once for the
+        // Require.js initializations, another for the ES module ones.
+        // That way, we need to only set the data-page-initialized attribute to
+        // true when this function is called by the second time.
+        if (requireJsEnabled && hasEsModulePageInit) {
+          var value = dom.body.attr(dataAttribute);
+          if (value !== partialValue) {
+            dom.body.attr(dataAttribute, "partial");
+            return;
+          }
+        }
+
         dom.body.attr("data-page-initialized", "true");
 
         return Array.from(dom.body.find(".pageloading-mask")).map((mask) =>
@@ -250,7 +296,7 @@ export default exports_ = _.extend(loadLibrariesAndInitialize, {
   // second, which helps ensure that other initializions on the page are in place.
   //
   // * fieldId - element id of field to focus on
-  focus(fieldId: string) {
+  focus: function(fieldId: string) {
     const field = dom(fieldId);
 
     if (field) {
@@ -292,7 +338,7 @@ export default exports_ = _.extend(loadLibrariesAndInitialize, {
     addStylesheets(partial != null ? partial.stylesheets : undefined);
 
     // Make sure all libraries are loaded
-    exports_.loadLibraries(partial != null ? partial.libraries : undefined, function() {
+    exports_.loadLibraries(partial != null ? partial.libraries : undefined, requireJsEnabled, function() {
 
       // After libraries are loaded, update each zone:
       _(partial != null ? partial.content : undefined).each(function(...args) {
@@ -310,7 +356,7 @@ export default exports_ = _.extend(loadLibrariesAndInitialize, {
       callback && callback(response);
 
       // Lastly, perform initializations from the partial page render response.
-      return exports_.initialize(partial != null ? partial.inits : undefined);
+      return exports_.initialize(partial != null ? partial.inits : undefined, requireJsEnabled);
     });
 
   }
diff --git a/tapestry-core/src/main/typescript/src/t5/core/select.ts b/tapestry-core/src/main/typescript/src/t5/core/select.ts
index c109caf8a..40e537867 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/select.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/select.ts
@@ -19,12 +19,12 @@
 
 import events from "t5/core/events";
 import dom from "t5/core/dom";
-import zone from "t5/core/zone";
+import { findZone } from "t5/core/zone";
 
 dom.onDocument("change", "select[data-update-zone]", function() {
 
   // @ts-ignore
-  const containingZone = zone.findZone(this);
+  const containingZone = findZone(this);
 
   if (containingZone) {
     containingZone.trigger(events.zone.refresh, {
diff --git a/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts b/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts
new file mode 100644
index 000000000..5edf150a1
--- /dev/null
+++ b/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts
@@ -0,0 +1,56 @@
+// Copyright 2012, 2015 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.
+
+// Provides a small amount of backwards compatibility to the Tapestry 5.3 approach.
+// This provides placeholders for the following:
+//
+// * `T5` namespace, including `extend`, `define`, and `initializers`, `extendInitializers`, and `_` properties
+//
+// * `Tapestry` namespace: just the `Initializer` property, as an alias of `T5.initializers`
+
+import _ from "underscore";
+
+// @ts-ignore
+var T5, Tapestry;
+T5 = {
+    _: _,
+    // @ts-ignore
+    extend: function(destination, source) {
+        if (_.isFunction(source)) {
+            source = source();
+        }
+        return _.extend(destination, source);
+    },
+    // @ts-ignore
+    define: function(name, source) {
+        var namespace;
+        namespace = {};
+        T5[name] = namespace;
+        // @ts-ignore
+        return T5.extend(namespace, source);
+    },
+    initializers: {},
+    // @ts-ignore
+    extendInitializers: function(source) {
+        // @ts-ignore
+        return T5.extend(T5.initializers, source);
+    }
+};
+Tapestry = {
+    Initializer: T5.initializers
+};
+// @ts-ignore
+window.T5 = T5;
+// @ts-ignore
+window.Tapestry = Tapestry;
diff --git a/tapestry-core/src/main/typescript/src/t5/core/typeahead.ts b/tapestry-core/src/main/typescript/src/t5/core/typeahead.ts
index 932fb05fc..6e0aa22f6 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/typeahead.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/typeahead.ts
@@ -723,7 +723,7 @@
         function ignoreDuplicates() {
             return false;
         }
-    })(this);
+    })(this || window);
     var html = function() {
         return {
             wrapper: '<span class="twitter-typeahead"></span>',
diff --git a/tapestry-core/src/main/typescript/src/t5/core/validation.ts b/tapestry-core/src/main/typescript/src/t5/core/validation.ts
index 39d47c5ab..cb5a4d69b 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/validation.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/validation.ts
@@ -22,13 +22,9 @@ import dom from "t5/core/dom";
 import events from "t5/core/events";
 import utils from "t5/core/utils";
 import messages from "t5/core/messages";
-import fields from "t5/core/fields";
+import "t5/core/fields";
 import { ElementWrapper } from "t5/core/types";
 
-// Line below is used to force the TypeScript compiler to actually import t5/core/fields
-// as it's not used directly here. This file uses events set up by the imported files.
-let workaround = fields;
-
 const REGEXP_META = "t5:regular-expression";
 
 const minus = messages("decimal-symbols.minus");
diff --git a/tapestry-core/src/main/typescript/src/t5/core/zone.ts b/tapestry-core/src/main/typescript/src/t5/core/zone.ts
index 822e6da72..f08479029 100644
--- a/tapestry-core/src/main/typescript/src/t5/core/zone.ts
+++ b/tapestry-core/src/main/typescript/src/t5/core/zone.ts
@@ -213,5 +213,5 @@ const deferredZoneUpdate = (id: string, url: string) => _.defer(function() {
 });
 
 // Most of this module is document-level event handlers, but there's also some exports:
-export default { deferredZoneUpdate, findZone };
+export { deferredZoneUpdate, findZone };
 
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ModuleConfigurationCallbackTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ModuleConfigurationCallbackTests.groovy
index c305d71a3..74ebce902 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ModuleConfigurationCallbackTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/ModuleConfigurationCallbackTests.groovy
@@ -23,9 +23,18 @@ class ModuleConfigurationCallbackTests extends App1TestCase {
     @Test
     void simple() {
         
-        openLinks "ModuleConfigurationCallback Demo"
+        if (isRequireJsEnabled()) {
         
-        assert getHtmlSource().contains("\"waitSeconds\" : \"13\"");
+            openLinks "ModuleConfigurationCallback Demo"
+            
+            assert getHtmlSource().contains("\"waitSeconds\" : \"13\"");
+            
+            // Just to test @Import(module), since it's not used
+            // by Tapestry itself, being replaced by 
+            // RequireJsModeHelper.importModule() 
+            assert isElementPresent("css=script[data-requiremodule=validate-in-error]");
+            
+        }
 
     }
 
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 a9eafe7a7..183597c75 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
@@ -5,6 +5,7 @@ import org.apache.tapestry5.dom.Element
 import org.apache.tapestry5.dom.XMLMarkupModel
 import org.apache.tapestry5.internal.test.InternalBaseTestCase
 import org.apache.tapestry5.json.JSONArray
+import org.apache.tapestry5.services.javascript.EsModuleManager
 import org.apache.tapestry5.services.javascript.InitializationPriority
 import org.apache.tapestry5.services.javascript.ModuleManager
 import org.apache.tapestry5.services.javascript.StylesheetLink
@@ -32,7 +33,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("not-html").text("not an HTML document")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         // Only checked if there's something to link.
 
@@ -55,7 +56,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("not-html").text("not an HTML document")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         // Only checked if there's something to link.
 
@@ -76,7 +77,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
     void missing_root_element_is_a_noop() {
         Document document = new Document()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         linker.addLibrary("foo.js")
         linker.addScript(InitializationPriority.NORMAL, "doSomething();")
@@ -93,8 +94,9 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         document.newRootElement("html").element("body").element("p").text("Ready to be updated with scripts.")
 
         def manager = mockModuleManager(["core.js", "foo.js", "bar/baz.js"], [new JSONArray("t5/core/pageinit:evalJavaScript", "pageINIT();")])
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, false, "1.2.3", true)
 
         replay()
 
@@ -122,7 +124,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("html").element("body").element("p").text("Ready to be marked with generator meta.")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, false, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, false, false, "1.2.3", true)
 
         linker.updateDocument(document)
 
@@ -141,7 +143,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("no_html").text("Generator meta only added if root is html tag.")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, false, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, false, false, "1.2.3", true)
 
         linker.updateDocument(document)
 
@@ -158,7 +160,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("html").element("body").element("p").text("Ready to be updated with styles.")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         linker.addStylesheetLink(new StylesheetLink("foo.css"))
         linker.addStylesheetLink(new StylesheetLink("bar/baz.css", new StylesheetOptions("print")))
@@ -178,7 +180,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         document.newRootElement("html").element("head").comment(" existing head ").container.element("body").text(
             "body content")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         linker.addStylesheetLink(new StylesheetLink("foo.css"))
 
@@ -197,8 +199,9 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         document.newRootElement("html").element("body").element("p").text("Ready to be updated with scripts.")
 
         def manager = mockModuleManager([], [new JSONArray("t5/core/pageinit:evalJavaScript", "doSomething();")])
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null, true, true, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, true, "1.2.3", true)
 
         replay()
 
@@ -223,8 +226,9 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         document.newRootElement("html").element("notbody").element("p").text("Ready to be updated with scripts.")
 
         def manager = mockModuleManager(["foo.js"], [])
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, false, "1.2.3", true)
 
         replay()
 
@@ -250,8 +254,9 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         head.element("script")
 
         def manager = mockModuleManager([], [new JSONArray("['immediate/module:myfunc', {'fred':'barney'}]")])
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, false, "1.2.3", true)
 
         replay()
 
@@ -273,7 +278,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("html")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         linker.addStylesheetLink(new StylesheetLink("everybody.css"))
         linker.addStylesheetLink(new StylesheetLink("just_ie.css", new StylesheetOptions().withCondition("IE")))
@@ -295,7 +300,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         document.newRootElement("html")
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(null, null, true, false, "1.2.3", true)
 
         linker.addStylesheetLink(new StylesheetLink("whatever.css"))
         linker.addStylesheetLink(new StylesheetLink("insertion-point.css", new StylesheetOptions().asAjaxInsertionPoint()))
@@ -318,8 +323,10 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
         def manager = mockModuleManager([], ["my/module",
             new JSONArray("my/other/module:normal", 111, 222),
             new JSONArray("my/other/module:late", 333, 444)])
+        
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null, true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, false, "1.2.3", true)
 
         replay()
 
@@ -346,8 +353,10 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         def manager = mockModuleManager([], ["my/module",
             new JSONArray("my/other/module:normal", 111, 222)])
+        
+        def esManager = mockEsModuleManager()
 
-        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, null,  true, false, "1.2.3")
+        DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, esManager, true, false, "1.2.3", true)
 
         replay()
 
@@ -386,4 +395,24 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
 
         return mock;
     }
+    
+    private EsModuleManager mockEsModuleManager() {
+        
+        EsModuleManager mock = newMock(EsModuleManager);
+
+        expect(mock.writeInitialization(isA(Element),
+            isA(java.util.List), isA(java.util.List))).once()
+
+        /*expect(mock.writeInitialization(isA(Element),
+            eq(libraryURLs),
+            eq(inits))).andAnswer({
+            def body = EasyMock.currentArguments[0];
+
+            body.comment("MM-INIT");
+        } as IAnswer).once()*/
+
+
+        return mock;
+    }
+        
 }
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
index c631d4448..7caf1b90d 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportAutofocusTests.groovy
@@ -42,7 +42,7 @@ class JavaScriptSupportAutofocusTests extends InternalBaseTestCase {
         replay()
 
         // Test in partial mode, to bypass the logic about importing the "core' stack.
-        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor, null, true, null)
+        def jss = new JavaScriptSupportImpl(linker, stackSource, stackPathConstructor, null, true, null, true)
 
         cls jss
 
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
index 969498904..127389631 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImplTest.groovy
@@ -34,7 +34,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupport jss = new JavaScriptSupportImpl(null, null, null, null)
+        JavaScriptSupport jss = new JavaScriptSupportImpl(null, null, null, null, true)
 
         assertEquals(jss.allocateClientId(resources), "tracy")
         assertEquals(jss.allocateClientId(resources), "tracy_0")
@@ -45,7 +45,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
     @Test
     void commit_with_no_javascript() {
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(null, null, null, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(null, null, null, null, true)
 
         jss.commit()
     }
@@ -62,7 +62,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, null, new IdAllocator(), true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, null, new IdAllocator(), true, null, true)
 
         jss.addScript("doSomething();")
 
@@ -108,7 +108,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook, true)
 
         jss.addScript(InitializationPriority.IMMEDIATE, "doSomething();")
 
@@ -129,7 +129,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.importJavaScriptLibrary(library)
 
@@ -165,7 +165,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.importJavaScriptLibrary(library1)
 
@@ -196,7 +196,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, false, new StaticHook(true))
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, false, new StaticHook(true), true)
 
         jss.importStack(InternalConstants.CORE_STACK_NAME)
 
@@ -233,7 +233,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.require("foo/bar")
 
@@ -275,7 +275,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook, true)
 
         jss.importStack("custom")
 
@@ -331,7 +331,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook, true)
 
         jss.importStack("child")
 
@@ -356,7 +356,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.importJavaScriptLibrary(library1)
         jss.importJavaScriptLibrary(library2)
@@ -380,7 +380,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "chuck")
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", "charley")
@@ -406,7 +406,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", chuck)
         jss.addInitializerCall(InitializationPriority.IMMEDIATE, "setup", buzz)
@@ -436,7 +436,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, null, true)
 
         jss.addInitializerCall("setup", "chuck")
 
@@ -470,7 +470,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, null, true, falseHook, true)
 
         jss.addInitializerCall("setup", chuck)
 
@@ -494,7 +494,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook, true)
 
         jss.importStylesheet(stylesheet)
 
@@ -517,7 +517,7 @@ class JavaScriptSupportImplTest extends InternalBaseTestCase {
 
         replay()
 
-        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook)
+        JavaScriptSupportImpl jss = new JavaScriptSupportImpl(linker, stackSource, pathConstructor, falseHook, true)
 
         jss.importStylesheet(new StylesheetLink("style.css", options))
         jss.importStylesheet(new StylesheetLink("style.css", new StylesheetOptions("hologram")))
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/App1TestCase.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/App1TestCase.java
index 6ca5457bb..ce0640d51 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/App1TestCase.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/App1TestCase.java
@@ -10,4 +10,12 @@ import org.apache.tapestry5.test.TapestryTestConfiguration;
 @TapestryTestConfiguration(webAppFolder = "src/test/app1")
 public abstract class App1TestCase extends TapestryCoreTestCase
 {
+    protected boolean isRequireJsEnabled()
+    {
+        if (!isElementPresent("require-js-enabled-value"))
+        {
+            openBaseURL();
+        }
+        return Boolean.valueOf(getText("require-js-enabled-value"));
+    }
 }
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/AssetTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/AssetTests.java
index 06a92458a..7cb10e5e5 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/AssetTests.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/AssetTests.java
@@ -52,10 +52,10 @@ public class AssetTests extends App1TestCase
     {
         openLinks("AssetDemo");
         
-        assertEquals("http://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js", getText("httpAsset"));
-        assertEquals("https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js", getText("httpsAsset"));
-        assertEquals("http://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js", getText("protocolRelativeAsset"));
-        assertEquals("ftp://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js", getText("ftpAsset"));
+        assertEquals("http://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js", getText("httpAsset"));
+        assertEquals("https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js", getText("httpsAsset"));
+        assertEquals("http://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js", getText("protocolRelativeAsset"));
+        assertEquals("ftp://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js", getText("ftpAsset"));
         
         // check whether externaly @Import'ed d3 works
         assertTrue(isElementPresent("css=svg"));
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/EsModuleTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/EsModuleTests.java
index d2ba735ee..26e85f508 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/EsModuleTests.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/EsModuleTests.java
@@ -30,7 +30,6 @@ import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback;
 import org.apache.tapestry5.services.javascript.EsModuleInitialization;
 import org.apache.tapestry5.services.javascript.EsModuleManager;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
-import org.openqa.selenium.support.ui.ExpectedCondition;
 import org.testng.annotations.Test;
 
 /**
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
index b5a6d8e13..5ffe581ea 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java
@@ -13,9 +13,11 @@
 package org.apache.tapestry5.integration.app1.components;
 
 import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.annotations.BeginRender;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.http.services.Request;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
 
@@ -25,7 +27,7 @@ import java.util.Calendar;
  * Here's a component with a template, including a t:body element. Really should rename this to "Layout" as that's the
  * T5 naming.
  */
-@Import(stylesheet = "context:css/app.css", module = {"bootstrap/collapse", "app/test-support"})
+@Import(stylesheet = "context:css/app.css")
 public class Border
 {
     @Inject
@@ -37,6 +39,9 @@ public class Border
     
     @Inject @Property
     private PageClassLoaderContextManager pccm; 
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     public static final int year;
 
@@ -67,4 +72,11 @@ public class Border
         return version + " from " + System.getProperty("java.vendor");
     }
     
+    @BeginRender
+    void beginRender()
+    {
+        requireJsModeHelper.importModule("bootstrap/collapse");
+        requireJsModeHelper.importModule("app/test-support");
+    }
+    
 }
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ClientConsoleDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ClientConsoleDemo.java
index a319dd580..9b19a5b4f 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ClientConsoleDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ClientConsoleDemo.java
@@ -14,11 +14,19 @@
 
 package org.apache.tapestry5.integration.app1.pages;
 
-import org.apache.tapestry5.annotations.Import;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.annotations.Inject;
 
 /**
  * @since 5.3
  */
-@Import(module = "client-console-demo")
 public class ClientConsoleDemo {
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
+    void beginRender()
+    {
+        requireJsModeHelper.importModule("client-console-demo");
+    }
 }
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/EsModuleDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/EsModuleDemo.java
index de17500ba..f58e2c674 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/EsModuleDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/EsModuleDemo.java
@@ -69,7 +69,7 @@ public class EsModuleDemo
             .with();
 
         javaScriptSupport.importEsModule("parameter-type-default-export")
-            .with(null, true, false, Math.PI * Math.E, "string", "jsonLiteral",
+            .with(JSONObject.NULL, true, false, Math.PI * Math.E, "string", "jsonLiteral",
                     new JSONObject("key", "value"), new JSONArray(1, "2"));
 
         if (overrideEsModuleImportAgain != null && overrideEsModuleImportAgain)
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleConfigurationCallbackDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleConfigurationCallbackDemo.java
index 09deb7cfe..2342053d7 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleConfigurationCallbackDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleConfigurationCallbackDemo.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package org.apache.tapestry5.integration.app1.pages;
 
+import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@@ -23,6 +24,7 @@ import org.apache.tapestry5.services.javascript.ModuleConfigurationCallback;
  * {@link JavaScriptSupport#addModuleConfigurationCallback(ModuleConfigurationCallback)}.
  * Based on http://www.requirejs.org/jqueryui-amd/example/webapp/app.html.
  */
+@Import(module = "validate-in-error")
 public class ModuleConfigurationCallbackDemo
 {
 
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleInitDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleInitDemo.java
index 7c20afe1c..c39959ed7 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleInitDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ModuleInitDemo.java
@@ -1,16 +1,16 @@
 package org.apache.tapestry5.integration.app1.pages;
 
 
-import org.apache.tapestry5.annotations.Environmental;
-import org.apache.tapestry5.services.javascript.JavaScriptSupport;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.annotations.Inject;
 
 public class ModuleInitDemo
 {
-    @Environmental
-    private JavaScriptSupport javaScriptSupport;
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
 
     void afterRender()
     {
-        javaScriptSupport.require("app/alert").invoke("alert").with("Module Initialization Demo");
+        requireJsModeHelper.importModule("app/alert").invoke("alert").with("Module Initialization Demo");
     }
 }
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
index 7af7bcc61..898dcccf5 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MultiZoneUpdateDemo.java
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.integration.app1.pages;
 
 import org.apache.tapestry5.Block;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.ValueEncoder;
 import org.apache.tapestry5.ajax.MultiZoneUpdate;
 import org.apache.tapestry5.annotations.InjectComponent;
@@ -24,6 +25,8 @@ import org.apache.tapestry5.internal.services.StringValueEncoder;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;
 import org.apache.tapestry5.services.ajax.JavaScriptCallback;
+import org.apache.tapestry5.services.javascript.AbstractInitialization;
+import org.apache.tapestry5.services.javascript.Initialization;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
 import java.util.Date;
@@ -46,7 +49,7 @@ public class MultiZoneUpdateDemo
     {
         return new Date();
     }
-
+    
     Object onActionFromUpdate()
     {
         wilmaMessage = "His Wife, Wilma.";
@@ -58,7 +61,12 @@ public class MultiZoneUpdateDemo
         {
             public void run(JavaScriptSupport javascriptSupport)
             {
-                javascriptSupport.require("app/multi-zone-update").with("message", "Updated");
+                final String moduleName = "app/multi-zone-update";
+                final AbstractInitialization<?> initialization = 
+                        javascriptSupport.isRequireJsEnabled() ?
+                        javascriptSupport.require(moduleName) :
+                        javascriptSupport.importEsModule(moduleName);
+                initialization.with("message", "Updated");
             }
         });
 
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java
index 3694f8ccc..cfe7c92d8 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteDemo.java
@@ -30,12 +30,12 @@ import org.apache.tapestry5.commons.services.TypeCoercer;
 import org.apache.tapestry5.integration.app1.data.ProgrammingLanguage;
 import org.apache.tapestry5.internal.OptionGroupModelImpl;
 import org.apache.tapestry5.internal.OptionModelImpl;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.util.AbstractSelectModel;
 import org.apache.tapestry5.util.EnumSelectModel;
 import org.apache.tapestry5.util.EnumValueEncoder;
 
-@Import(module="palette-demo")
 public class PaletteDemo
 {
     @Inject
@@ -52,6 +52,13 @@ public class PaletteDemo
     @Inject
     private TypeCoercer typeCoercer;
 
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
+    void beginRender()
+    {
+        requireJsModeHelper.importModule("palette-demo");
+    }
 
     void onPrepareFromDemo()
     {
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteGroupedDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteGroupedDemo.java
index 0fe130ba7..efd65a18c 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteGroupedDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PaletteGroupedDemo.java
@@ -30,20 +30,22 @@ import org.apache.tapestry5.commons.services.TypeCoercer;
 import org.apache.tapestry5.integration.app1.data.ProgrammingLanguage;
 import org.apache.tapestry5.internal.OptionGroupModelImpl;
 import org.apache.tapestry5.internal.OptionModelImpl;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
 import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.util.AbstractSelectModel;
 import org.apache.tapestry5.util.EnumValueEncoder;
 
-@Import(module="palette-demo")
 public class PaletteGroupedDemo
 {
     @Inject
     private ComponentResources resources;
+    
+    @Inject private RequireJsModeHelper requireJsModeHelper;
 
     @Persist
     @Property
     private List<ProgrammingLanguage> languages;
-
+    
     @Persist
     @Property
     private boolean reorder;
@@ -55,6 +57,11 @@ public class PaletteGroupedDemo
             ProgrammingLanguage.ERLANG, ProgrammingLanguage.HASKELL, ProgrammingLanguage.LISP);
     private static final Iterable<ProgrammingLanguage> OO = Arrays.asList(ProgrammingLanguage.JAVA,
             ProgrammingLanguage.RUBY);
+    
+    void beginRender()
+    {
+        requireJsModeHelper.importModule("palette-demo");
+    }
 
     void onPrepareFromDemo()
     {
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PublishEventDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PublishEventDemo.java
index 9400d49aa..aa8e8dac8 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PublishEventDemo.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PublishEventDemo.java
@@ -1,4 +1,4 @@
-// Copyright 2016 The Apache Software Foundation
+// Copyright 2017, 2025 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.
@@ -25,15 +25,44 @@
 // limitations under the License.
 package org.apache.tapestry5.integration.app1.pages;
 
+import org.apache.tapestry5.Asset;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.OnEvent;
+import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.annotations.PublishEvent;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 
-@Import(stack = "core", library = "PublishEventDemo.js")
 public class PublishEventDemo
 {
 
+    @Inject
+    private JavaScriptSupport javaScriptSupport;
+    
+    @Inject
+    @Symbol(SymbolConstants.REQUIRE_JS_ENABLED)
+    private boolean requireJsEnabled;
+    
+    @Inject
+    @Path("PublishEventDemo.js")
+    private Asset publishEventDemoAmdAsset;
+    
+    void beginRender()
+    {
+        if (requireJsEnabled)
+        {
+            javaScriptSupport.importStack("core");
+            javaScriptSupport.importJavaScriptLibrary(publishEventDemoAmdAsset);
+        }
+        else
+        {
+            javaScriptSupport.importEsModule("publish-event-demo");
+        }
+    }
+
     @PublishEvent
     JSONObject onAction()
     {
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ValidateInErrorEvent.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ValidateInErrorEvent.java
index 645703abb..e70308884 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ValidateInErrorEvent.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ValidateInErrorEvent.java
@@ -1,10 +1,18 @@
 package org.apache.tapestry5.integration.app1.pages;
 
-import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper;
+import org.apache.tapestry5.ioc.annotations.Inject;
 
-@Import(module = {"validate-in-error"})
 public class ValidateInErrorEvent {
     @Property
     private String value;
+    
+    @Inject
+    private RequireJsModeHelper requireJsModeHelper;
+    
+    void beginRender() {
+        requireJsModeHelper.importModule("validate-in-error");
+    }
+    
 }
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java
index d067f8cac..173707245 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/services/AppModule.java
@@ -187,10 +187,15 @@ public class AppModule
         // This is the emphasis of testing at this point.
         configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "jquery");
 
-        configuration.add(D3_URL_SYMBOL, "cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.js");
+        configuration.add(D3_URL_SYMBOL, "cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js");
         configuration.add(SymbolConstants.PRELOADER_MODE, PreloaderMode.ALWAYS);
         configuration.add(SymbolConstants.OPENAPI_APPLICATION_VERSION, "1.2.3.4");
         configuration.add(SymbolConstants.OPENAPI_BASE_PATH, "/rest/");
+        
+        configuration.add(SymbolConstants.CORS_ENABLED, "true");
+        configuration.add(SymbolConstants.CORS_ALLOWED_ORIGINS, "https://cdnjs.cloudflare.com");
+        configuration.add(SymbolConstants.CORS_MAX_AGE, "60");
+        
 //        configuration.add(SymbolConstants.ERROR_CSS_CLASS, "yyyy");
 //        configuration.add(SymbolConstants.DEFAULT_STYLESHEET, "classpath:/org/apache/tapestry5/integration/app1/app1.css");
 //        configuration.add(SymbolConstants.CORS_ENABLED, true);
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImplTest.java
index 0e41bc12e..055dcac92 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImplTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImplTest.java
@@ -32,42 +32,42 @@ public class EsModuleManagerImplTest
     @Test
     public void test_null_arguments()
     {
-        assertEquals(convert(null, true), "");
-        assertEquals(convert(null, false), "");
+        assertEquals(convert(JSONObject.NULL, true), "null");
+        assertEquals(convert(JSONObject.NULL, false), "null");
     }
     
     @Test
     public void test_empty_arguments()
     {
-        assertEquals(convert(new Object[0], true), "");
-        assertEquals(convert(new Object[0], false), "");
+        assertEquals(convert(new JSONArray(), true), "");
+        assertEquals(convert(new JSONArray(), false), "");
     }
     
     @Test
     public void test_one_argument()
     {
-        assertEquals(convert(new Object[] {null}, false), null);
+        assertEquals(convert(new JSONArray(JSONObject.NULL), false), "null");
         
-        assertEquals(convert(new Object[] {STRING}, false), quote(STRING));
+        assertEquals(convert(new JSONArray().put(STRING), false), quote(STRING));
         
-        assertEquals(convert(new Object[] {NUMBER}, false), NUMBER.toString());
-        assertEquals(convert(new Object[] {Boolean.TRUE}, false), Boolean.TRUE.toString());
-        assertEquals(convert(new Object[] {Boolean.FALSE}, false), Boolean.FALSE.toString());
+        assertEquals(convert(new JSONArray(NUMBER), false), NUMBER.toString());
+        assertEquals(convert(new JSONArray(Boolean.TRUE), false), Boolean.TRUE.toString());
+        assertEquals(convert(new JSONArray(Boolean.FALSE), false), Boolean.FALSE.toString());
 
-        assertEquals(convert(new Object[] {JSON_LITERAL}, false), quote(JSON_LITERAL.toString()));
+        assertEquals(convert(new JSONArray(JSON_LITERAL), false), quote(JSON_LITERAL.toString()));
 
-        assertEquals(convert(new Object[] {JSON_ARRAY}, false), JSON_ARRAY.toString(false));
-        assertEquals(convert(new Object[] {JSON_ARRAY}, true), JSON_ARRAY.toString(true));
+        assertEquals(convert(new JSONArray(JSON_ARRAY), false), JSON_ARRAY.toString(false));
+        assertEquals(convert(new JSONArray(JSON_ARRAY), true), JSON_ARRAY.toString(true));
 
-        assertEquals(convert(new Object[] {JSON_OBJECT}, false), JSON_OBJECT.toString(false));
-        assertEquals(convert(new Object[] {JSON_OBJECT}, true), JSON_OBJECT.toString(true));
+        assertEquals(convert(new JSONArray(JSON_OBJECT), false), JSON_OBJECT.toString(false));
+        assertEquals(convert(new JSONArray(JSON_OBJECT), true), JSON_OBJECT.toString(true));
         
     }
     
     @Test
     public void test_multiple_arguments()
     {
-        Object[] arguments = new Object[] { null, STRING, JSON_LITERAL, JSON_ARRAY, JSON_OBJECT };
+        JSONArray arguments = new JSONArray(JSONObject.NULL, STRING, JSON_LITERAL, JSON_ARRAY, JSON_OBJECT);
         final String format = "null, '%s', '%s', %s, %s";
         
         assertEquals(convert(arguments, false), 
@@ -84,10 +84,15 @@ public class EsModuleManagerImplTest
     {
         return "'" + string + "'";
     }
+    
+    private String convert(Object object, boolean compactJSON) 
+    {
+        return convert(new JSONArray(object), compactJSON);
+    }
 
-    private String convert(Object[] blah, boolean compactJSON) 
+    private String convert(JSONArray array, boolean compactJSON) 
     {
-        return EsModuleManagerImpl.convertToJsFunctionParameters(blah, compactJSON);
+        return EsModuleManagerImpl.convertToJsFunctionParameters(array, compactJSON);
     }
     
 }
diff --git a/tapestry-core/src/test/resources/META-INF/assets/ExpressionInJsFunction.js b/tapestry-core/src/test/resources/META-INF/assets/ExpressionInJsFunction.js
index 3cd15539d..20df3d163 100644
--- a/tapestry-core/src/test/resources/META-INF/assets/ExpressionInJsFunction.js
+++ b/tapestry-core/src/test/resources/META-INF/assets/ExpressionInJsFunction.js
@@ -1,4 +1,4 @@
-require(["t5/core/dom"], function (dom) {
+function runTest(dom) {
 	
 	dom = dom.default;
 
@@ -9,5 +9,12 @@ require(["t5/core/dom"], function (dom) {
     window.test_func_with_map = function () {
         dom('target').value("{key=test2}");
     };
-});
+}
 
+const requireJsEnabled = "true" == document.querySelector("html")?.dataset['requireJsEnabled'];
+if (requireJsEnabled) {
+	require(["t5/core/dom"], runTest);	
+}
+else {
+	import("t5/core/dom").then(runTest);
+}
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/multi-zone-update.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/multi-zone-update.js
new file mode 100644
index 000000000..411363128
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/multi-zone-update.js
@@ -0,0 +1,5 @@
+import dom from "t5/core/dom";
+
+export default function (id, message) {
+	dom(id).update(message);
+};
\ No newline at end of file
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js
new file mode 100644
index 000000000..7728ef0c1
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js
@@ -0,0 +1,14 @@
+// Provide test support functions that can be addressed via Selenium.
+
+// TODO: Maybe move this to main, for external re-use?
+
+import dom from "t5/core/dom";
+
+const exports = {
+  findCSSMatchCount(selector) { return dom.body.find(selector).length; },
+  doesNotExist(elementId) { return (dom(elementId)) === null; }
+};
+
+window.testSupport = exports;
+
+export default exports;
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/client-console-demo.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/client-console-demo.js
new file mode 100644
index 000000000..33df7e83a
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/client-console-demo.js
@@ -0,0 +1,6 @@
+import dom from "t5/core/dom";
+import console from  "t5/core/console";
+
+for (var name of ["debug", "info", "warn", "error"]) {
+  ((name => (dom(name)).on("change", function() { return console[name](this.value()); })))(name);
+}
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/palette-demo.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/palette-demo.js
new file mode 100644
index 000000000..5943f74b6
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/palette-demo.js
@@ -0,0 +1,14 @@
+import dom from "t5/core/dom"
+import events from "t5/core/events";
+import _ from "underscore"
+import console from "t5/core/console";
+
+dom.onDocument(events.palette.willChange, function(event, memo) {
+
+  console.info("palette-demo, palette willChange");
+
+  const values = _.map(memo.selectedOptions, o => o.value);
+
+  (dom("event-selection")).update(JSON.stringify(values));
+  return (dom("event-reorder")).update(memo.reorder.toString());
+});
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/publish-event-demo.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/publish-event-demo.js
new file mode 100644
index 000000000..7be8d6c5c
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/publish-event-demo.js
@@ -0,0 +1,23 @@
+import dom from "t5/core/dom";
+import ajax from "t5/core/ajax";
+import jQuery from "jquery";
+	
+function makeAjaxCall(eventName, eventElement, outputElement) {
+	ajax(eventName, { 
+		element: eventElement,
+		success: function(response) {
+			outputElement.innerHTML = response.json.origin;
+		} 
+	});
+}
+
+jQuery('tbody tr').each(function() {
+	var td = jQuery(this).find('td');
+	var eventName = td[1].innerHTML;
+	var elementId = td[0].innerHTML;
+	var eventElement = null;
+	if (elementId != '(no element)') {
+		eventElement = jQuery('#' + elementId);
+	}
+	makeAjaxCall(eventName, eventElement, td[3]);
+});
diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/validate-in-error.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/validate-in-error.js
new file mode 100644
index 000000000..55f4777fe
--- /dev/null
+++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/validate-in-error.js
@@ -0,0 +1,8 @@
+import dom from 't5/core/dom';
+import events from 't5/core/events';
+
+dom.onDocument(events.form.validateInError, 'form', function() {
+  const attributes =
+    {id: 'validate-in-error'};
+  this.prepend(dom.create('div', attributes, 'Validate in error'));
+});
diff --git a/tapestry-core/src/test/resources/META-INF/assets/zonedemo.js b/tapestry-core/src/test/resources/META-INF/assets/zonedemo.js
index 9ab069ff9..015dfe198 100644
--- a/tapestry-core/src/test/resources/META-INF/assets/zonedemo.js
+++ b/tapestry-core/src/test/resources/META-INF/assets/zonedemo.js
@@ -3,5 +3,23 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
  */
-require(["t5/core/dom", "t5/core/events"],
-  (dom, events) => dom.default.onDocument(events.default.zone.didUpdate, () => (dom.default("zone-update-message")).update("Zone updated.")));
+
+function execute(dom, events) {
+	dom.default.onDocument(events.default.zone.didUpdate, function() {
+		dom.default("zone-update-message").update("Zone updated.");
+	});
+}
+
+if (typeof require !== 'undefined') {
+	require(["t5/core/dom", "t5/core/events"], function(dom, events) {
+		execute(dom, events);
+	});
+	  
+}
+else {
+	import("t5/core/dom").then((dom) => { 
+		import("t5/core/events").then((events) => {
+			execute(dom, events);
+		});
+	});
+}
\ No newline at end of file
diff --git a/tapestry-core/src/test/resources/META-INF/modules/client-console-demo.js b/tapestry-core/src/test/resources/META-INF/modules/client-console-demo.js
index ebca6c142..56f5c9478 100644
--- a/tapestry-core/src/test/resources/META-INF/modules/client-console-demo.js
+++ b/tapestry-core/src/test/resources/META-INF/modules/client-console-demo.js
@@ -1,11 +1,9 @@
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
- */
 define(["t5/core/dom", "t5/core/console"],
   function(dom, console) {
 
+	dom = dom.default;
+	console = console.default;
+	
     for (var name of ["debug", "info", "warn", "error"]) {
       ((name => (dom(name)).on("change", function() { return console[name](this.value()); })))(name);
     }
diff --git a/tapestry-core/src/test/resources/META-INF/modules/palette-demo.js b/tapestry-core/src/test/resources/META-INF/modules/palette-demo.js
index 5e0f73f6e..ceb4b39e7 100644
--- a/tapestry-core/src/test/resources/META-INF/modules/palette-demo.js
+++ b/tapestry-core/src/test/resources/META-INF/modules/palette-demo.js
@@ -1,8 +1,3 @@
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
- */
 define(["t5/core/dom", "t5/core/events", "underscore", "t5/core/console"],
   (dom, events, _, console) => {
 	dom = dom.default;
diff --git a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.js b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.js
index 5b6a4cb05..2eb6c0421 100644
--- a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.js
+++ b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.js
@@ -1,4 +1,5 @@
-var sampleSVG = d3.select("#viz")
+function run(d3) {
+  var sampleSVG = d3.select("#viz")
         .append("svg")
         .attr("width", 100)
         .attr("height", 100);    
@@ -10,4 +11,15 @@ var sampleSVG = d3.select("#viz")
         .attr("cx", 50)
         .attr("cy", 50)
         .on("mouseover", function(){d3.select(this).style("fill", "aliceblue");})
-        .on("mouseout", function(){d3.select(this).style("fill", "white");});
\ No newline at end of file
+        .on("mouseout", function(){d3.select(this).style("fill", "white");});
+		
+}
+
+if (typeof require !== 'undefined') {
+	require(["http://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js"], function(d3) {
+		run(d3);
+	})
+}
+else {
+	import("http://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.js").then(ignored => run(d3));
+}
\ No newline at end of file
diff --git a/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java b/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java
index 6481bc3d2..42b2fb423 100755
--- a/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java
+++ b/tapestry-upload/src/main/java/org/apache/tapestry5/upload/components/Upload.java
@@ -23,6 +23,7 @@ import org.apache.tapestry5.corelib.base.AbstractField;
 import org.apache.tapestry5.corelib.mixins.RenderDisabled;
 import org.apache.tapestry5.http.services.Request;
 import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.services.FieldValidatorDefaultSource;
 import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.upload.services.MultipartDecoder;
@@ -59,6 +60,10 @@ public class Upload extends AbstractField
 
     @Inject
     private Locale locale;
+    
+    @Inject
+    @Symbol(SymbolConstants.REQUIRE_JS_ENABLED)
+    private boolean requireJsEnabled;
 
     @SuppressWarnings("unused")
     @Mixin
@@ -123,7 +128,8 @@ public class Upload extends AbstractField
     {
         formSupport.setEncodingType(MULTIPART_ENCTYPE);
 
-        writer.element("input", "type", "file", "name", getControlName(), "id", getClientId(), "class", cssClass);
+        final String clientId = getClientId();
+        writer.element("input", "type", "file", "name", getControlName(), "id", clientId, "class", cssClass);
 
         validate.render(writer);
 
@@ -134,7 +140,15 @@ public class Upload extends AbstractField
         // TAPESTRY-2453
         if (request.isXHR())
         {
-            javaScriptSupport.require("t5/core/injected-upload").with(getClientId());
+            final String moduleName = "t5/core/injected-upload";
+            if (requireJsEnabled)
+            {
+                javaScriptSupport.require(moduleName).with(clientId);
+            }
+            else
+            {
+                javaScriptSupport.importEsModule(moduleName).with(clientId);
+            }
         }
     }