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:
-
https://demos.conterra.de/mapapps/resources/jsregistry/root (con terra’s public instance)
-
http://locahost:9090/resources/jsregistry/root (your development instance, for example)
General structure
A bundle is folder that has the following 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 How to support multiple languages. 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:
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.
{
"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.
{
"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:
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:
|
Now, consider the following bundle with the module.js
file outlined above:
mybundle/
├── activators.js
├── manifest.json
├── module.js
├── Services.js
└── util.js
The bundle manifest declares two components and an activator:
{ ...
"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 (i18n)
Bundles support internationalization, so components of bundles can display messages in bundles, based on the user-selected language, for example.
There is a guide describing how to add i18n to a 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:
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 layer file 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:
{
...
"activator" : "AClassName"
...
}
The App Runtime calls the start
or stop
method of the activator class.
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.
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.