Module Layer

This section defines what a JavaScript OSGi module (bundle) is and how it is structured.

A bundle is the OSGi term for a module and defines a minimal structural unit within the OSGi runtime. An application built on top of the JS OSGi runtime consists of 1-n bundles, which contain the code and the resources to build up the whole application. A bundle typically solves one concrete domain problem or provides a special tool. An OSGi bundle is normally reused in multiple applications.

The JavaScript OSGi runtime is based on Dojo Toolkit (http://dojotoolkit.org/) which is using the "Asynchronous Module Definition (AMD)" format (http://dojotoolkit.org/documentation/tutorials/1.7/modules/) since version 1.7. This format defines how source files are structured and how they are loaded. Base knowledge about this mechanism is required to fully understand this chapter.

As of map.apps 4.x next to the AMD module syntax the ES6 module syntax is supported but has to be transpiled to the AMD format.

Within the documentation most of the samples use the new ES6 syntax.

A typical bundle has following structure:

Bundle-Structure
\- <bundle-folder>                // equals the name of the bundle
    +- nls                      // i18n directory (see dojo i18n) optional
    |   +- de                   //  German i18n resource directory
    |   |   \-bundle.js         //  German i18n resource file
    |   \- bundle.js            // root i18n resource file (en)
    |
    +- manifest.json            // bundle metadata description (required)
                                // instead of manifest.json a package.json is also accepted
    |
    +- main.js                  // AMD package main file, normally used to trigger i18n loading (optional)
    +- module.js                // Bundle-Layer, main trigger for loading dependent JS files (optional)
    +- AClass.js                // A Implementation Class, with certain functionality (optional)
    +- AActivator.js            // An Activator Implementation Class (optional)

The important thing is the manifest.json file. The manifest.json file provides some meta information and defines the functionality provided by the bundle. A minimal manifest.json file might look like this:

Minimal manifest.json
{
  "name": "mybundle",       // the official name of the bundle, this should match the name of the folder (required)
  "version": "1.0",         // the version identifier of the bundle (required)
}

The name is used as an identifier of the bundle within the configuration and loading system and must be unique among all bundles. Each bundle has its own version number, which should be incremented if the bundle is changed.

Version Identifiers

The synax of version identifiers in map.apps follow the specification defined in Semantic Versioning 2.0 .

This means that any version consists of the three mandatory components:

  • MAJOR

  • MINOR

  • PATCH

The components are separated by a single period ('.').

Version identifiers

Valid: 3.1.0

Invalid: 3.1 (missing PATCH)

Version Identifiers can be extended by a pre-release identifier appending a hyphen and a series of dot-separated identifiers.

Version identifiers with pre-release identifier

3.1.0-alpha

3.1.0-alpha.1

Additionally, version Identifiers can be extended by a build metadata identifier by appending a plus sign and a series of dot-separated identifiers.

Version identifiers with build metadata identifier

3.1.0+20141230.r320964

3.1.0-alpha.1+20141230.r320964

It is recommended to use version numbers as specified in Semantic Version 2.0 on the semantical level, too. Citing the specification summary, this means:

Increment the:

  1. MAJOR version when you make incompatible API changes,

  2. MINOR version when you add functionality in a backwards-compatible manner, and

  3. PATCH version when you make backwards-compatible bug fixes.

Dependencies on other Bundles

If a bundle requires another bundle to be installed in the application to work correctly, this requirement should explicitly be expressed in the manifest.json file:

Dependencies to other bundles
{
  ...
  "dependencies": {
    "map" : "~4.0.0",    // <name> : <version range expression>
    "dojo" : "1.11.1"
  },

  ...
  "optionalDependencies": {
    "coordinatetransformer" : "^4.0.0"
  },
  ...
}

The JS OSGi runtime tests if the required bundles are installed and ensures that theses bundles are started before the bundle that is declaring the dependency. If a bundle has an implementation class dependency to another bundle (if it needs a class of another bundle) you have to declare this dependency to ensure that the needed class is loaded before the bundle is started.

In contrast to the Java OSGi specification, dependencies to other bundles are directly expressed using the bundle name. The JS OSGi implementation provides no package-level dependency expressions (Export-Packages, Import-Packages). Also the same bundle cannot be loaded in different versions in the same page.

Version Ranges

Different version range expressions are possible inside the dependencies declarations.

Since map.apps 3.1 two different styles of version range expressions exist:

  1. OSGi-like: This style, based on the OSGi specification, is the only one available before map.apps 3.1.

  2. npm-like: Introduced with the JS Registry in map.apps 3.1 this style uses the version range syntax defined by the "semver" tool of the Node Package Manager (npm).

OSGi-like Version Ranges

This style of defining version ranges is deprecated with map.apps 3.1.

Version ranges are expressed by an interval syntax:

Version Range Syntax
versionrange := '(' | '[' + version + ',' + version + ')' | ']';
version := major + '.' + minor + '.' + bugfix + '-'|'.' + POSTFIX;
major   := an integer number >= 0
minor   := an integer number >= 0 optional
bugfix  := an integer number >= 0 optional
POSTFIX := a string optional
Interval brace meaning
Brace Description

(

left opened not including interval

[

left closed including interval

)

right opened not including interval

]

right closed including interval

Some syntax examples
Sample Description

2.0.0

Exactly version 2.0.0 is required

[1.0.0,2.0.0]

Version 1.0.0 till 2.0.0 are allowed (including version 1.0.0 and 2.0.0)

(1.0.0,2.0.0)

All versions between 1.0.0 and 2.0.0 are allowed (not including the version 1.0.0 and 2.0.0)

[1.0.0,2.0.0)

All versions greater or equal to 1.0.0 and lower 2.0.0 are allowed (including 1.0.0 and not including 2.0.0)

[1.0.0,)

All versions greater or equal to 1.0.0 are allowed (including 1.0.0)

[,]

All versions are allowed (the default)

(,1.0.0)

All versions greater 0.0.0 and lower 1.0.0 are allowed.

A version consists of the well known parts, major number, minor number, bug fix number and a post fix. Only the major part must be defined, all other parts are optional.

A version is compared to another version using the following rules:

  1. compare the major number part, only if equal go to step 2

  2. compare the minor number part, only if equal go to step 3

  3. compare the bug fix number part, only if equal go to step 4

  4. compare the pre-release post fix part, using string compare. A version with a pre-release postfix is lower as the version without that postfix.

Comparing Samples
Sample Description

1 < 2

Major part is different

1.1 < 1.2

Minor part is different

1.1.1 < 1.1.2

Bug fix part is different

1.1.1-SNAPSHOT < 1.1.1

Special postfix SNAPSHOT is interpreted as pre-release version

1.1.1-PREVIEW < 1.1.1

Special postfix PREVIEW is interpreted as pre-release version

1.1.1-RC-1 < 1.1.1-RC-2

A postfix can have its own incremental number part

1 = 1.0

You can skip the minor, bugfix, and postfix part

1 = 1.0.0

You can skip the minor, bugfix, and postfix part

NPM-like Version Ranges

Specific Versions

1.2.3

Specific version 1.2.3. Build metadata is still ignored, so "1.2.3+build2012" satisfies this range.

Ranges

>1.2.3

Greater than 1.2.3 version.

<1.2.3

Less than 1.2.3. If there is no prerelease tag on the version range, no prerelease version is allowed either, even though these are technically "less than".

>=1.2.3

Greater than or equal to 1.2.3. Prerelease versions are NOT equal to their "normal" equivalents, so 1.2.3-beta does not satisfy this range, but 2.3.0-beta will.

<=1.2.3

Less than or equal to 1.2.3. In this case, prerelease versions ARE allowed, so 1.2.3-beta satisfies.

Intersections of Ranges

Ranges can be joined by whitespace to express an intersection ("and") of ranges.

>=3.1.0 <4.0.0

Greater than or equal to 3.1.0 and less than 4.0.0, so 3.2.0 is satisfied.

3.1.0 - 4.0.0

Equivalent to >=3.1.0 ⇐4.0.0, using special "hyphen" syntax

If ranges are joined using '||' (double pipe) it is interpreted as "or".

3.1.0 || 3.1.1

Version 3.1.0 or 3.1.1

3.1.0 || >= 3.1.2 <3.2.0

"3.1.0" or "greater or equal to 3.1.2 but less than 3.2.0"

Advanced Syntax

~1.2.3

Equivalent to >=1.2.3-0 <1.3.0-0. "Reasonably close to 1.2.3". When using tilde operators, prerelease versions are supported as well, but a prerelease of the next significant digit does NOT satisfy, so 1.3.0-beta does not satisfy ~1.2.3.

^1.2.3

Equivalent to >=1.2.3-0 <2.0.0-0. "Compatible with 1.2.3". When using caret operators, anything from the specified version (including prerelease) is supported up to, but not including, the next major version (or its prereleases). 1.5.1 satisfies ^1.2.3, while 1.2.2 and 2.0.0-beta does not.

^0.1.3

Equivalent to >=0.1.3-0 <0.2.0-0. "Compatible with 0.1.3". 0.x.x versions are special: the first non-zero component indicates potentially breaking changes, meaning the caret operator matches any version with the same first non-zero component starting at the specified version.

^0.0.2

Equivalent to =0.0.2. "Only the version 0.0.2 is considered compatible".

~1.2

Equivalent to >=1.2.0-0 <1.3.0-0 "Any version starting with 1.2".

^1.2

Equivalent to >=1.2.0-0 <2.0.0-0 "Any version compatible with 1.2".

1.2.x

Equivalent to >=1.2.0-0 <1.3.0-0 "Any version starting with 1.2".

~1

Equivalent to >=1.0.0-0 <2.0.0-0 "Any version starting with 1".

^1

Equivalent to >=1.0.0-0 <2.0.0-0 "Any version compatible with 1".

1.x

Equivalent to >=1.0.0-0 <2.0.0-0 "Any version starting with 1"

*

Equivalent to >= 0.0.0. Any range.

The OSGi format is also supported:

     [1.0,) := >=1.0.0
     (1.0,) := >1.0.0
     (,1.0] := <=1.0.0
     (,1.0) := <1.0.0
     [1.0,2.0] := >=1.0.0 <=2.0.0
     [1.0,2.0) := >=1.0.0 <2.0.0
     (1.0,2.0] := >1.0.0  <=2.0.0
     (1.0,2.0) := >1.0.0  <2.0.0
     (1.0,2.0) := >1.0.0  <2.0.0

Internationalization (i18N)

Add the following files to internationalize a bundle:

i18n Files
\- <bundle-folder>
    +- nls                      // i18n directory (see dojo i18n) optional
        +- de                   //  German i18n resource directory
        |   \-bundle.js         //  German i18n resource file
        \- bundle.js            // root i18n resource file (en)

The file structure is based on the dojo/i18n module explained at http://dojotoolkit.org/reference-guide/1.8/dojo/i18n.html.

The file /nls/bundle.js (root i18n file) contains the English resources, like:

/nls/bundle.js
// AMD
define({
  root: {
    bundleName: "My Bundle",
    bundleDescription: "The bundle provides some functionality."
  },
  "de": true, // german supported
  ... // list supported languages
});

// ES6 (NOTE: in i18n files, the new export default syntax is currently not supported)
module.exports = {
  root: {
    bundleName: "My Bundle",
    bundleDescription: "The bundle provides some functionality."
  },
  "de": true
};

The file /nls/de/bundle.js (german i18n file) contains the German resources, and does not need the root property:

/nls/de/bundle.js
//AMD
define({
  bundleName: "Mein Modul",
  bundleDescription: "Das Modul bietet Funktionen."
});


// ES6 (NOTE: in i18n files, the new export default syntax is currently not supported)
module.exports = {
  bundleName: "Mein Modul",
  bundleDescription: "Das Modul bietet Funktionen."
};

To trigger the load of the nls files you have to a add the line dojo/i18n!./nls/bundle.js to the dependencies of the main.js file:

i18n in main.js
//AMD
define(["dojo/i18n!./nls/bundle"],{});


//ES6
import "dojo/i18n!./nls/bundle";

With enabled i18n support it is possible to use i18n key replacement in manifest.json files. This means you can use expressions like ${i18nkey} to access i18n properties in the manifest.json file. The next sample shows how to use a language aware title:

i18n keys in manifest.json
{
  ...
  "title": "${bundleName}",
  ...
}

If you wish to access i18n properties from JavaScript code, see Module LifeCycle for a sample use in Activators and Declarative services for use in components.

If you wish to disable the i18n support, you have to specify this in the manifest.json file:

Disable i18n in manifest.json
{
  ...
  "i18n" : [], // disables i18n support for this bundle
  ...
}

The default declaration of the "i18n" property is:

Default i18n value in manifest.json
{
  ...
  "i18n" : ["bundle"],
  ...
}

So you can change the default name of the nls files by modifying this property, but it is not recommended.

Bundle JS Loading

By default a bundle has to provide a file named module.js which is loaded during start of the bundle. This file is considered to be the bundles "JavaScript Layer" file, which means that it triggers the loading of any further JavaScript file needed by the bundle.

The name of the file can be changed using the following property.

layer in manifest.json
{
  ...
  "layer": "module"
  ...
}

If you wish to disable the resolving of a layer file, you have to declare the property with an empty string:

Disable Bundle-Layer in manifest.json
{
  ...
  "layer": "" // disables the resolving of any JavaScript file
  ...
}

A module.js file typically looks like this:

module.js
//AMD
define([
  ".",
  "./MyClass"
  ],{});


//ES6
import ".";
import "MyClass";

In the following, the loading behavior and class name resolving are explained by an example.

Classloading Example

Consider the following bundle files:

Bundle-Structure
\- <bundle-folder>
    +- /nls/bundle.js  //i18n
    +- manifest.json
    +- main.js
    +- module.js
    +- Activator.js

With the content:

manifest.json
{ ...
  // An instance of class Activator should be created and informed about the bundle start and stop
  "activator": "Activator",
  ...
}
main.js
// AMD
// trigger load of i18n files
define(["dojo/i18n!./nls/bundle"],{});


//ES6
import "dojo/i18n!./nls/bundle";
Activator.js
// AMD
define(["dojo/_base/declare"], function(declare) {
    // declare the class Activator
    return declare([], {
      start: function(){},
      stop: function(){}
    });
  });


// ES6 (use default exports)
export default class {
    start(){}
    stop(){}
}

// ES6 (use named exports, requires that the file is named Activator.js)
export class Activator{
    start(){}
    stop(){}
}
module.js
// AMD
define([
  // trigger load of main.js
  ".",
  // trigger load of Activator.js
  "./Activator"
  ],{});


// ES6
import ".";
import "./Activator";

Each bundle is registered as an AMD package using the "name" value during loading. This means that all source files within a bundle should reference each other using relative path expressions in the AMD module declaration (define statement).

The special expression "." used within the define statements is resolved by the AMD loader to the file main.js. The module.js file is used to trigger the loading of the main.js and Activator.js. During the start of the bundle the JS OSGi Runtime checks the 'activator' property and resolves it using a 'require' statement. However, this does only work if the class is already loaded.

Restrict a bundle to a specific environment

A bundle can be restricted to a special execution environment using the following properties in the manifest.json file. If a browser does not match the execution requirement rules, the bundle is not started.

Execution Environments in manifest.json
{
  ...
  // Bundle supports Internet Explorer from version 7 and FireFox from version 3
  "requiredExecutionEnvironment": ["IE:[7,]","FF:[3,]"],
  ...
  // Bundle does not supports Opera Browsers
  "excludedExecutionEnvironment": ["Opera"]
  ...
}

The property 'requiredExecutionEnvironment' defines a (white) list of supported browsers and the 'excludedExecutionEnvironment' defines a (black) list of not supported browsers. Both are optional properties.

Each entry in the array of browsers has the following format:

Execution Environments in manifest.json
value := <browsername>(:<version-range>)?
Samples Description

FF:[3.1,4.0]

Firefox from version 3.1 to 4.0

FF:[1.0,4.0)

Firefox from version 1.0 to 4.0 (4.0 not included)

FF

Firefox any version

FF:[3,]

Firefox from version 3 up to any higher

Following browser names can be used:

Name Description

IE

Internet Explorer

Edge

Microsoft Edge

FF

Firefox

Chrome

Google Chrome

Khtml

KHTML browsers

Mozilla

Mozilla-based browsers (Firefox, SeaMonkey)

Opera

Opera

Safari

Safari based browsers, Safari or iPhone

WebKit

WebKit based browser (such as Konqueror, Safari, Chrome)

Android

Android

iPhone

iPhone

iPad

iPad

BlackBerry

BlackBerry

Touch

Browser supports touch input

Mobile

The browser is a mobile variant

manifest.json properties

The following table gives an overview about all possible properties that can be used in a manifest.json file:

Property Optional Default Description

"name" : "mybundle"

no

The symbolic name of this bundle

"version" : "1.0.0"

no

The version of this bundle

"title" : "My Bundle"

yes

the name

The display name of this bundle

"vendor": "con terra GmbH",

yes

""

The vendor of this bundle. Displayed in the Live Configuration bundle details.

"description": "My bundle does something"

yes

""

A description of this bundle. Displayed in the Live Configuration bundle details.

"keywords": ["osgi", "test", "nursery"]

yes

[]

A list of categories (application specific use, tags for the bundle). Displayed in the Live Configuration bundle details.

"contactAddress": "Martin-Luther-King Weg 24, 48145 M\u00FCnster"

yes

""

A contact address for this bundle. Displayed in the Live Configuration bundle details.

"copyright": "con terra GmbH"

yes

""

The copyright of this bundle. Displayed in the Live Configuration bundle details.

"url": "http://conterra.de/mapapps/systembundle"

yes

""

A Documentation link. Displayed in the Live Configuration bundle details.

"icon": {
  "url":"/icons/bundle-logo.png",
  "width":64,
   "height":64
}

yes

{}

Metadata about the icon of this bundle. Used in the Live Configuration bundle list.

"licenses":[{
   "type": "GPL",
   "version" :"2.4",
   "url": "http://www.opensource.org/licenses/jabberpl.php"
    }
]

yes

{}

Metadata about the license of this bundle. Displayed in the Live Configuration bundle details.

"layer" : "module"

yes

"module"

Specifies the name of the source file, which is loaded during the resolve phase of the bundle. This file is very important for bundle loading and source/component name resolution.

"startLevel" : 1

yes

50, or the value defined in the property 'Framework-Startlevel-Beginning' in the system bundle

Defines the StartLevel of this bundle. This provides a way to modify or optimize the start order of bundles.

"autoStartPolicy" : "yes"

yes

yes

Defines that this bundle shall be started or not.

"activator": "Activator"

yes

""

Specifies a bundle activator class. This activator is informed about the start and stop of the bundle.

"i18n" : ["bundle"]

yes

[bundle]

Specifies if the bundle provides i18n, see Internationalization.

"requiredExecutionEnvironment": ["IE:[7,]","FF:[3,]"]

yes

[]

Specifies client environment requirements. If this requirements are not fulfilled, the bundle is not started.

"excludedExecutionEnvironment" : ["iPhone"]

yes

[]

Excludes a execution environment, works like requiredExecutionEnvironment, but excluding and not including.

"dependencies" : {
    "map" : "1.0.0",
    ...
}

yes

{}

Declares the dependencies of this bundle to other bundles.

"optionalDependencies" : {
    "map" : "1.0.0",
    ...
}

yes

{}

Declares the dependencies of this bundle to other bundles.

"components" : […​]

yes

[], interpreted by the ct.bundles.system.component package

This is the entry point for component declarations, see Declarative services.