.. Copyright (c) Jupyter Development Team. .. Distributed under the terms of the Modified BSD License. .. _developer_extensions: Develop Extensions ================== 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. See the sections below for more detailed information, or browse the rest of this page for an overview. .. warning:: Your extensions may break with new releases of JupyterLab. As noted in :ref:`versioning_notes`, JupyterLab development and release cycles follow semantic versioning, so we recommend planning your development process to account for possible future breaking changes that may disrupt users of your extensions. Consider documenting your maintenance plans to users in your project, or setting an upper bound on the version of JupyterLab your extension is compatible with in your project's package metadata. .. toctree:: :maxdepth: 1 extension_points ui_components documents notebook virtualdom ui_helpers internationalization identity extension_tutorial extension_multiple_ui extension_migration Other resources --------------- Before we get started, here are some resources for hands-on practice or more in-depth reference documentation. Tutorials ^^^^^^^^^ Learn how to write JupyterLab extensions with these guides: - :ref:`extension_tutorial`: A tutorial to learn how to make a simple JupyterLab extension. - :ref:`Making Extensions Compatible with Multiple Applications Tutorial ` A tutorial for making extensions that work in both JupyterLab, Jupyter Notebook 7+ and more - The `JupyterLab Extension Examples Repository `_: A short tutorial series to learn how to develop extensions for JupyterLab by example. - :ref:`developer-extension-points`: A list of the most common JupyterLab extension points. - Another common pattern for extending JupyterLab document widgets with application plugins is covered in :ref:`documents`. Extension template ^^^^^^^^^^^^^^^^^^ We provide several templates to create JupyterLab extensions: - `extension-template `_: Create a JupyterLab extension using `copier `_ - [DEPRECATED] `extension-cookiecutter-ts `_: Create a JupyterLab extension using `cookiecutter `_ (use the copier template instead). API Reference Documentation ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Here is some autogenerated API documentation for JupyterLab and Lumino packages: - `JupyterLab API Documentation <../api/>`_ - `Lumino API Documentation `_ Overview of Extensions ---------------------- A JupyterLab plugin is the basic unit of extensibility in JupyterLab. An extension is a package that contains one or more JupyterLab plugins. Extensions can be distributed in two ways: - A *prebuilt extension* (since 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. - [DEPRECATED] 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. However, the total size of the JupyterLab code delivered to a user's browser may be reduced compared to using prebuilt extensions. See :ref:`deduplication` for the technical reasons for rebuilding JupyterLab when a source extension is installed. An extension can be published both as a source extension on NPM and as a prebuilt extension (e.g., published as a Python package). In some cases, system administrators may even choose to install a prebuilt extension by directly copying the prebuilt bundle to an appropriate directory, circumventing the need to create a Python package. If a source extension and a prebuilt extension with the same name are installed in JupyterLab, the prebuilt extension takes precedence. Because prebuilt extensions do not require a JupyterLab rebuild, they have a distinct advantage in multi-user systems where JupyterLab is installed at the system level. On such systems, only the system administrator has permissions to rebuild JupyterLab and install source extensions. Since prebuilt extensions can be installed at the per-user level, the per-environment level, or the system level, each user can have their own separate set of prebuilt extensions that are loaded dynamically in their browser on top of the system-wide JupyterLab. .. 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. Application plugins in core JupyterLab include the main menu system, the file browser, and the notebook, console, and file editor components. - **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. Examples of mime renderer plugins that come in core JupyterLab are the pdf viewer, the JSON viewer, and the Vega viewer. - **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. JupyterLab comes with light and dark theme plugins. Application Plugins ^^^^^^^^^^^^^^^^^^^ An application plugin is a JavaScript object with a number of metadata fields. A typical application plugin might look like this in TypeScript: .. code-block:: typescript const plugin: JupyterFrontEndPlugin = { id: 'my-extension:plugin', description: 'Provides a new service.', autoStart: true, requires: [ILabShell, ITranslator], optional: [ICommandPalette], provides: MyToken, activate: activateFunction }; 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`. - ``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. - ``description`` is an optional string. It allows to document the purpose of a plugin. - ``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 :ref:`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 :ref:`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 a plugin's ``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. - ``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. See :ref:`shell` for more details. See the JupyterLab API reference documentation for the ``JupyterFrontEnd`` class for more details. .. _dependency-injection-basic-info: .. _services: Plugins Interacting with Each Other ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *(The Provider-Consumer pattern is explained here)* 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. Note that these kinds of *provider* and *consumer* plugins are fundamental parts of JupyterLab's Provider-Consumer pattern (which is a type of `dependency-injection `_ pattern). .. _tokens: Tokens """""" 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, or from a third package exporting the token for both the provider and consumer) and list the token in their plugin metadata ``requires`` or ``optional`` fields. When JupyterLab instantiates the consumer plugin by calling its ``activate`` function, it will pass in the service associated with the token as an argument. If the service is not available (i.e., the token has not been registered with JupyterLab), then JupyterLab will either throw an error and not activate the consumer (if the token was listed in ``requires``), or will set the corresponding ``activate`` argument to ``null`` (if the token was listed in ``optional``). 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 consumer might list a token as ``optional`` when the service it identifies is not critical to the consumer, but would be nice to have if the service is available. For example, a consumer might list the status bar service as optional so that it can add an indicator to the status bar if it is available, but still make it possible for users running a customized JupyterLab distribution without a status bar to use the consumer plugin. A token defined in TypeScript can also define a TypeScript interface for the service associated with the token. If a package using the token uses TypeScript, the service will be type-checked against this interface when the package is compiled to JavaScript. .. note:: JupyterLab uses tokens to identify services (instead of strings, for example) to prevent conflicts between identifiers and to enable type checking when using TypeScript. Publishing Tokens """"""""""""""""" Since consumers will need to import a token used by a provider, the token should be exported in a published JavaScript package. Tokens will need to be deduplicated in JupyterLab—see :ref:`deduplication` for more details. A 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. .. We comment out the following, until we can import from a submodule of a package. See https://github.com/jupyterlab/jupyterlab/pull/9475. 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. .. _rendermime: MIME Renderer Plugins ^^^^^^^^^^^^^^^^^^^^^ MIME Renderer plugins are a convenience for creating a plugin that can render mime data in a notebook and files of the given mime type. MIME renderer plugins are more declarative and more restricted than standard plugins. A mime renderer plugin is an object with the fields listed in the `rendermime-interfaces IExtension <../api/interfaces/rendermime_interfaces.IRenderMime.IExtension.html>`__ object. JupyterLab has a `pdf mime renderer extension `__, for example. In core JupyterLab, this is used to view pdf files and view pdf data mime data in a notebook. We have a `MIME renderer example `__ walking through creating a mime renderer extension which adds mp4 video rendering to JupyterLab. The `extension template `_ supports MIME renderer extensions. The mime renderer can update its data by calling ``.setData()`` on the model it is given to render. This can be used for example to add a ``png`` representation of a dynamic figure, which will be picked up by a notebook model and added to the notebook document. When using ``IDocumentWidgetFactoryOptions``, you can update the document model by calling ``.setData()`` with updated data for the rendered MIME type. The document can then be saved by the user in the usual manner. Theme plugins ^^^^^^^^^^^^^ A theme is a special application plugin that registers a theme with the ``ThemeManager`` service. Theme CSS assets are specially bundled in an extension (see :ref:`themePath`) so they can be unloaded or loaded as the theme is activated. Since CSS files referenced by the ``style`` or ``styleModule`` keys are automatically bundled and loaded on the page, the theme files should not be referenced by these keys. The extension package containing the theme plugin must include all static assets that are referenced by ``@import`` in its theme CSS files. Local URLs can be used to reference files relative to the location of the referring sibling CSS files. For example ``url('images/foo.png')`` or ``url('../foo/bar.css')`` can be used to refer local files in the theme. Absolute URLs (starting with a ``/``) or external URLs (e.g. ``https:``) can be used to refer to external assets. See the `JupyterLab Light Theme `__ for an example. See the `TypeScript extension template `__ (choosing ``theme`` as ``kind`` ) for a quick start to developing a theme plugin. .. _source_extensions: Source Extensions ----------------- A source extension is a JavaScript (npm) package that exports one or more plugins. All JupyterLab extensions are developed as source extensions (for example, prebuilt extensions are built from source extensions). A source extension has metadata in the ``jupyterlab`` field of its ``package.json`` file. The `JSON schema `__ for the metadata is distributed in the ``@jupyterlab/builder`` package. We will talk about each ``jupyterlab`` metadata field in ``package.json`` for source extensions below. * ``extension``: :ref:`main_entry_point` * ``mimeExtension``: :ref:`mimeExtension` * ``themePath``: :ref:`themePath` * ``schemaDir``: :ref:`schemaDir` * ``disabledExtensions``: :ref:`disabledExtensions` * ``sharedPackages``: :ref:`deduplication` * ``discovery``: :ref:`ext-author-companion-packages` A JupyterLab extension must have at least one of ``jupyterlab.extension`` or ``jupyterlab.mimeExtension`` set. .. _main_entry_point: Application Plugins ^^^^^^^^^^^^^^^^^^^ The ``jupyterlab.extension`` field signifies that the package exports one or more JupyterLab application plugins. Set the value to ``true`` if the default export of the main package module (i.e., the file listed in the ``main`` key of ``package.json``) is an application plugin or a list of application plugins. If your plugins are exported as default exports from a different module, set this to the relative path to the module (e.g., ``"lib/foo"``). Example:: "jupyterlab": { "extension": true } .. _mimeExtension: MIME Renderer Plugins ^^^^^^^^^^^^^^^^^^^^^ The ``jupyterlab.mimeExtension`` field signifies that the package exports mime renderer plugins. Like the ``jupyterlab.extension`` field, the value can be a boolean (indicating a mime renderer plugin or list of mime renderer plugins is the default export from the ``main`` field), or a string, which is the relative path to the module exporting (as the default export) one or more mime renderer plugins. .. _themePath: Theme path ^^^^^^^^^^ Theme plugin assets (e.g., CSS files) need to bundled separately from a typical application plugin's assets so they can be loaded and unloaded as the theme is activated or deactivated. If an extension exports a theme plugin, it should give the relative path to the theme assets in the ``jupyterlab.themePath`` field:: "jupyterlab": { "extension": true, "themePath": "style/theme.css" } An extension cannot bundle multiple theme plugins. Ensure that the theme path files are included in the ``files`` metadata in ``package.json``. If you want to use SCSS, SASS, or LESS files, you must compile them to CSS and point ``jupyterlab.themePath`` to the CSS files. .. _schemaDir: Plugin Settings ^^^^^^^^^^^^^^^ JupyterLab exposes a plugin settings system that can be used to provide default setting values and user overrides. A plugin's settings are specified with a JSON schema file. The ``jupyterlab.schemaDir`` field in ``package.json`` gives the relative location of the directory containing plugin settings schema files. The setting system relies on plugin ids following the convention ``:``. The settings schema file for the plugin ``plugin_name`` is ``/.json``. For example, the JupyterLab ``filebrowser-extension`` package exports the ``@jupyterlab/filebrowser-extension:browser`` plugin. In the ``package.json`` for ``@jupyterlab/filebrowser-extension``, we have:: "jupyterlab": { "schemaDir": "schema", } The file browser setting schema file (which specifies some default keyboard shortcuts and other settings for the filebrowser) is located in ``schema/browser.json`` (see `here `__). See the `fileeditor-extension `__ for another example of an extension that uses settings. Please ensure that the schema files are included in the ``files`` metadata in ``package.json``. When declaring dependencies on JupyterLab packages, use the ``^`` operator before a package version so that the build system installs the newest patch or minor version for a given major version. For example, ``^4.0.0`` will install version 4.0.0, 4.0.1, 4.1.0, etc. A system administrator or user can override default values provided in a plugin's settings schema file with the :ref:`overrides.json ` file. .. _disabledExtensions: Disabling other extensions ^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``jupyterlab.disabledExtensions`` field gives a list of extensions or plugins to disable when this extension is installed, with the same semantics as the ``disabledExtensions`` field of :ref:`page_config.json `. This is useful if your extension overrides built-in extensions. For example, if an extension replaces the ``@jupyterlab/filebrowser-extension:share-file`` plugin to :ref:`override the "Copy Shareable Link" ` functionality in the file browser, it can automatically disable the ``@jupyterlab/filebrowser-extension:share-file`` plugin with:: "jupyterlab": { "disabledExtensions": ["@jupyterlab/filebrowser-extension:share-file"] } To disable all plugins in an extension, give the extension package name, e.g., ``"@jupyterlab/filebrowser-extension"`` in the above example. .. _deduplication: Deduplication of Dependencies ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``jupyterlab.sharedPackages`` field controls how dependencies are bundled, shared, and deduplicated with prebuilt extensions. 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. :ref:`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. JupyterLab automatically deduplicates the entire dependency tree between source extensions when it rebuilds itself during a source extension installation. Deduplication between source and prebuilt extensions, or between prebuilt extensions themselves, is a more nuanced problem (for those curious about implementation details, this deduplication in JupyterLab is powered by the Webpack 5.0 `module federation system `__). JupyterLab comes with a reasonable default strategy for deduplicating dependencies for prebuilt extensions. The ``jupyterlab.sharedPackages`` object in an extension's ``package.json`` enables an extension author to modify the default deduplication strategy for a given dependency with three boolean options. The keys of this object are dependency package names, and the values are either ``false`` (signifying that dependency should not be shared/deduplicated), or objects with up to three fields: * ``bundled``: if ``true`` (default), the dependency is bundled with the extension and is made available as one of the copies available to JupyterLab. If ``false``, the dependency is not bundled with the extension, so the extension will use a version of the dependency from a different extension. * ``singleton``: if ``true``, the extension will always prefer to use the copy of the dependency that other extensions are using, rather than using the highest version available. The default is ``false``. * ``strictVersion``: if ``true``, the extension will always make sure the copy of the dependency it is using satisfies the dependency version range it requires. By default, JupyterLab deduplicates direct dependencies of prebuilt extensions with direct dependencies of other source and prebuilt extensions, choosing the highest version of a dependency available to JupyterLab. JupyterLab chooses reasonable default options when using tokens and services from core JupyterLab packages. We suggest the following ``sharedPackages`` configurations when using tokens provided by packages other than core JupyterLab packages (see :ref:`services` for more details about using tokens). .. _dedup_provide_service: Providing a service """"""""""""""""""" When an extension (the "provider") is providing a service identified by a token that is imported from a dependency ``token-package``, the provider should configure the dependency as a singleton. This makes sure the provider is identifying the service with the same token that others are importing. If ``token-package`` is not a core package, it will be bundled with the provider and available for consumers to import if they :ref:`require the service `. .. code-block:: json "jupyterlab": { "sharedPackages": { "token-package": { "singleton": true } } } .. _dedup_require_service: Requiring a service """"""""""""""""""" When an extension (the "consumer") is requiring a service provided by another extension (the "provider"), identified by a token imported from a package (the ``token-package``, which may be the same as the provider), the consumer should configure the dependency ``token-package`` to be a singleton to ensure the consumer is getting the exact same token the provider is using to identify the service. Also, since the provider is providing a copy of ``token-package``, the consumer can exclude it from its bundle. .. code-block:: json "jupyterlab": { "sharedPackages": { "token-package": { "bundled": false, "singleton": true } } } .. _dedup_optional_service: Optionally using a service """""""""""""""""""""""""" When an extension (the "consumer") is optionally using a service identified by a token imported from a package (the ``token-package``), there is no guarantee that a provider is going to be available and bundling ``token-package``. In this case, the consumer should only configure ``token-package`` to be a singleton: .. code-block:: json "jupyterlab": { "sharedPackages": { "token-package": { "singleton": true } } } .. TODO: fill out the following text to a more complete explanation of how the deduplication works. Prebuilt extensions need to deduplicate many of their dependencies with other prebuilt extensions and with source extensions. This deduplication happens in two phases: 1. When JupyterLab is initialized in the browser, the core Jupyterlab build (including all source extensions) and each prebuilt extension can share copies of dependencies with a package cache in the browser. 2. A source or prebuilt extension can import a dependency from the cache while JupyterLab is running. The main options controlling how things work in this deduplication are as follows. If a package is listed in this sharing config, it will be requested from the package cache. * ``bundled`` - if true, a copy of this package is also provided to the package cache. If false, we will request a version from the package cache. Set this to false if we know that the package cache will have the package and you do not want to bundle a copy (perhaps to make your prebuilt bundle smaller). ``singleton`` - if true, makes sure to use the same copy of a dependency that others are using, even if it is not the right version. ``strictVersion`` - if true, throw an error if we would be using the wrong version of a dependency. .. _ext-author-companion-packages: Companion packages ^^^^^^^^^^^^^^^^^^ If your extension depends on the presence of one or more packages in the kernel, or on a notebook server extension, you can add metadata to indicate this to the extension manager by adding metadata to your package.json file. The full options available are:: "jupyterlab": { "discovery": { "kernel": [ { "kernel_spec": { "language": "", "display_name": "" // optional }, "base": { "name": "" }, "overrides": { // optional "": { "name": "" } }, "managers": [ // list of package managers that have your kernel package "pip", "conda" ] } ], "server": { "base": { "name": "" }, "overrides": { // optional "": { "name": "" } }, "managers": [ // list of package managers that have your server extension package "pip", "conda" ] } } } A typical setup for e.g. a jupyter-widget based package will then be:: "keywords": [ "jupyterlab-extension", "jupyter", "widgets", "jupyterlab" ], "jupyterlab": { "extension": true, "discovery": { "kernel": [ { "kernel_spec": { "language": "^python", }, "base": { "name": "myipywidgetspackage" }, "managers": [ "pip", "conda" ] } ] } } Currently supported package managers are ``pip`` and ``conda``. Extension CSS ^^^^^^^^^^^^^ If your extension has a top-level ``style`` key in ``package.json``, the CSS file it points to will be included on the page automatically. A convention in JupyterLab for deduplicating CSS on the page is that if your extension has a top-level ``styleModule`` key in ``package.json`` giving a JavaScript module that can be imported, it will be imported (as a JavaScript module) instead of importing the ``style`` key CSS file as a CSS file. Prebuilt Extensions ------------------- package.json metadata ^^^^^^^^^^^^^^^^^^^^^ In addition to the package metadata for :ref:`source extensions `, prebuilt extensions have extra ``jupyterlab`` metadata. * ``outputDir``: :ref:`outputDir` * ``webpackConfig``: :ref:`webpackConfig` .. _outputDir: Output Directory """""""""""""""" When JupyterLab builds the prebuilt extension, it creates a directory of files which can then be copied into the :ref:`appropriate install location `. The ``jupyterlab.outputDir`` field gives the relative path to the directory where these JavaScript and other files should be placed. A copy of the ``package.json`` file with additional build metadata will be put in the ``outputDir`` and the JavaScript and other files that will be served are put into the ``static`` subdirectory. .. code-block:: json "jupyterlab": { "outputDir": "mypackage/labextension" } .. _webpackConfig: Custom webpack config """"""""""""""""""""" .. warning:: This feature is *experimental* and may change without notice since it exposes internal implementation details (namely webpack). Be careful in using it, as a misconfiguration may break the prebuilt extension system. The prebuilt extension system uses the Webpack `Module Federation System `_. Normally this is an implementation detail that prebuilt extension authors do not need to worry about, but occasionally extension authors will want to tweak the configuration used to build their extension to enable various webpack features. Extension authors can specify a custom webpack config file that will be merged with the webpack config generated by the prebuilt extension system using the ``jupyterlab.webpackConfig`` field in ``package.json``. The value should be the relative path to the config file: .. code-block:: json "jupyterlab": { "webpackConfig": "./webpack.config.js" } Custom webpack configuration can be used to enable webpack features, configure additional file loaders, and for many other things. Here is an example of a ``webpack.config.js`` custom config that enables the async WebAssembly and top-level ``await`` experimental features of webpack: .. code-block:: javascript module.exports = { experiments: { topLevelAwait: true, asyncWebAssembly: true, } }; This custom config will be merged with the `prebuilt extension config `_ when building the prebuilt extension. .. _prebuilt_dev_workflow: Developing a prebuilt extension ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Build a prebuilt extension using the ``jupyter labextension build`` command. This command uses dependency metadata from the active JupyterLab to produce a set of files from a source extension that comprise the prebuilt extension. The files include a main entry point ``remoteEntry..js``, dependencies bundled into JavaScript files, ``package.json`` (with some extra build metadata), as well as plugin settings and theme directory structures if needed. While authoring a prebuilt extension, you can use the ``labextension develop`` command to create a link to your prebuilt output directory, similar to ``pip install -e``:: jupyter labextension develop . --overwrite Then rebuilding your extension and refreshing JupyterLab in the browser should pick up changes in your prebuilt extension source code. If using Windows, you may need to configure your operating system for the ``develop`` command described above to work, please see the note: :ref:`important-for-windows-users` If you are developing your prebuilt extension against the JupyterLab source repo, you can run JupyterLab with ``jupyter lab --dev-mode --extensions-in-dev-mode`` to have the development version of JupyterLab load prebuilt extensions. It would be best if you had in mind that the JupyterLab packages that your extension depends on may differ from those published; this means that your extension doesn’t build with JupyterLab dependencies from your node_modules folder but those in JupyterLab source code. If you are using TypeScript, the TypeScript compiler would complain because the dependencies of your extension may differ from those in JupyterLab. For that reason, you need to add to your ``tsconfig.json`` the path where to search for these dependencies by adding the option `paths `_: .. code-block:: json { "compilerOptions": { "baseUrl": ".", "paths": { "@jupyterlab/*": ["../jupyterlab/packages/*"], "*": ["node_modules/*"] } }, } When adding the path to find JupyterLab dependencies, it may cause troubles with other dependencies (like lumino or react) in your project because JupyterLab packages will take its dependencies from JupyterLab ``node_modules`` folder. In contrast, your packages will take them from your ``node_modules`` folder. To solve this problem, you’ll need to add the dependencies with conflicts to ``resolutions`` in your ``package.json``. This way, both projects (JupyterLab and your extension) use the same version of the duplicated dependencies. We provide a `extension template `_ that handles all of the scaffolding for an extension author, including the shipping of appropriate data files, so that when the user installs the package, the prebuilt extension ends up in ``share/jupyter/labextensions`` .. _distributing_prebuilt_extensions: Distributing a prebuilt extension ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prebuilt extensions can be distributed by any system that can copy the prebuilt assets into an appropriate location where JupyterLab can find them. The `official extension template `_ shows how to distribute prebuilt extensions via Python pip or conda packages. A system package manager, or even just an administrative script that copies directories, could be used as well. To distribute a prebuilt extension, copy its :ref:`output directory ` to a location where JupyterLab will find it, typically ``/share/jupyter/labextensions/``, where ```` is the JavaScript package name in the ``package.json``. For example, if your JavaScript package name is ``@my-org/my-package``, then the appropriate directory would be ``/share/jupyter/labextensions/@my-org/my-package``. The JupyterLab server makes the ``static/`` files available via a ``/labextensions/`` server handler. The settings and themes handlers in the server also load settings and themes from the prebuilt extension directories. If a prebuilt extension has the same name as a source extension, the prebuilt extension is preferred. .. _install.json: Packaging Information """"""""""""""""""""" Since prebuilt extensions are distributed in many ways (Python pip packages, conda packages, and potentially in many other packaging systems), the prebuilt extension directory can include an extra file, ``install.json``, that helps the user know how a prebuilt extension was installed and how to uninstall it. This file should be copied by the packaging system distributing the prebuilt extension into the top-level directory, for example ``/share/jupyter/labextensions//install.json``. This ``install.json`` file is used by JupyterLab to help a user know how to manage the extension. For example, ``jupyter labextension list`` includes information from this file, and ``jupyter labextension uninstall`` can print helpful uninstall instructions. Here is an example ``install.json`` file:: { "packageManager": "python", "packageName": "mypackage", "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package mypackage" } * ``packageManager``: This is the package manager that was used to install the prebuilt extension, for example, ``python``, ``pip``, ``conda``, ``debian``, ``system administrator``, etc. * ``packageName``: This is the package name of the prebuilt extension in the package manager above, which may be different than the package name in ``package.json``. * ``uninstallInstructions``: This is a short block of text giving the user instructions for uninstalling the prebuilt extension. For example, it might instruct them to use a system package manager or talk to a system administrator. .. _dev_trove_classifiers: PyPI Trove Classifiers """""""""""""""""""""" Extensions distributed as Python packages may declare additional metadata in the form of `trove classifiers `__. These improve the browsing experience for users on `PyPI `__. While including the license, development status, Python versions supported, and other topic classifiers are useful for many audiences, the following classifiers are specific to Jupyter and JupyterLab. .. code-block:: Framework :: Jupyter Framework :: Jupyter :: JupyterLab Framework :: Jupyter :: JupyterLab :: 1 Framework :: Jupyter :: JupyterLab :: 2 Framework :: Jupyter :: JupyterLab :: 3 Framework :: Jupyter :: JupyterLab :: 4 Framework :: Jupyter :: JupyterLab :: Extensions Framework :: Jupyter :: JupyterLab :: Extensions :: Mime Renderers Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt Framework :: Jupyter :: JupyterLab :: Extensions :: Themes Include each relevant classifier (and its parents) to help describe what your package provides to prospective users in your ``setup.py``, ``setup.cfg``, or ``pyproject.toml``. In particular ``Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt`` is used by the extension manager to get the available extensions from PyPI.org. .. hint:: For example, a theme, only compatible with JupyterLab 3, and distributed as a ready-to-run, prebuilt extension might look like: .. code-block:: python # setup.py setup( # the rest of the package's metadata # ... classifiers=[ "Framework :: Jupyter", "Framework :: Jupyter :: JupyterLab", "Framework :: Jupyter :: JupyterLab :: 3", "Framework :: Jupyter :: JupyterLab :: Extensions", "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt", "Framework :: Jupyter :: JupyterLab :: Extensions :: Themes", ] ) This would be discoverable from, for example, a `PyPI search for theme extensions `__. .. _source_dev_workflow: Development workflow for source extensions ------------------------------------------ :ref:`Developing prebuilt extensions ` is usually much easier since they do not require rebuilding JupyterLab to see changes. If you need to develop a source extension, here are some tips for a development workflow. While authoring a source extension, you can use the command: .. code-block:: bash jlpm install # install npm package dependencies jlpm run build # optional build step if using TypeScript, babel, etc. jupyter labextension install # install the current directory as an extension This causes the builder to re-install the source folder before building the application files. You can re-build at any time using ``jupyter lab build`` and it will reinstall these packages. You can also link other local ``npm`` packages that you are working on simultaneously using ``jupyter labextension link``; they will be re-installed but not considered as extensions. Local extensions and linked packages are included in ``jupyter labextension list``. When using local extensions and linked packages, you can run the command .. code-block:: shell jupyter lab --watch This will cause the application to incrementally rebuild when one of the linked packages changes. Note that only compiled JavaScript files (and the CSS files) are watched by the WebPack process. This means that if your extension is in TypeScript you'll have to run a ``jlpm run build`` before the changes will be reflected in JupyterLab. To avoid this step you can also watch the TypeScript sources in your extension which is usually assigned to the ``tsc -w`` shortcut. If webpack doesn't seem to detect the changes, this can be related to `the number of available watches `__. Note that the application is built against **released** versions of the core JupyterLab extensions. You should specify the version using the ``^`` operator, such as ``^4.0.0``, so that the build system can use newer minor and patch versions of a package with a particular major version. If your extension depends on JupyterLab packages, it should be compatible with the dependencies in the ``jupyterlab/static/package.json`` file. Note that building will always use the latest JavaScript packages that meet the dependency requirements of JupyterLab itself and any installed extensions. If you wish to test against a specific patch release of one of the core JupyterLab packages you can temporarily pin that requirement to a specific version in your own dependencies. If you want to test a source extension against the unreleased versions of JupyterLab, you can run the command .. code-block:: shell jupyter lab --watch --splice-source This command will splice the local ``packages`` directory into the application directory, allowing you to build source extension(s) against the current development sources. To statically build spliced sources, use ``jupyter lab build --splice-source``. Once a spliced build is created, any subsequent calls to `jupyter labextension build` will be in splice mode by default. A spliced build can be forced by calling ``jupyter labextension build --splice-source``. Note that :ref:`developing a prebuilt extension ` against a development version of JupyterLab is generally much easier than source package building. The package should export EMCAScript 6 compatible JavaScript. It can import CSS using the syntax ``require('foo.css')``. The CSS files can also import CSS from other packages using the syntax ``@import url('~foo/index.css')``, where ``foo`` is the name of the package. The following file types are also supported (both in JavaScript and CSS): ``json``, ``html``, ``jpg``, ``png``, ``gif``, ``svg``, ``js.map``, ``woff2``, ``ttf``, ``eot``. If your package uses any other file type it must be converted to one of the above types or `include a loader in the import statement `__. If you include a loader, the loader must be importable at build time, so if it is not already installed by JupyterLab, you must add it as a dependency of your extension. If your JavaScript is written in any other dialect than EMCAScript 6 (2015) it should be converted using an appropriate tool. You can use Webpack to pre-build your extension to use any of it's features not enabled in our build configuration. To build a compatible package set ``output.libraryTarget`` to ``"commonjs2"`` in your Webpack configuration. (see `this `__ example repo). If you publish your extension on ``npm.org``, users will be able to install it as simply ``jupyter labextension install ``, where ```` is the name of the published ``npm`` package. You can alternatively provide a script that runs ``jupyter labextension install`` against a local folder path on the user's machine or a provided tarball. Any valid ``npm install`` specifier can be used in ``jupyter labextension install`` (e.g. ``foo@latest``, ``bar@3.0.0.0``, ``path/to/folder``, and ``path/to/tar.gz``). We encourage extension authors to add the `jupyterlab-extension GitHub topic `__ to any GitHub extension repository. .. _testing_with_jest: Testing your extension ---------------------- .. note:: We highly recommend using the `extension template `_ to set up tests configuration. There are a number of helper functions in ``testutils`` in this repo (which is a public ``npm`` package called ``@jupyterlab/testutils``) that can be used when writing tests for an extension. See ``tests/test-application`` for an example of the infrastructure needed to run tests. If you are using `jest `__ to test your extension, you will need to transpile the jupyterlab packages to ``commonjs`` as they are using ES6 modules that ``node`` does not support. To transpile jupyterlab packages, you need to install the following package: .. code-block:: shell jlpm add --dev jest @types/jest ts-jest @babel/core@^7 @babel/preset-env@^7 Then in ``jest.config.js``, you will specify to use babel for js files and ignore all node modules except the ES6 modules: .. code-block:: javascript const jestJupyterLab = require('@jupyterlab/testutils/lib/jest-config'); const esModules = ['@jupyterlab/'].join('|'); const baseConfig = jestJupyterLab(__dirname); module.exports = { ...baseConfig, automock: false, collectCoverageFrom: [ 'src/**/*.{ts,tsx}', '!src/**/*.d.ts', '!src/**/.ipynb_checkpoints/*' ], coverageReporters: ['lcov', 'text'], testRegex: 'src/.*/.*.spec.ts[x]?$', transformIgnorePatterns: [ ...baseConfig.transformIgnorePatterns, `/node_modules/(?!${esModules}).+` ] }; Finally, you will need to configure babel with a ``babel.config.js`` file containing: .. code-block:: javascript module.exports = require('@jupyterlab/testutils/lib/babel.config');