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:

General structure

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 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 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.

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.

Dependencies

To be able to do its work a bundle sometimes requires another bundle to be started previously. It might directly depend on a service or rely on the effect of the other bundle’s activator. Use the dependencies element in the manifest to declare such a dependency on another bundle.

Declaring required bundle dependencies
{
  "name": "mybundle",
  "dependencies": {
    "map-init" : "^4.14.0" // "<name>" : "<version range>"
  }
}

The App Runtime tests if the declared bundles are installed and ensures that theses bundles are started before the bundle that is declaring the dependency. The startup of the declaring bundle (mybundle in the example above) will fail if a bundle listed under dependencies is not installed or cannot be started, for its part.

Dependencies can be optional, if the declaring bundle can still work without the other bundle. If the bundle of an optional dependency is not installed or fails to start, the App Runtime will not cancel the startup of the declaring bundle. To declare an optional dependency, you need to list it under the optionalDependencies property.

Declaring optional bundle dependencies
{
  "name": "mybundle",
  "optionalDependencies": {
    "coordinatetransformer" : "^4.14.0" // "<name>" : "<version range>"
  }
}

In this case, mybundle will be started even if coordinatetransformer is not available. But if it is available, coordinatetransformer is started prior to mybundle.

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.

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
  ...
}

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.

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.