Skip to content
This repository has been archived by the owner on Jan 22, 2024. It is now read-only.

The Server side AMD Config file

Chuck Dumont edited this page Apr 27, 2016 · 75 revisions

The server-side AMD configuration is a JavaScript object that closely mirrors the client-side AMD configuration as detailed on the RequreJS web site and defines the paths and packages that relate AMD module identifiers to JavaScript and other resources, however, instead of specifying web resources, the server-side AMD config specifies URIs to resources on the server. The config file itself is specified as a resource URI. If the URI is not absolute (i.e. protocol specified), then the it is assumed to reside in the OSGi bundle that contains the plugin.xml defining the servlet. Server-side resources are specified using Universal Resource Identifiers (URIs) as specified by the java.net.URI class. The Aggregator provides support for URIs using the standard file protocol for files that reside on the server's file system, and the non-standard, Aggregator defined, namedbundleresource protocol for resources residing in OSGi bundles.

The URI to the server-side AMD config file is specified using the config servlet init-param in the <servlet> element defining the servlet.

The namedbundleresource protocol

The namedbundleresource protocol allows resources to be specified using the bundle symbolic name that the resource is contained in. A namedbundleresource URI looks similar to the following:

namedbundleresource://com.ibm.jaggr.sample.dojo/WebContent/dojo-release-1.8.0-src/dojo

which specifies the resource path /WebContent/dojo-release-1.8.0-src/dojo within the OSGi bundle with the symbolic name com.ibm.jaggr.sampled.dojo. URIs specified using the namedbundleresource protocol are not universal because multiple resources residing in different bundles using the same symbolic name may exist, and which resource the URI resolves to depends on the behavior of the OSGi class loader as it relates to resolving bundles. This ambiguity violates the rules governing URIs. It is, never the less, convenient to be able to specify resources that reside in named bundles using the URI syntax, so the Aggregator supports the use of the namedbundleresource protocol for URIs specified within the server-side AMD config file. The ambiguity around which bundle to use is dealt with by ensuring that the class loader of the application's bundle (the bundle containing the plugin.xml defining the servlet) is always used to load the bundle whose name is specified in the host part of the URI.

Other URI protocols

The Aggregator may be extended using the com.ibm.jaggr.service.resourcefactory extension point to support additional URI protocols, providing the ability to extend support to include resources residing within jar files, databases, etc. The default file and namedbundleresource protocols are supported using classes that implement this extension.

Variable property substitution

Strings of the form ${propertyName} may appear anywhere within the config JavaScript and will be replaced with the value of the named property. The value is obtained by calling BundleContext.getProperty() using the BundleContext of the contributing bundle, or if the named property is not defined there, then by calling any registered OSGi services that support the com.ibm.jaggr.service.IVariableResolver interface. Variable property substitution is performed before script evaluation. If a value cannot be found for a named property, then the string is left un-modified.

Scoped variables

When the server-side config javascript is being evaluated, the following JavaScript variables are defined and in scope, and may be referenced by JavaScript expressions in your config:

initParams
An object of the name-value pairs defined in the servlet-init-params
options
An object of the name-value pairs defined in the Aggregator options
headers
An object of the name-value pairs specified in the bundle manifest for the contributing bundle (i.e. the bundle that instantiated the servlet)
console
An object containing logging methods such as error, warn, info and log. These methods can be used to write messages to the server logs from javascript code in the config
getProperty
A function that returns the value of the named property. This function can be used as an alternative to Variable Property Substitution. The only difference is in when the evaluation is performed (script execution time vs. config file load time) and the result when the property does not have a value (null vs. the original string).
getBundleVersionsHash (version 1.2)
A function that returns an MD5 hash of the bundle header properties (default: Bundle-Version) for the specified bundles. If the first argument is an array, then the array specifies the list of bundle header properties to include, followed by the bundle names. Syntax is:

getBundleVersionsHash('com.acme.bundle1', 'com.acme.bundle2', ...);

