About bundles

An app built on top of the App Runtime is composed out of several bundles. Bundles contain the code and resources to build up the whole app. A single bundle typically provides one feature or tool.

Here are some examples of what a bundle could provide:

  • Display a welcome window with configurable content when an app is started.

  • Display a form and send its data to a backend service.

  • Add geometries provided by a web service to the map.

map.apps comes with a bunch of bundles pre-installed that allow to compose rich mapping applications. You can browse the bundles of a map.apps instance using the JS Registry service: http(s)://<host>/<mapapps-context>/resources/jsregistry/root.

Examples:

Structure of a bundle

A bundle is folder that has the following structure:

Bundle structure
mybundle/
├── nls/
│   ├── de/
│   │   └── bundle.js
│   └── bundle.js
├── AClass.js
├── Activator.js
├── manifest.json       // required
└── module.js
mybundle/

Folder containing all files of a bundle. Should be the same name as the bundle defines in the manifest.json file.

nls/

Folder containing all internationalization (i18n) language files.
The details of internationalization are described in the chapter Bundle internationalization.

manifest.json

The bundle manifest file.
See below for a small introduction into the bundle manifest.

module.js

The bundle layer file that triggers loading of all JavaScript files required by the bundle.
The section Bundle layer file explains all the details and options.

AClass.js

Implementation class with certain functionality. Often such classes provide the implementation of a component declared in the bundle manifest.

Activator.js

A bundle activator implementation class. A bundle activator can hook into the bundle’s lifecycle and implement special behavior on bundle startup and shutdown.

Bundle manifest

The most important file of a bundle is the manifest.json file, the bundle manifest. The bundle manifest provides bundle metadata and defines the functionality provided by the bundle. A pretty simple bundle manifest might look like this:

Example manifest.json file
{
  "name": "mybundle",   (1)
  "version": "1.0",     (2)
  "components": [       (3)
    {
        "name": "ComponentA",
        "impl": "AClass"
    }
  ]
}
1 The official name of the bundle, this should match the name of the folder (required).
2 The version identifier of the bundle (required).
3 An array of component configurations declared by the bundle.

The name of a bundle is used as an identifier within the App Runtime and must be unique among all bundles. Each bundle has to define a version number. It should be incremented if the bundle is changed.

A manifest can have a lot of properties that are listed in the reference of manifest properties.

Bundle dependencies

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

Declaring dependencies to other bundles in manifest.json
{
  ...
  "dependencies": {
    "map-init" : "^4.14.0"    // <name> : <version range expression>
  },

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

The App 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), declare this dependency to ensure that the needed class is loaded before the bundle is started.

Bundle layer file

When the App Runtime first starts a bundle it looks for the bundle layer file and loads it. This bundle layer file is responsible to load all required JavaScript files of the bundle. A file is required to be loaded if it provides a class that implements a component or bundle activator declared in the bundle manifest.

By default a bundle layer file is assumed to be named module.js. It typically looks like this:

module.js
export { MyService, AnotherService } from "./Services";
export { FooActivator as Activator } from "./activators";

It just re-exports all required classes, optionally making them available under a different name.

Older bundles might use other styles to load the right classes:

define(["./MyService"],{}); //AMD-style

import "./MyService";       //ES6 import-style

Now, consider the following bundle with the module.js file outlined above:

Bundle structure
mybundle/
├── activators.js
├── manifest.json
├── module.js
├── Services.js
└── util.js

The bundle manifest declares two components and an activator:

manifest.json
{ ...
  "activator": "Activator",         // required class: Activator
  "components": [
      {
          "name": "MyService"       // required class: MyService
      },
      {
          "name": "Service2",       // required class: AnotherService
          "impl": "AnotherService"
      }
  ]
  ...
}

The bundle layer file module.js loads all required classes, so the App Runtime can start the bundle.

Bundles without a layer file

Some bundles don’t need to provide a layer file.

This is mainly because they implement a library or define an API, and don’t declare any components themselves. Instead they are dependencies for other bundles and get imported directly. In that case you need to set "layer" : "" in the bundle manifest. Otherwise the App Runtime will try to load a file named module.js and fail with an error if it does not exist.

Bundle internationalization

Bundles support internationalization, so you can display messages in bundles, based on the user-selected language, for example.

Defining resources

To provide language-specific strings, a bundle must have a folder nls/ containing bundles.js files structured like this:

i18n Files
mybundle/
└── nls/                    // fixed folder name for i18n resources
    ├── de/                 // German i18n resource directory
    │   └── bundle.js       // German i18n resource file
    └── bundle.js           // root i18n resource file (en)

The root i18n file mybundle/nls/bundle.js contains the English resources. Only this root i18n file needs to wrap the resource properties in a root element:

export default {
  root: {
    bundleName: "My Bundle",
    bundleDescription: "The bundle provides some functionality."
  },
  "de": true  // declare other available languages; only required in this root i18n file
};

The file mybundle/nls/de/bundle.js contains the German resources.

export default {
  bundleName: "Mein Modul",
  bundleDescription: "Das Modul bietet Funktionen."
};

Referencing resources

But you can also use ${resourceKey} expressions to access i18n properties directly inside the bundle manifest. The next sample shows how to implement a language-aware bundle title:

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

Disabling internationalization

To disable i18n support, set an empty array in the manifest.json file:

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

Bundle lifecycle

A bundle has a defined lifecycle that starts when it is installed into the App Runtime. The lifecycle defines the states a bundle can be in. You can hook into the lifecycle of a bundle by implementing a bundle activator or by listening to bundle events.

The following diagram illustrates a bundle’s states and their transitions:

bundle lifecycle

State Description

INSTALLED

This is the initial state of a bundle after the installation into the runtime.

RESOLVED

This state is reached during the first start of the bundle. It means that the bundle layer is loaded successfully.

STARTING

The bundle is in the start process.

ACTIVE

The bundle is successfully started and ready to operate.

STOPPING

The bundle is shutting down.

UNINSTALLED

The bundle is uninstalled from the runtime and cannot be restarted again.

Bundle activator

The bundle activator is a special class that a bundle can optionally provide. The App Runtime calls the bundle activator when the bundle that declares it is started or stopped.

A bundle activator needs to be declared in the bundle manifest using the activator property:

Declaring a bundle activator in manifest.json
{
  ...
  "activator" : "AClassName"
  ...
}

The App Runtime calls the start or stop method of the activator class.

A simple bundle activator
export default class {
    start(bundleContext) {
      // do something
    }

    stop(bundleContext) {
      // clean up
    }
}

A bundle activator can return a Promise in its start method. The App Runtime waits until the Promise is resolved before setting the state of the bundle to ACTIVE.

Bundle activator returning a Promise
import Promise from "apprt-core/Promise";
export default class {
  start() {
     return new Promise((resolve) => {
        setTimeout(function() {
          resolve(true);
        }, 100);
     });
  }
}

The main class for interacting with the App Runtime is the BundleContext, which provides all methods to manipulate bundle states, register for framework events or use the service layer.