Skip to content

BlackBeltTechnology/pandino

Repository files navigation

Pandino

build-test Discord license TypeScript

An OSGi - lite framework for JavaScript runtimes.

What is Pandino

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:

Supported platforms

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.

Inspiration

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.

Highlighted differences compared to OSGI

Most noteworthy differences compared to the OSGi standard are explained in the docs/osgi-comparison.md document.

Beginner's guide

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!

1) Create an Application

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!

2) Create a Bundle which exposes a string-inverter service

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();
  }
}

3) Wire the Bundle into our application

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!

Documentation

For detailed information about Pandino and it's internals, please check the Documentation page.

Example Projects

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.

Extras

This repository contains extra packages, e.g.: specifications, corresponding reference-implementations solving common software development problems. Usage is opt-in of course.

Default Loader Configurations

Bundle Installer

Configuration Management

Event Admin

Persistence Manager

Bundler Plugins

Activator Resolvers

React integration

Roadmap

v0.8.x

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.

v0.9.x

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.

v1.0.x

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.

License

Eclipse Public License - v 2.0