Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional Lumino plugin load #601

Open
fcollonval opened this issue Jun 23, 2023 · 3 comments
Open

Conditional Lumino plugin load #601

fcollonval opened this issue Jun 23, 2023 · 3 comments
Labels
enhancement New feature or request

Comments

@fcollonval
Copy link
Member

This is a draft that requires refinement.

To reduce the start-up time of JupyterLab, we could be smarter about plugin loading logic. For now, a Lumino plugin can be started automatically or not using the autoStart flag. But there is no way to specify conditions for which a plugin should start.

Before laying down a plan for Lumino plugin, here is an analysis of VS Code extension API that allows to conditionally load plugins. There are two key points:

  • Entry points must be defined in a no-code way
  • Trigger events must be defined in a no-code way

In VS Code, those elements are defined in the extension package.json. The entry points are for example (see contributions documentation):

  • Commands
  • Menus
  • Views

The trigger events are for example (see activation events documentation):

  • onCommand: When a command of the extension is triggered
  • onView: When a specific view is restored
  • onStartupFinished: After the application has started
    The side effect of defining the contributed element in a no-code format raises the need to have an evaluable string to determine if an element is activable or not. This is called when clause in VS Code.

Back to JupyterLab and Lumino, the equivalent of onStartupFinished could be implemented quickly. But it may result in unwanted side effects; e.g. if the plugin is providing a widget and that widget is used in a notebook output that is loaded at startup, the widget may or may not be rendered. To ensure robustness, other events should be available; for example the widget plugin should load before opening a notebook.

Plugins adding new panels in the main area or in the side panel, like jupyterlab-git, are probably the best candidates for optimization. This requires defining side panels and main area widgets in a no-code way. In JupyterLab, we introduced the definition of keyboard shortcuts and menus in setting schema files. So it seems a more natural place than the extension package.json.

To be able to add a panel, widget titles need to be defined in a no-code way. This requires in particular the definition of icons in a no-code way.

Some first actions could be:

  • Extend autoStart to be a boolean or ‘"defer"’ (done in Add a 'defer' option to the autoStart argument #588)
  • We need to figure out how we could load the settings defining the entry points and the triggered events without loading the JavaScript code. A possibility would be to define entry points as we do for keyboard shortcuts or menus; i.e. in the settings file definitions. The entry points / no-code definitions in order of implementation would be:
    • Icon definition (needed for commands, widget title, file type,...)
    • Command definition
    • Panel definition (via the widget title to be displayed as placeholder in sidebar for example).
    • File type definition (needed for document factory)
    • Document factory
@fcollonval fcollonval added the enhancement New feature or request label Jun 23, 2023
@welcome
Copy link

welcome bot commented Jun 23, 2023

Thank you for opening your first issue in this project! Engagement like this is essential for open source projects! 🤗

If you haven't done so already, check out Jupyter's Code of Conduct. Also, please try to follow the issue template as it helps other other community members to contribute more effectively.
welcome
You can meet the other Jovyans by joining our Discourse forum. There is also an intro thread there where you can stop by and say Hi! 👋

Welcome to the Jupyter community! 🎉

@fcollonval
Copy link
Member Author

Discussion notes: https://hackmd.io/DBlDBDszSVSjYK9ieg1mZA

@fcollonval
Copy link
Member Author

fcollonval commented May 3, 2024

More thoughts about this for the GSoC project that will be implemented in JupyterLab repo directly - on a specific branch.

Note

This is a proposal to help kicking off an implementation

Vocabulary

What is a plugin entrypoint? An plugin entrypoint is no-code model defined as JSON that describes enough about a type of application feature to create a placeholder in the user interface.

Then if the user interact with the placeholder, the JavaScript of the plugin will be loaded and its activate function will be called to replace all placeholders (for that plugin) with the real implementation.

What is a plugin? It is object providing features in the application.
What is an extension? It is a NPM package providing one or more plugins.

Generic logic

A type of application feature is defined by an object provided by a plugin through a Token or an attribute of the Application. Other plugins may request that token to add a variant for that feature. For example, a application feature is the command that can be added through the CommandRegistry. Another example is a document viewer that can be added through the DocRegistry in JupyterLab. In the following I will name registry an object allowing to add a type of application feature.

Therefore the generic logic to use entrypoints is the following:

  1. List entrypoints per plugins from all extension package.json
  2. Store those entrypoints in the Application
  3. Registries supporting entrypoints will look at the entrypoints defined in the Application
  4. For each supported entrypoint, they will add a placeholder
  5. Each placeholder should be stored in the Application. So that the application can disposed them before activating the associated plugin.
  6. When an user triggered a placeholder:
    1. will load the plugin assets
    2. activate the plugin

A specific case

Let take the example of a new document viewer for CSV (interface IWidgetFactory) that is added to the DocRegistry using addWidgetFactory.

If the JSON schema of IWidgetFactory looks like:

{
    defaultFor?: readonly string[];
    defaultRendered?: readonly string[];
    fileTypes: readonly string[];
    label?: string;
    modelName?: string;
    name: string;
}

The placeholder interface will be something like:

interface IPlaceholder extends IObservableDisposable {}

The idea of using IObservableDisposable is to automatically trigger loading and activating the plugin by the Application when it is notified that a placeholder is disposed.
But a dedicated method in IPlaceholder that returns a Promise resolving when the plugin is activated may be more appropriate to know when the actual call to the feature can be made.

The example could be

{
    "name": "CSVTable",
    "label": "CSV Viewer",
    "fileTypes": ["csv"],
    "defaultFor": ["csv"]
}
class WidgetFactoryPlaceholder implements IWidgetFactory, IPlaceholder {
  constructor(entrypoint: IWidgetFactoryPlaceholder) {
    // ...
  }

  createNew() {
    // This is actually this method that when triggered should load the plugin
  }
}

The DocRegistry would add the placeholder:

app.docRegistry.addWidgetFactory(new WidgetFactoryPlaceholder(entrypoint));

Note

One of the technical difficulty is to be able to redo the action triggered on the placeholder with the real code once the plugin is loaded. For the aforementioned example, this materializes in how to trigger the widget factory createNew method from the real code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant