An OSGi - lite framework for JavaScript runtimes.
Pandino helps build software in a way where functionality is "interfaced" and concrete implementation(s) are provided at runtime or potentially by different teams.
Other tools provide dependency injection capabilities but Pandino is more. Pandino gives us the ability to wire in implementation in a way where it can come and go at runtime. It is also worth noting that Pandino does not require you to install specialized tooling to work compared to other micro-frontend frameworks / tools.
Pandino is also extendable, some of the functionalities defined in the OSGi Compendium are already provided out of the box, such as:
Pandino can be used as an ES Module in browsers and NodeJS v20 as well. To support legacy setups, we export CJS modules as well, where applicable.
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.
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/@pandino/pandino.mjs';
import loaderConfiguration from 'https://unpkg.com/@pandino/loader-configuration-dom@latest/dist/@pandino/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!
Loaders abstract away mandatory configurations for each platform. 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/@pandino/pandino.mjs';
import loaderConfiguration from 'https://unpkg.com/@pandino/loader-configuration-dom@latest/dist/@pandino/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