getBundleVersionsHash(['Bundle-Version', 'Bnd-LastModified'], 'com.acme.bundle1', 'com.acme.bundle2', ...);

It is convenient to assign the value returned by the function to the cacheBust config property because the value will automatically change whenever the values of any of the bundle headers in any of the specified bundles changes.

The function recognizes a period when used as a bundle name to indicate the current, or contributing bundle. This is the bundle who's plugin.xml contains the servlet tag that instantiated the aggregator.

See the sample application's server-side AMD config file, testaggr-config.js for example usage of this property.

This function is new in version 1.2.

Extending the config scope

In addition to the functions and properties listed above, the config scope may be extended by third parties using the com.ibm.jaggr.core.config.IConfigScopeModifier interface. Implementations of this interface are registered with the OSGi service registry either through application code or via the com.ibm.jaggr.service.serviceprovider extension point. The getBundleVersionsHash() function is implemented this way and may be used as an example of how to extend the config scope.

This feature is new in version 1.2

Override URIs

The baseUrl, paths and location properties specify resource URIs which may be either a string value, or a two element array of strings specifying primary and override URIs. The idea behind override URIs is to facilitate product customization by supporting a location on the file system where customized resources may be placed. Resources that exist in the override locations will be used instead of the corresponding resources in the location specified by the primary URI. In this scenario, the primary URI typically specifies an OSGi bundle, using the namedbundleresource URI scheme, while the override URI specifies a customization area on the server's file system.

Config properties

 

Property Name Description
baseUrl The base URI to use for all relative URIs specified in this config. If baseUrl is not absolute, then it is assumed to be relative to the root of the bundle defining the servlet. Note that the property name specifies Url, with a lower-case L, instead of URI with an upper-case i, to maintain symmetry with the client-side config. This property may specify a single URI or primary and override URIs as described in Override URIs.   Files and folders located under the folder specified by baseUrl are not scanned when the Aggregator builds the module dependency map used for require list expansion unless the depsIncludeBaseUrl property is specified with a value of true.
paths Path mappings for module names to resource URIs. The path URIs are assumed to be relative to baseUrl, unless the URI starts with a "/" or specfies a protocol. If the path URI starts with a "/", then the path is assumed to be relative to the root of the bundle defining the servlet. Path entries may specify a single URI or primary and override URIs as described in Override URIs.
packages Defines the packages for the application. A package can be assocated with a module name/prefix. The package config can specify the following properties for a specific package:
  • name: The name of the package (used for the module name/prefix mapping)
  • location: The location URI on the server. Locations are relative to baseUrl unless they contain a protocol or start with a front slash (/). If the location URI starts with a "/", then it is assumed to be relative to the root of the bundle defining the servlet. Location entries may specify a single URI or primary and override URIs as described in Override URIs.
  • main: The module id that should be used when someone does a require for the package. The default value is ./main, so you only need to specify it if it differs from the default. Note that unlike location, which specifies the URI to a resource on the server, main specifies a module id that is mapped to a server resource using the defined paths and packages.
aliases Specifies an array of vector pairs with alias mappings. Each array entry is a two element array with the first element specifying the string to replace, and the second element specify the replacement. Two forms of alias mappings are supported. The first form is a simple string substitution mapping with the first entry specifying the string to replace and the second specifying the string to replace it with. For example:
aliases: [
    ["text", "dojo/text"]
]

The second form allows for regular expression pattern mapping and the specification of the replacement as a string or as a function, For replacement functions, the has() function may be called to query the feature set specified in the request. Note that unlike the has() function on the client, this has() function returns either a boolean value, representing the value of the feature on the client after cooercing the value to a boolean type, or undefined if the feature was not specified in the request.

aliases: [
    [/^(.*)\/foo\/(.*)$/, "$1/bar/$2"]
    [/^(.*)\/backend\/(.*)$/, 
        function($0,$1,$2){
            return $1+(has("foo")?
               "/foo/":(has("bar")?
               "/bar/":"/backend/"))+$2;
        }
    ]
]

