An OSGi - lite framework for JavaScript runtimes.
Pandino is inspired by the OSGi framework originally created for Java.
Given the wast differences between the Java platform and JavaScript, Pandino only utilizes a limited set of the original specification. In certain cases the "porting" of features even altered the original standard. Such differences can be observed in the source code via comments, or in the documentation it self.
Most noteworthy differences compared to the OSGi standard are explained in the docs/osgi-comparison.md document.
- There is a need to decouple an application at design time
- Functionality can come and go at runtime
- Parts of an application need to be customized without rebuilding the complete application
- You don't want to vendor-lock yourself to e.g.: build tools or complete frameworks
- You want to have complete control over how you manage your functionality
Pandino can be used as an ES Module in browsers and NodeJS v14, v16, and v18 as well. To support legacy setups, we export CJS modules as well, where applicable.
For brevity's sake, we will demonstrate how to add Pandino to a pure and plain JavaScript application. We will also load a custom service which will alter the application it self.
Please visit the examples folder for more complex use-cases!
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>App with inverter</title>
</head>
<body>
<h1>Hello!</h1>
<p id="text-to-invert">This text should be inverted!</p>
<script type="module">
// 0. Import Pandino, and a bundle loader config preset.
import Pandino from 'https://unpkg.com/@pandino/pandino@latest/dist/esm/pandino.mjs';
import loaderConfiguration from 'https://unpkg.com/@pandino/loader-configuration-dom@latest/dist/loader-configuration-dom.mjs';
const pandino = new Pandino({
...loaderConfiguration,
});
await pandino.init();
await pandino.start();
// Pandino should be up and running, which should be visible by looking at the console
// window of your browser's dev-tools
</script>
</body>
</html>
In this example we are importing a loaderConfiguration
. This is only for our convenience, you can implement
your own loader if for some reason the default is not sufficient!
Pandino has 2 mandatory init parameters (which the loaderConfiguration
also implement):
pandino.manifest.fetcher
: an object with afetch()
method where we implement the Manifest loading mechanismpandino.bundle.importer
: an object with animport()
method where we implement the Activator loading mechanism
The reason for why we would want to manually define pandino.manifest.fetcher
and pandino.bundle.importer
is that
Pandino it self is platform agnostic, which means that the "file loading" mechanism will be different in e.g. a Browser
compared to NodeJS.
Another benefit is that this way the library user can fine-tune the loading logic if needed (e.g.: handle proxies, etc...).
A complete list of Framework configuration properties can be found in the corresponding source code: framework-config-map.ts
For different platforms or languages, please check the installation documentation!
Every Bundle consists of at least 2 artifacts:
- One JSON file containing Manifest info necessary for Pandino to manage the Bundle and it's dependencies / features
- One Activator JavaScript file with or without the source code bundled into it
- the Activator it self MUST be default exported!
string-inverter-manifest.json
{
"Bundle-ManifestVersion": "1",
"Bundle-SymbolicName": "@example/string-inverter",
"Bundle-Name": "String Inverter",
"Bundle-Version": "0.1.0",
"Bundle-Activator": "./string-inverter.js"
}
The Bundle-SymbolicName
property should be considered to be similar to the name
property in a package.json
file.
The Bundle-SymbolicName
and Bundle-Version
properties together serve as "composite keys" (make the bundle uniquely
identifiable)!
A complete list of Bundle Manifest Header properties can be found in the corresponding source code: bundle-manifest-headers.ts
string-inverter.js
const STRING_INVERTER_INTERFACE_KEY = '@example/string-inverter/StringInverter';
class StringInverterImpl {
invert(str) {
return str.split('').reverse().join('');
}
}
export default class Activator {
inverterRegistration;
async start(context) {
// Registers the service with the scope type of SINGLETON
this.inverterRegistration = context.registerService(STRING_INVERTER_INTERFACE_KEY, new StringInverterImpl());
}
async stop(context) {
this.inverterRegistration.unregister();
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>App with inverter</title>
</head>
<body>
<h1>Hello!</h1>
<p id="text-to-invert">This text should be inverted!</p>
<script type="module">
import Pandino from 'https://unpkg.com/@pandino/pandino@latest/dist/esm/pandino.mjs';
import loaderConfiguration from 'https://unpkg.com/@pandino/loader-configuration-dom@latest/dist/loader-configuration-dom.mjs';
const pandino = new Pandino({
...loaderConfiguration,
});
await pandino.init();
await pandino.start();
// 1. Install our new bundle via it's manifest:
const context = pandino.getBundleContext();
await context.installBundle('./string-inverter-manifest.json');
// 2. Obtain a Service Object
const inverterReference = context.getServiceReference('@example/string-inverter/StringInverter');
const inverterService = context.getService(inverterReference);
// 3. Use our Service to invert some text in our DOM
const paragraphToInvert = document.getElementById('text-to-invert');
paragraphToInvert.textContent = inverterService.invert(paragraphToInvert.textContent);
</script>
</body>
</html>
Keep in mind that Bundles can be managed by other Bundles as well! This means that you do not need to pass the actual
pandino
or BundleContext
reference in your app!
For detailed information about Pandino and it's internals, please check the Documentation page.
Multiple example projects are available under the examples folder. Each example is a stand-alone, dedicated project, which means that specific instructions regarding how to operate them are detailed in their respective folders.
This repository contains extra packages, e.g.: specifications, corresponding reference-implementations solving common software development problems. Usage is opt-in of course.
Context: This version range is the first public version published.
Goal: Gather community feedback, improve stability and APIs, introduce missing key features.
Users should expect breaking changes somewhat often.
There is no official end of life, a couple of months should pass at least until we will bump the version.
Context: This version range is a Release Candidate.
Goal: Fix bugs, improve stability.
No API breaking changes are allowed.
Similarly to v0.8.x
, this range will have a lifetime of at least a couple of months.
Context: This version range is considered to be production-ready.
From this point onwards, we only plan to maintain a single version line. Based on our community and user-base growth, we may consider LTS branches in the future.
Eclipse Public License - v 2.0