Running the plugin registry on the server#

Lumino’s PluginRegistry, the plugin system behind JupyterLab, lives in @lumino/coreutils and has no DOM dependency, so it runs in Node as-is. You don’t need @lumino/application or @lumino/widgets to use it.

The example-plugin-registry-server example (in the repo’s examples/ directory) shows this with two plugins that the registry wires together through a shared service token, without importing each other:

  • hello provides an IGreeting service.

  • greeter requires IGreeting and uses it.

The shared token#

A Token identifies the service. The provider and the consumer both refer to this token; neither imports the other.

import { Token } from '@lumino/coreutils';

/**
 * The application object passed to every plugin's `activate()`.
 */
export interface IApp {
  /**
   * Log a message.
   */
  log(message: string): void;
}

/**
 * A service that one plugin provides and another consumes.
 */
export interface IGreeting {
  /**
   * Return a greeting.
   */
  greet(): string;
}

/**
 * The token used to provide and require the {@link IGreeting} service.
 *
 */
export const IGreeting = new Token<IGreeting>(
  '@lumino/example-plugin-registry-server:IGreeting'
);

Providing and requiring a service#

The hello plugin lists the token in provides and returns the service from activate:

import type { IPlugin } from '@lumino/coreutils';

import { IApp, IGreeting } from './tokens';

/**
 * A plugin that *provides* the `IGreeting` service.
 */
const plugin: IPlugin<IApp, IGreeting> = {
  id: '@lumino/example-plugin-registry-server:hello',
  autoStart: true,
  provides: IGreeting,
  activate: (app: IApp): IGreeting => {
    app.log('[hello] providing IGreeting');
    return {
      greet: () => 'Hello! (from the hello plugin)'
    };
  }
};

export default plugin;

The greeter plugin lists the token in requires; the registry passes the resolved service as an argument to activate:

import type { IPlugin } from '@lumino/coreutils';

import { IApp, IGreeting } from './tokens';

/**
 * A plugin that *requires* the `IGreeting` service.
 */
const plugin: IPlugin<IApp, void> = {
  id: '@lumino/example-plugin-registry-server:greeter',
  autoStart: true,
  requires: [IGreeting],
  activate: (app: IApp, greeting: IGreeting): void => {
    app.log(`[greeter] ${greeting.greet()}`);
  }
};

export default plugin;

Running it#

Register the plugins and activate them:

import { PluginRegistry } from '@lumino/coreutils';

import greeter from './greeter';
import hello from './hello';
import { IApp } from './tokens';

/**
 * Run the Lumino `PluginRegistry` on the server.
 */
async function main(): Promise<void> {
  const registry = new PluginRegistry<IApp>();
  registry.application = { log: message => console.log(message) };
  registry.registerPlugins([hello, greeter]);
  await registry.activatePlugins('startUp');
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

From the root of the repository, build the packages and then the example:

yarn install
yarn run build
yarn workspace @lumino/example-plugin-registry-server build
yarn workspace @lumino/example-plugin-registry-server start
[hello] providing IGreeting
[greeter] Hello! (from the hello plugin)

The [greeter] line is produced by the consumer plugin calling the service the provider returned. The two are connected only through the shared IGreeting token, never by importing each other.