The Need for Modules
What's not apparent here is that function
onclickHelp() is actually attached to the global window object. Further, the variable
One approach to solving these kinds of problems is a hygienic function wrapper. The concept here is to define a function and immediately execute it. The functions and variables defined inside the function are private to that function.
This is an improvement in so far as it assists with name collisions. The variables and functions can only be referenced by name from inside the wrapper.
However, if you are building a library of code to reuse across your application (or building a library to share between applications) then something is still missing: a way to expose just the function you want from inside you wrapper to the outside world.
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.
Tapestry uses the RequireJS library as the client-side implementation of AMD. It supplements this on the server-side with Tapestry services for even more flexibility.
- Modules have a unique name, such as
- A module has a constructor function that exports a value.
- A module defines dependencies on any number of other modules.
- The export of each dependency is provided as a parameter to the constructor function.
Here's an example from Tapestry itself:
confirm-click module is used to raise a modal confirmation dialog when certain buttons are clicked; it is loaded by the Confirm mixin.
This module depends on several other modules:
bootstrap/modal. These other modules will have been loaded, and their constructor functions executed, before the
confirm-click constructor function is executed. The export of each module is provided as a parameter in the order in which the dependencies are defined.
bootstrap/modal module is loaded for side-effects.
confirm-click defines a local function,
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
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
Typically, your application will place it's modules directly in this folder. If you are writing a reusable library, you will put modules for that library into a subfolder to prevent naming conflicts. Tapestry's own modules are prefixed with
If you are using the optional
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.
The simplest approach is to use the Import annotation:
module attribute may either a single module name, or a list of module names.
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.
In development mode, Tapestry will write details into the client-side console.
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
Libraries are loaded sequentially, so if you can avoid using libraries, so much the better in terms of page load time.
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.
Unlike assets, modules can't be fingerprinted, so on each page load, the client browser must ask the server for the module's contents frequently (typically getting a 304 Not Modified response).
This is acceptable in development mode, but quite undesirable in production.
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.
Adding a module to the stack is not the same as
require-ing it. In fact, you must still use
Tapestry does not attempt to do dependency analysis; that is left as a manual exercise. Typically, if you aggregate a module, your should look at its dependencies, and aggregate those as well. Failure to do so will cause unwanted requests back to the Tapestry server for the dependency modules, even though the aggregated module's code is present.
To break this down:
- 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
The core stack includes several libraries and modules; the exact configuration is subject to a number of factors (such as whether Prototype or jQuery is being used as the underlying framework). That being said, this is the current list of modules aggregated into the core stack:
The optimum configuration is always a balancing act between including too little and including too much. Generally speaking, including too much is less costly than including too little. It is up to you to analyze the requests coming into your application and determine what modules should be aggregated.