Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Fix cwiki-test attachment urls

JavaScript Modules are a mechanism for bringing modern concepts of variable scope and dependency management to JavaScript. Starting with version 5.4, Tapestry uses RequireJS  modules internally, and provides support for using RequireJS modules in your own Tapestry application.

Div
stylefloat:right
titleRelated Articles
classaui-label
Content by Label
showLabelsfalse
showSpacefalse
titleRelated Articles
cqllabel = "javascript" and space = currentSpace()

The Need for Modules

As web applications have evolved, the use of JavaScript in the client has expanded almost exponentially. This has caused all kinds of growing pains, since the original design of the web browser, and the initial design of JavaScript, was never intended for this level of complexity. Unlike Java, JavaScript has no native concept of a "package" or "namespace" and has the undesirable tendency to make everything a global.

In the earliest days, client-side JavaScript was constructed as libraries that would define simple functions and variables:

Code Block
languagejs
 
function onclickHelp(event) {
  if (helpModal === undefined) {
    helpModal = ...
  }
  document.getElementById("modalContainer") ...
}
 
$("#helpButton").click(onClickHelp);

...

Code Block
languagejs
(function() {
  var helpModal = null;
 
  function onClickHelp(event) { ... }
 
  $("#helpButton").click(onClickHelp);
})();

...

The old-school route is to choose a hopefully unique prefix, building a cumbersome name (perhaps myapp_onClickHelp), and attach that to the global window object. But that just makes your code that much uglier, and leaves you open to problems if not all members of your development team understand the rules and prefixes.

Enter the Asynchronous Module Definition. The AMD is pragmatic way to avoid globals, and adds a number of bells and whistles that can themselves be quite important.

...

Code Block
languagejs
titleModule t5/core/confirm-click
(function() {
  define(["jquery", "./events", "./dom", "bootstrap/modal"], function($, events, dom) {
    var runDialog;
    runDialog = function(options) {
      ...
    };
    $("body").on("click", "[data-confirm-message]:not(.disabled)", function() {
      ...
    });
    dom.onDocument("click", "a[data-confirm-message]:not(.disabled)", function() {
      ...
    });
    return {
      runDialog: runDialog
    };
  });
}).call(this);


Info

The above code is actually the compiled output of a CoffeeScript source file. The confirm-click module is used to raise a modal confirmation dialog when certain buttons are clicked; it is loaded by the Confirm mixin.

...

confirm-click defines a local function, runDialog. It performs some side-effects, attaching event handlers to the body and the document. ItThe module's export is a JavaScript object containing a function that allows other modules to raise the modal dialog.

If a module truly exports only a single function and is unlikely to change, then it is acceptable to just return the function itself, not an object containing the function. However, returning an object makes it easier to expand the responsibilities of confirm-click in the future; perhaps to add a dismissDialog function.

Location of Modules

Modules are stored as a special kind of Tapestry asset. On the server, modules are stored on the class path under META-INF/modules. In a typical environment, that means the sources will be in src/main/resources/META-INF/modules.

...

If you are using the optional tapestry-web-resources module (that's a server-side module, not an AMD module), then you can write your modules as CoffeeScript files (or TypeScript, starting in Tapestry 5.5); Tapestry will take care of compiling them to JavaScript as necessary.

The service ModuleManager is the central piece of server-side support for modules. It supports overriding of existing modules by contributing overriding module definitions. This can be useful to monkey patch an existing module supplied with Tapestry, or as part of a third-party library.

Loading Modules from Tapestry Code

Often, you will have a Tapestry page or component that defines client-side behavior; such a component will need to load a module.

...

Code Block
languagejava
  @Environmental
  JavaScriptSupport javaScriptSupport;
 
  ...
 
  javaScriptSupport.require("my-module").with(clientId, actionUrl);
 
  ...
 
  javaScriptSupport.require("my-module").invoke("setup").with(clientId, actionUrl);

In the first example, my-module exports a single function of two parameters. In the second example, my-module exports an object and the setup key is the function that is invoked.

Development Mode

In development mode, Tapestry will write details into the client-side console.

Image RemovedImage Added

This lists modules explicitly loaded (for initialization), but does not include modules loaded only as dependencies. You can see more details about what was actually loaded using view source; RequireJS adds <script> tags to the document to load libraries and modules.

Libraries

...

versus Modules

Tapestry still supports JavaScript libraries.  When the page is loading, all libraries are loaded before any modules.

...

Libraries work in both normal page rendering, and Ajax partial page updates. Even in partial page updates, the libraries will be loaded sequentially before modules are loaded or exported functions invoked.

Aggregating Modules

An important part of performance for production applications is JavaScript aggregation.

In development mode, you want your modules and other assets to load individually. For both CSS and JavaScript, smaller files that align with corresponding server-side files makes it much easier to debug problems.

Unlike assets, modules can't be fingerprinted, so on each page load, the client browser must ask the server for the module again 's contents frequently (typically getting a 304 Not Modified response).

This is acceptable in development mode, but quite undesirable in production.

Note

By default, Tapestry sets a max age of 60 (seconds) on modules, so you won't see module requests on every page load. This is configurable and you may want a much higher value in production. If you are rapidly iterating on the source of a module, you may need to force the browser to reload after clearing local cache. Chrome has an option to disable the client-side cache when its developer tools are open.

With JavaScript aggregation, the module can be included in the single virtual JavaScript library that represents a JavaScript stack. This significantly cuts down on both the number of requests from the client to the server, and the overall number of bytes transferred.

...

  • @Contribute indicates we are contributing to a JavaScriptStack service
  • Since there are (or at least, could be) multiple services that implement JavaScriptStack, we provide the @Core annotation to indicate which one we are contributing to (this is a marker annotation, which exists for this exact purpose)
  • It is possible to contribute libraries, CSS files, other stacks, and modules; here we are contributing modules
  • Each contribution has a unique id and a StackExtension value

...