Server-side aliases are provided in support of client-side aliases, which are supported by some AMD loaders including the Dojo loader. In general, server-side and client-side aliases should produce the same mappings. Because client-side aliases are resolved on the client before modules are requested, server-side aliases are not used to find requested modules. Instead, they are used in dependency expansion, in order to locate dependent modules so that they can be expanded within a list of require dependencies (if doing Require list expansion or so that the dependent modules may be include in the response (if doing Server-side layer expansion. An important significance of this detail is that if the mappings produced by an alias resolver are conditional upon the feature set provided in the request, then those features need to be defined on the client when the request for the modules is made. When doing Require list expansion, the alias resolution on the server can occur well before the request for the aliased module itself. For this reason, server-side alias resolvers should be implemented in such a way that alias resolution fails (and the module path is not changed) if the tests for the conditioned feature(s) evaluates to undefined rather than assuming the feature is false. This will cause the aliased resource's dependencies to not be expanded in the require list (because it won't be found on the server) instead of potentially expanding the dependencies for the wrong module.

Regular expressions specified in alias mappings are evaluated using Java regular expression classes for performance reasons, so you need to be aware of the subtle differences between java and javascript regular expressions.

In addition to the JavaScript variables defined in Scoped variables above, the following variables are defined and in scope and may be referenced by your alias resolver function:

  • has - function to test if a specified feature is defined in the feature set provided with the request
  • <user-defined> - Any variables that you defined within your server-side config JavaScript and placed in global scope.
depsIncludeBaseUrl Optional. If specified with a value of true, then the files and folders under the directory specified by baseUrl will be scanned when the Aggregator builds the module dependency map used for Require list expansion. If false, then only the paths specified by the paths and packages properties are scanned. The default value is false.
depScanIncludeExtensions Optional. Specifies the file extensions, as an array of strings, of resources to include when scanning for dependencies and building the module id name map. The extensions js, html and css are included by default and don't need to be specified.

Module id name mapping is used to encode requested modules in Aggregator URLs. If a requested module is not included in the module id name map, then it will be requested by name. Requesting modules by name consumes much more URL space than requesting modules using mapped id, so adding the extensions of resource requested by your application using aggregator supported loader plugins can reduce URL lengths and eliminate request splitting due to URL length limits being exceeded.
expires Optional. Specifies the number of seconds in the future that cached responses in third-party caches should be validated by the server. Specifically, it specifies the value of the Cache-Control:max-age header that is returned with Aggregator responses. If this property is not specified, then no Cache-Control:max-age header is returned in the response.
coerceUndefinedToFalse If true, then the Aggregator will treat undefined features as if they were defined with a value of false. This applies to has.js feature trimming of javascript code. If this value is false, then has.js conditionals for undefined features are left unchanged. If the value is true, then conditionals involving undefined features are trimmed based on the value of the feature evaluating to false. The default value for this option is false.
cacheBust An arbitrary string specified by the application that is associated with the serialized meta-data for Aggregator caches and the module dependency maps. When these data structures are de-serialized on server restarts, the saved value is compared against the value that is read from the current config, and if the values don't match, then the de-serialized data is discarded and the caches and dependency maps are deleted and rebuilt.
notice

Specifies a URI to a resource containing arbitrary text that is included at the beginning of every Aggregator response. The notice text should be in the form of a JavaScript block comment. The Aggregator does not do any processing on the notice text.

If the URI is relative, it is assumed to be relative to the location of the config file.

textPluginDelegators Specifies an array of loader plugin module ids that delegate to the default text loader plugin. When performing server-side layer expansion of dependencies, dependencies that specify plugins included in this list will be expanded in the response as if they were required by the text plugin.
jsPluginDelegators

Specifies an array of loader plugin module names that delegate to the AMD loader for javascript modules. An example of such a plugin is the i18n loader plugin. When performing server-side layer expansion of dependencies, dependencies that specify plugins included in this list will be expanded in the response.

Note: Starting with version 1.2.1, the i18n plugin is no longer included in this property by default.

cachePrimerBundleName

Specifies the bundle symbolic name of an optional [cache-primer bundle](Caching#the-cache-primer-bundle). If specified, then the Aggregator will attempt to load the bundle at startup and use the contents of the bundle to prime the Aggregator cache if it is empty or stale.

This property was introduced in version 1.3

compilerOptions

Specifies Google Closure Compiler options to use when compiling JavaScript files. The options are specified as a property map of name/value pairs. The property names correspond to property names supported by the CompilerOptions class. The names can be any property which is either declared as a public field or has a setter method which can be mapped to the property name using standard JavaBean property naming convention. The value is an array who's number and types of elements matches the number and types of the formal parameters defined by the setter method. If the setter method takes exactly one parameter, or the property is a public field which has no setter method, then the array notation may be dispensed with and the single parameter may be specified directly as in the following examples:

compilerOptions: {
   /* 
    * calls 
    * setDefineToBooleanLiteral("defineName", true);
    */
   defineToBooleanLiteral:['defineName', true]  
}
compilerOptions: {
   /* 
    * this is identical to acceptConstKeyword:[true].
    * Both will invoke setAcceptConstKeyword(true);
    */
   acceptConstKeyword:true
}
compilerOptions: {
   /*
    * setAliasableStrings takes a java.util.Set, so
    * need to use a nested array so that the string 
    * set will be passed as a single parameter.
    */
   aliasableStrings:[['foo', 'bar']] 
}

The following JavaScript to Java property value conversions will be performed when attempting to match the provided values to the value types declared by the class:

  • Values specified as a JavaScript arrays will be converted to either a java.util.List or a java.util.Set as determined by the declared type of the receiving parameter.
  • If the declared type of the receiving parameter is an Enum and the provided value is a string which matches one of the Enum named constants, then the matching constant value will be used
compilerOptions: {
   /*
    * This invokes 
    * setCheckGlobalThisLevel(CheckLevel.WARNING);
    */
   checkGlobalThisLevel:'WARNING'
}

This property was introduced in version 1.3.7.

Aggregator extensions may support additional properties. For example, the CSS module builder supports config properties for controlling in-lining of image references and imported CSS.
  ## Config file example
{
    /*
     * Paths are relative the WebContent folder of the bundle containing this config.
     */
    baseUrl: ['WebContent', '${MYAPP_CUSTOMIZATIONS}'],
packages: [
    {
        name: 'dojo',
        location: ['namedbundleresource://org.dojo-1.8.res/WebContent/dojo', 'dojo'],
        lib: '.'
    },
    {
        name: 'dijit',
        location: ['namedbundleresource://org.dojo-1.8.res/WebContent/dijit', 'dijit'],
        lib: '.'
    },

    {
        name: 'dojox',
        location: ['namedbundleresource://org.dojo-1.8.res/WebContent/dojox', 'dojox'],
        lib: '.'
    }
],

paths: {
    "css": "js/css"   // uses override path derived from baseUrl (i.e. ${MYAPP_CUSTOMIZATIONS}/js/css)
},
expires: 3153600,
cacheBust: headers["Bundle-Version"]

}

Defining JavaScript variables that may be referenced by alias resolver functions

The following example shows how you can define a JavaScript function that can later be called from within an alias resolver function:

(function() {
    /* Define transform function in config scope */
    function transform(a, b, c) {
        a+(has("foo")?"/foo/":(has("bar")?"/bar/":c))+b
    };

    return {
        baseUrl: '...',
   
        packages: [...],

        aliases: [
            [/^(.*)\/backend\/(.*)$/, function($0,$1,$2){return transform($1, $2, '/backend/');}],
        ],
    };
})();