|
@@ -3,13 +3,14 @@
|
|
|
Extension Developer Guide
|
|
|
=========================
|
|
|
|
|
|
-The JupyterLab application is comprised of a core application object and a set of plugins. JupyterLab plugins provide nearly every function in JupyterLab, including notebooks, document editors and viewers, code consoles, terminals, themes, the file browser, contextual help system, debugger, and settings editor. Plugins even provide more fundamental parts of the application, such as the menu system, status bar, and the underlying communication mechanism with the server.
|
|
|
+The JupyterLab application is comprised of a core application object and a set of extensions. JupyterLab extensions provide nearly every function in JupyterLab, including notebooks, document editors and viewers, code consoles, terminals, themes, the file browser, contextual help system, debugger, and settings editor. Extensions even provide more fundamental parts of the application, such as the menu system, status bar, and the underlying communication mechanism with the server.
|
|
|
|
|
|
+A JupyterLab extension is a package that contains a number of JupyterLab plugins. We will discuss how to write a plugin, then how to package together a set of plugins into a JupyterLab extension.
|
|
|
|
|
|
-Getting Started
|
|
|
+Other resources
|
|
|
---------------
|
|
|
|
|
|
-The documentation in this section covers the basic and advanced concepts for writing extensions. To get hands-on practice or more in-depth reference documentation, we have tutorials, examples, cookiecutters, and generated reference documentation.
|
|
|
+Before we get started, here are some resources for hands-on practice or more in-depth reference documentation.
|
|
|
|
|
|
Tutorials
|
|
|
^^^^^^^^^
|
|
@@ -43,13 +44,7 @@ Here is some autogenerated API documentation for JupyterLab and Lumino packages:
|
|
|
Overview of Extensions
|
|
|
----------------------
|
|
|
|
|
|
-A JupyterLab extension is a package that contains JupyterLab plugins. A plugin is the basic unit of extensibility in JupyterLab. JupyterLab supports several types of plugins:
|
|
|
-
|
|
|
-- **application plugins:** Application plugins are the fundamental building block of JupyterLab functionality. Application plugins interact with JupyterLab and other plugins by requiring services provided by other plugins, and optionally providing their own service to the system.
|
|
|
-- **mime renderer plugins:** Mime renderer plugins are simplified, restricted ways to extend JupyterLab to render custom mime data in notebooks and files. These plugins are automatically converted to equivalent application plugins by JupyterLab when they are loaded.
|
|
|
-- **theme plugins:** Theme plugins provide a way to customize the appearance of JupyterLab by changing themeable values (i.e., CSS variable values) and providing additional fonts and graphics to JupyterLab.
|
|
|
-
|
|
|
-An extension is a package that contains one or more JupyterLab plugins. There are several types of extensions:
|
|
|
+A JupyterLab extension is a package that contains JupyterLab plugins. A plugin is the basic unit of extensibility in JupyterLab. An extension is a package that contains one or more JupyterLab plugins. There are several types of extensions:
|
|
|
|
|
|
- A *source extension* is a JavaScript (npm) package that exports one or more plugins. Installing a source extension requires a user to rebuild JupyterLab. This rebuilding step requires Node.js and may take a lot of time and memory, so some users may not be able to install a source extension. See :ref:`deduplication` for the technical reasons for rebuilding JupyterLab when a source extension is installed.
|
|
|
- A *prebuilt extension* (new in JupyterLab 3.0) distributes a bundle of JavaScript code prebuilt from a source extension that can be loaded into JupyterLab without rebuilding JupyterLab. In this case, the extension developer uses tools provided by JupyterLab to compile a source extension into a JavaScript bundle that includes the non-JupyterLab JavaScript dependencies, then distributes the resulting bundle in, for example, a Python pip or conda package. Installing a prebuilt extensions does not require Node.js.
|
|
@@ -61,16 +56,27 @@ Because prebuilt extensions do not require a JupyterLab rebuild, they have a dis
|
|
|
.. tip::
|
|
|
We recommend publishing prebuilt extensions in Python packages for user convenience.
|
|
|
|
|
|
+Plugins
|
|
|
+-------
|
|
|
+
|
|
|
+A JupyterLab plugin is the basic unit of extensibility in JupyterLab. JupyterLab supports several types of plugins:
|
|
|
+
|
|
|
+- **Application plugins:** Application plugins are the fundamental building block of JupyterLab functionality. Application plugins interact with JupyterLab and other plugins by requiring services provided by other plugins, and optionally providing their own service to the system.
|
|
|
+- **Mime renderer plugins:** Mime renderer plugins are simplified, restricted ways to extend JupyterLab to render custom mime data in notebooks and files. These plugins are automatically converted to equivalent application plugins by JupyterLab when they are loaded.
|
|
|
+- **Theme plugins:** Theme plugins provide a way to customize the appearance of JupyterLab by changing themeable values (i.e., CSS variable values) and providing additional fonts and graphics to JupyterLab.
|
|
|
+
|
|
|
|
|
|
Application Plugins
|
|
|
--------------------
|
|
|
+^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
-A typical plugin is specified by the following metadata. The ``id`` and ``activate`` fields are required and the other fields may be omitted. For more information about the ``requires``, ``optional``, or ``provides`` fields, see :ref:`services`.
|
|
|
+An application plugin is a JavaScript object with a number of metadata fields. The ``id`` and ``activate`` fields are required and the other fields may be omitted. For more information about how to use the ``requires``, ``optional``, or ``provides`` fields, see :ref:`services`.
|
|
|
|
|
|
-.. code::
|
|
|
+A typical application plugin might look like this in TypeScript:
|
|
|
+
|
|
|
+.. code-block:: typescript
|
|
|
|
|
|
const plugin: JupyterFrontEndPlugin<MyToken> = {
|
|
|
- id: 'MyExtension:my_plugin',
|
|
|
+ id: 'my-extension:plugin',
|
|
|
autoStart: true,
|
|
|
requires: [ILabShell, ITranslator],
|
|
|
optional: [ICommandPalette],
|
|
@@ -78,36 +84,37 @@ A typical plugin is specified by the following metadata. The ``id`` and ``activa
|
|
|
activate: activateFunction
|
|
|
};
|
|
|
|
|
|
-- ``id`` is a required unique string. The convention is to use the NPM extension package name and a string identifying the plugin inside the extension, separated by a colon.
|
|
|
-- ``autostart`` indicates whether your plugin should be activated at application startup. Typically this should be ``true``. If it is ``false`` or omitted, your plugin will be instantiated when any other plugin requests the token your plugin is providing.
|
|
|
-- ``requires`` and ``optional`` are lists of tokens. The corresponding objects in the system will be provided to the ``activate`` function when the plugin is instantiated. Tokens in the ``requires`` list will be required for your plugin to work, and your plugin activation will error if a ``required`` token is not registered with JupyterLab. Tokens in the ``optional`` list may or may not be registered, but will be provided to your plugin if they exist.
|
|
|
-- ``provides`` is the token associated with the service your plugin is providing to the system. A token can only be registered with the system once. If your plugin does not provide a service to the system, omit this field and do not return a value from your ``activate`` function.
|
|
|
-- ``activate`` is the function called when your plugin is activated. The arguments are, in order, the Application object, the services corresponding to the ``requires`` tokens, then the services corresponding to the ``optional`` tokens (or ``null`` if that particular ``optional`` token is not registered in the system). The return value of the ``activate`` function (or resolved return value if a promise is returned) will be stored in the system as the service associated with the ``provides`` token.
|
|
|
+- ``id`` is a required unique string. The convention is to use the NPM extension package name, a colon, then a string identifying the plugin inside the extension.
|
|
|
+- ``autostart`` indicates whether your plugin should be activated at application startup. Typically this should be ``true``. If it is ``false`` or omitted, your plugin will be activated when any other plugin requests the token your plugin is providing.
|
|
|
+- ``requires`` and ``optional`` are lists of tokens corresponding to services other plugins provide. These services will be given as arguments to the ``activate`` function when the plugin is activated. If a ``requires`` service is not registered with JupyterLab, an error will be thrown and the plugin will not be activated.
|
|
|
+- ``provides`` is the token associated with the service your plugin is providing to the system. If your plugin does not provide a service to the system, omit this field and do not return a value from your ``activate`` function.
|
|
|
+- ``activate`` is the function called when your plugin is activated. The arguments are, in order, the :ref:`application_object`, the services corresponding to the ``requires`` tokens, then the services corresponding to the ``optional`` tokens (or ``null`` if that particular ``optional`` token is not registered in the system). If a ``provides`` token is given, the return value of the ``activate`` function (or resolved return value if a promise is returned) will be registered as the service associated with the token.
|
|
|
+
|
|
|
+.. _application_object:
|
|
|
|
|
|
Application Object
|
|
|
-^^^^^^^^^^^^^^^^^^
|
|
|
+""""""""""""""""""
|
|
|
|
|
|
-A Jupyter front-end application object is given to each plugin in its
|
|
|
-``activate()`` function. The application object has:
|
|
|
+A Jupyter front-end application object is given to each plugin ``activate`` function as its first argument. The application object has a number of properties and methods for interacting with the application, including:
|
|
|
|
|
|
- ``commands`` - an extensible registry used to add and execute commands in the application.
|
|
|
-- ``commandLinker`` - used to connect DOM nodes with the command registry so that clicking on them executes a command.
|
|
|
- ``docRegistry`` - an extensible registry containing the document types that the application is able to read and render.
|
|
|
- ``restored`` - a promise that is resolved when the application has finished loading.
|
|
|
- ``serviceManager`` - low-level manager for talking to the Jupyter REST API.
|
|
|
-- ``shell`` - a generic Jupyter front-end shell instance, which holds the user interface for the application.
|
|
|
+- ``shell`` - a generic Jupyter front-end shell instance, which holds the user interface for the application. See :ref:`shell` for more details.
|
|
|
|
|
|
+See the JupyterLab API reference documentation for the ``JupyterFrontEnd`` class for more details.
|
|
|
|
|
|
.. _services:
|
|
|
|
|
|
Plugins Interacting with Each Other
|
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
-One of the foundational features of the JupyterLab plugin system is that plugins can interact with other plugins by providing a service to the system and requiring services provided by other plugins. A service can be any JavaScript value, and typically is a JavaScript object with methods and data attributes. For example, the plugin that supplies the JupyterLab main menu provides a service object to the system with methods and attributes other plugins can use to interact with the main menu.
|
|
|
+One of the foundational features of the JupyterLab plugin system is that application plugins can interact with other plugins by providing a service to the system and requiring services provided by other plugins. A service can be any JavaScript value, and typically is a JavaScript object with methods and data attributes. For example, the core plugin that supplies the JupyterLab main menu provides a :ref:`mainmenu` service object to the system with a method to add a new top-level menu and attributes to interact with existing top-level application menus.
|
|
|
|
|
|
In the following discussion, the plugin that is providing a service to the system is the *provider* plugin, and the plugin that is requiring and using the service is the *consumer* plugin.
|
|
|
|
|
|
-A service provided by a plugin is identified by a *token*, i.e., a concrete instance of the Lumino Token class. The provider plugin lists the token in its plugin metadata ``provides`` field, and returns the associated service from its ``activate`` function. Consumer plugins import the token (for example, from the provider plugin's extension JavaScript package) and list the token in their plugin metadata ``requires`` or ``optional`` fields. When JupyterLab instantiates the consumer plugin, it will pass in the service associated with the token. JupyterLab orders plugin activation to ensure that a provider of a service is activated before its consumers.
|
|
|
+A service provided by a plugin is identified by a *token*, i.e., a concrete instance of the Lumino Token class. The provider plugin lists the token in its plugin metadata ``provides`` field, and returns the associated service from its ``activate`` function. Consumer plugins import the token (for example, from the provider plugin's extension JavaScript package) and list the token in their plugin metadata ``requires`` or ``optional`` fields. When JupyterLab instantiates the consumer plugin, it will pass in the service associated with the token. JupyterLab orders plugin activation to ensure that a provider of a service is activated before its consumers. A token can only be registered with the system once.
|
|
|
|
|
|
A token defined in TypeScript can also define a TypeScript interface for the service associated with the token. If the provider or consumer uses TypeScript, the service will be type-checked against this interface.
|
|
|
|
|
@@ -117,7 +124,7 @@ A token defined in TypeScript can also define a TypeScript interface for the ser
|
|
|
Publishing Tokens
|
|
|
"""""""""""""""""
|
|
|
|
|
|
-Since consumers will need to import a token used by a provider, the token should be exported in a published JavaScript package. A pattern in core JupyterLab is to create and export tokens from a self-contained ``tokens`` JavaScript module in a package. This enables consumers to import a token directly from the package's ``tokens`` module (e.g., ``import { MyToken } from 'provider/tokens';``), thus enabling a tree-shaking bundling optimization to bundle only the tokens and not other code from the package.
|
|
|
+Since consumers will need to import a token used by a provider, the token should be exported in a published JavaScript package. A pattern in core JupyterLab is to create and export tokens from a self-contained ``tokens`` JavaScript module in a package. This enables consumers to import a token directly from the package's ``tokens`` module (e.g., ``import { MyToken } from 'provider/tokens';``), thus enabling a tree-shaking bundling optimization to possibly bundle only the tokens and not other code from the package.
|
|
|
|
|
|
Another pattern in core JupyterLab is to create and export a token from a third package that both the provider and consumer extensions import, rather than defining the token in the provider's package. This enables a user to swap out the provider extension for a different extension that provides the same token with an alternative service implementation. For example, the core JupyterLab ``filebrowser`` package exports a token representing the file browser service (enabling interactions with the file browser). The ``filebrowser-extension`` package contains a plugin that implements the file browser in JupyterLab and provides the file browser service to JupyterLab (identified with the token imported from the ``filebrowser`` package). Extensions in JupyterLab that want to interact with the filebrowser thus do not need to have a JavaScript dependency on the ``filebrowser-extension`` package, but only need to import the token from the ``filebrowser`` package. This pattern enables users to seamlessly change the file browser in JupyterLab by writing their own extension that imports the same token from the ``filebrowser`` package and provides it to the system with their own alternative file browser service.
|
|
|
|
|
@@ -131,47 +138,16 @@ Deduplication of Dependencies
|
|
|
|
|
|
One important concern and challenge in the JupyterLab extension system is deduplicating dependencies of extensions instead of having extensions use their own bundled copies of dependencies. For example, the Lumino widgets system on which JupyterLab relies for communication across the application requires all packages use the same copy of the ``@lumino/widgets`` package. Tokens identifying plugin services also need to be shared across the providers and consumers of the services, so dependencies that export tokens need to be deduplicated.
|
|
|
|
|
|
-Deduplication in JupyterLab happens in two ways. For source extensions, JupyterLab deduplicates dependencies when rebuilds itself to include the extension during the extension installation process. Deduplication is one of the main reasons JupyterLab needs to be rebuilt when installing source extensions. For prebuilt extensions, JupyterLab relies on the new Webpack module federation system to share dependencies across different bundles (including the core JupyterLab application bundle).
|
|
|
-
|
|
|
-To ensure that a consumer gets the same token instance as the provider provided to the sytem, any required tokens that are imported by a consumer extension should list the exporting extension as a singleton package in their ``jupyterlab.sharedPackages`` config. Required token packages should be listed as ``bundled: false`` - this will generate a JavaScript error if the package (and thus the token) is not present in the system at runtime. Optional token packages should be listed as singletons that are bundled (otherwise, if they are not present in the system, it will cause a js error when you try to import them).
|
|
|
-
|
|
|
-
|
|
|
-Plugin Settings
|
|
|
-^^^^^^^^^^^^^^^
|
|
|
-
|
|
|
-JupyterLab exposes a plugin settings system that can be used to provide
|
|
|
-default setting values and user overrides.
|
|
|
-
|
|
|
-An extension can specify user settings using a JSON Schema. The schema
|
|
|
-definition should be in a file that resides in the ``schemaDir``
|
|
|
-directory that is specified in the ``package.json`` file of the
|
|
|
-extension. The actual file name should use is the part that follows the
|
|
|
-package name of extension. So for example, the JupyterLab
|
|
|
-``apputils-extension`` package hosts several plugins:
|
|
|
+Deduplication in JupyterLab happens in two ways. For source extensions, JupyterLab deduplicates dependencies when rebuilds itself to include the extension during the extension installation process. Deduplication is one of the main reasons JupyterLab needs to be rebuilt when installing source extensions. For prebuilt extensions, JupyterLab relies on the Webpack 5.0 module federation system to share dependencies across different bundles (including the core JupyterLab application bundle).
|
|
|
|
|
|
-- ``'@jupyterlab/apputils-extension:menu'``
|
|
|
-- ``'@jupyterlab/apputils-extension:palette'``
|
|
|
-- ``'@jupyterlab/apputils-extension:settings'``
|
|
|
-- ``'@jupyterlab/apputils-extension:themes'``
|
|
|
+To ensure that a consumer gets the same token instance that the provider provided to the sytem, the consumer should list the package it imported the tokens from as unbundled package in its ``package.json`` ``jupyterlab.sharedPackages`` config—this will generate a JavaScript error if the package (and thus the token) is not present in the system at runtime. Optional token packages should be listed as singletons that are bundled (otherwise, if they are not present in the system, it will cause a js error when you try to import them).
|
|
|
|
|
|
-And in the ``package.json`` for ``@jupyterlab/apputils-extension``, the
|
|
|
-``schemaDir`` field is a directory called ``schema``. Since the
|
|
|
-``themes`` plugin requires a JSON schema, its schema file location is:
|
|
|
-``schema/themes.json``. The plugin's name is used to automatically
|
|
|
-associate it with its settings file, so this naming convention is
|
|
|
-important. Ensure that the schema files are included in the ``"files"``
|
|
|
-metadata in ``package.json``.
|
|
|
|
|
|
-See the
|
|
|
-`fileeditor-extension <https://github.com/jupyterlab/jupyterlab/tree/master/packages/fileeditor-extension>`__
|
|
|
-for another example of an extension that uses settings.
|
|
|
-
|
|
|
-A system administrator or user can override default values of extension settings with the :ref:`overrides.json <overridesjson>` file.
|
|
|
|
|
|
.. _rendermime:
|
|
|
|
|
|
Mime Renderer Plugins
|
|
|
-----------------------
|
|
|
+^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
|
|
Mime Renderer plugins are a convenience for creating an plugin
|
|
|
that can render mime data and potentially render files of a given type.
|
|
@@ -214,7 +190,7 @@ document can then be saved by the user in the usual manner.
|
|
|
|
|
|
|
|
|
Theme plugins
|
|
|
--------------
|
|
|
+^^^^^^^^^^^^^
|
|
|
|
|
|
A theme is a JupyterLab plugin that uses a ``ThemeManager`` and can
|
|
|
be loaded and unloaded dynamically. The package must include all static
|
|
@@ -237,8 +213,8 @@ It is also possible to create a new theme using the
|
|
|
`TypeScript theme cookiecutter <https://github.com/jupyterlab/theme-cookiecutter>`__.
|
|
|
|
|
|
|
|
|
-Creating a Source Extension
|
|
|
---------------------------
|
|
|
+Source Extensions
|
|
|
+-----------------
|
|
|
|
|
|
A source extension is a JavaScript package that exports one or more plugins.
|
|
|
|
|
@@ -253,16 +229,47 @@ We recommended including the keyword ``jupyterlab-extension`` in ``package.json`
|
|
|
"jupyterlab-extension"
|
|
|
],
|
|
|
|
|
|
-``extension``
|
|
|
-"""""""""""""
|
|
|
-
|
|
|
+Main entry point
|
|
|
+""""""""""""""""
|
|
|
|
|
|
-The ``"extension"`` field signifies that this package is an extension and exports a plugin or list of plugins as the default exports from one of its JavaScript modules. Set the value to ``true`` if plugins are the default exports from the main package module (i.e., the file listed in the ``main`` key of ``package.json``). If your plugins are exported by a different module, set this to the relative path to the module (e.g., ``"lib/foo"``). Example::
|
|
|
+The ``jupyterlab.extension`` field signifies that this package is a JupyterLab extension and gives the module that exports a plugin or list of plugins as default exports. Set the value to ``true`` if plugins are the default exports from the main package module (i.e., the file listed in the ``main`` key of ``package.json``). If your plugins are exported by a different module, set this to the relative path to the module (e.g., ``"lib/foo"``). Example::
|
|
|
|
|
|
"jupyterlab": {
|
|
|
"extension": true
|
|
|
}
|
|
|
|
|
|
+Plugin Settings
|
|
|
+"""""""""""""""
|
|
|
+
|
|
|
+JupyterLab exposes a plugin settings system that can be used to provide
|
|
|
+default setting values and user overrides. This uses the ``jupyterlab.schemaDir`` field of the extension metadata.
|
|
|
+
|
|
|
+An extension can specify user settings using a JSON Schema. The schema
|
|
|
+definition should be in a file that resides in the ``schemaDir``
|
|
|
+directory that is specified in the ``package.json`` file of the
|
|
|
+extension. The actual file name should use is the part that follows the
|
|
|
+package name of extension. So for example, the JupyterLab
|
|
|
+``apputils-extension`` package hosts several plugins:
|
|
|
+
|
|
|
+- ``'@jupyterlab/apputils-extension:menu'``
|
|
|
+- ``'@jupyterlab/apputils-extension:palette'``
|
|
|
+- ``'@jupyterlab/apputils-extension:settings'``
|
|
|
+- ``'@jupyterlab/apputils-extension:themes'``
|
|
|
+
|
|
|
+And in the ``package.json`` for ``@jupyterlab/apputils-extension``, the
|
|
|
+``schemaDir`` field is a directory called ``schema``. Since the
|
|
|
+``themes`` plugin requires a JSON schema, its schema file location is:
|
|
|
+``schema/themes.json``. The plugin's name is used to automatically
|
|
|
+associate it with its settings file, so this naming convention is
|
|
|
+important. Ensure that the schema files are included in the ``"files"``
|
|
|
+metadata in ``package.json``.
|
|
|
+
|
|
|
+See the
|
|
|
+`fileeditor-extension <https://github.com/jupyterlab/jupyterlab/tree/master/packages/fileeditor-extension>`__
|
|
|
+for another example of an extension that uses settings.
|
|
|
+
|
|
|
+A system administrator or user can override default values of extension settings with the :ref:`overrides.json <overridesjson>` file.
|
|
|
+
|
|
|
Disabling other extensions
|
|
|
""""""""""""""""""""""""""
|
|
|
|
|
@@ -482,7 +489,7 @@ We encourage extension authors to add the `jupyterlab-extension GitHub topic
|
|
|
|
|
|
While authoring the extension, you can use the command:
|
|
|
|
|
|
-.. code:: bash
|
|
|
+.. code-block:: bash
|
|
|
|
|
|
npm install # install npm package dependencies
|
|
|
npm run build # optional build step if using TypeScript, babel, etc.
|