extension_dev.rst 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. .. _developer_extensions:
  2. Extension Developer Guide
  3. =========================
  4. 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.
  5. 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.
  6. Other resources
  7. ---------------
  8. Before we get started, here are some resources for hands-on practice or more in-depth reference documentation.
  9. Tutorials
  10. ^^^^^^^^^
  11. We provide a set of guides to get started writing extensions for JupyterLab:
  12. - :ref:`extension_tutorial`: A tutorial to learn how to make a simple JupyterLab extension.
  13. - The `JupyterLab Extension Examples Repository <https://github.com/jupyterlab/extension-examples>`_: A short tutorial series to learn how to develop extensions for JupyterLab by example.
  14. - :ref:`developer-extension-points`: A list of the most common JupyterLab extension points.
  15. - Another common pattern for extending JupyterLab document widgets with application plugins is covered in :ref:`documents`.
  16. Cookiecutters
  17. ^^^^^^^^^^^^^
  18. We provide several cookiecutters to create JupyterLab extensions:
  19. - `extension-cookiecutter-ts <https://github.com/jupyterlab/extension-cookiecutter-ts>`_: Create a JupyterLab extension in TypeScript
  20. - `extension-cookiecutter-js <https://github.com/jupyterlab/extension-cookiecutter-js>`_: Create a JupyterLab extension in JavaScript
  21. - `mimerender-cookiecutter-ts <https://github.com/jupyterlab/mimerender-cookiecutter-ts>`_: Create a MIME Renderer JupyterLab extension in TypeScript
  22. - `theme-cookiecutter <https://github.com/jupyterlab/theme-cookiecutter>`_: Create a theme extension for JupyterLab
  23. API Reference Documentation
  24. ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  25. Here is some autogenerated API documentation for JupyterLab and Lumino packages:
  26. - `JupyterLab API Documentation <https://jupyterlab.github.io/jupyterlab/>`_
  27. - `Lumino API Documentation <https://jupyterlab.github.io/lumino/>`_
  28. Overview of Extensions
  29. ----------------------
  30. A JupyterLab 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:
  31. - 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.
  32. - 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.
  33. 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.
  34. Because prebuilt extensions do not require a JupyterLab rebuild, they have a distinct advantage in multiuser systems where JuptyerLab 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.
  35. .. tip::
  36. We recommend publishing prebuilt extensions in Python packages for user convenience.
  37. Plugins
  38. -------
  39. A JupyterLab plugin is the basic unit of extensibility in JupyterLab. JupyterLab supports several types of plugins:
  40. - **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.
  41. - **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.
  42. - **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.
  43. Application Plugins
  44. ^^^^^^^^^^^^^^^^^^^
  45. An application plugin is a JavaScript object with a number of metadata fields. A typical application plugin might look like this in TypeScript:
  46. .. code-block:: typescript
  47. const plugin: JupyterFrontEndPlugin<MyToken> = {
  48. id: 'my-extension:plugin',
  49. autoStart: true,
  50. requires: [ILabShell, ITranslator],
  51. optional: [ICommandPalette],
  52. provides: MyToken,
  53. activate: activateFunction
  54. };
  55. 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`.
  56. - ``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.
  57. - ``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.
  58. - ``requires`` and ``optional`` are lists of :ref:`tokens <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.
  59. - ``provides`` is the :ref:`token <tokens>` 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.
  60. - ``activate`` is the function called when your plugin is activated. The arguments are, in order, the :ref:`application object <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.
  61. .. _application_object:
  62. Application Object
  63. """"""""""""""""""
  64. 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:
  65. - ``commands`` - an extensible registry used to add and execute commands in the application.
  66. - ``docRegistry`` - an extensible registry containing the document types that the application is able to read and render.
  67. - ``restored`` - a promise that is resolved when the application has finished loading.
  68. - ``serviceManager`` - low-level manager for talking to the Jupyter REST API.
  69. - ``shell`` - a generic Jupyter front-end shell instance, which holds the user interface for the application. See :ref:`shell` for more details.
  70. See the JupyterLab API reference documentation for the ``JupyterFrontEnd`` class for more details.
  71. .. _services:
  72. Plugins Interacting with Each Other
  73. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  74. 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.
  75. 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.
  76. .. _tokens:
  77. Tokens
  78. """"""
  79. 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.
  80. 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.
  81. .. note::
  82. JupyterLab uses tokens to identify services (instead of strings, for example) to prevent conflicts between identifiers and to enable type checking when using TypeScript.
  83. Publishing Tokens
  84. """""""""""""""""
  85. 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.
  86. 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.
  87. ..
  88. We comment out the following, until we can import from a submodule of a package. See https://github.com/jupyterlab/jupyterlab/pull/9475.
  89. 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.
  90. .. _rendermime:
  91. Mime Renderer Plugins
  92. ^^^^^^^^^^^^^^^^^^^^^
  93. Mime Renderer plugins are a convenience for creating a plugin
  94. 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.
  95. A mime renderer plugin is an object with the fields listed in the
  96. `rendermime-interfaces IExtension <https://jupyterlab.github.io/jupyterlab/interfaces/_rendermime_interfaces_src_index_.irendermime.iextension.html>`__
  97. object.
  98. JupyterLab has a `pdf mime renderer extension <https://github.com/jupyterlab/jupyterlab/tree/master/packages/pdf-extension>`__, for example. In core JupyterLab, this is used to view pdf files and view pdf data mime data in a notebook.
  99. We have a `mime renderer tutorial <https://github.com/jupyterlab/jupyterlab-mp4>`__ walking through creating a mime renderer extension which adds mp4 video rendering to JupyterLab. We also have a `cookiecutter for mime renderer extensions <https://github.com/jupyterlab/mimerender-cookiecutter-ts>`__ in TypeScript.
  100. The mime renderer can update its data by calling ``.setData()`` on the
  101. model it is given to render. This can be used for example to add a
  102. ``png`` representation of a dynamic figure, which will be picked up by a
  103. notebook model and added to the notebook document. When using
  104. ``IDocumentWidgetFactoryOptions``, you can update the document model by
  105. calling ``.setData()`` with updated data for the rendered MIME type. The
  106. document can then be saved by the user in the usual manner.
  107. Theme plugins
  108. ^^^^^^^^^^^^^
  109. 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.
  110. 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.
  111. See the `JupyterLab Light Theme <https://github.com/jupyterlab/jupyterlab/tree/master/packages/theme-light-extension>`__ for an example.
  112. See the `TypeScript theme cookiecutter <https://github.com/jupyterlab/theme-cookiecutter>`__ for a quick start to developing a theme plugin.
  113. Source Extensions
  114. -----------------
  115. 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).
  116. A source extension has metadata in the ``jupyterlab`` field of its ``package.json`` file. The `JSON schema <https://github.com/jupyterlab/jupyterlab/blob/master/builder/metadata_schema.json>`__ for the metadata is distributed in the ``@jupyterlab/builder`` package.
  117. If you would like publish your source extension to npm and want users to be able to install your source extension, we recommend including the npm keyword ``jupyterlab-extension`` in ``package.json``. This enables JupyterLab's extension manager to find your extension and display it for users in its graphical interface::
  118. "keywords": [
  119. "jupyterlab-extension"
  120. ],
  121. We will talk about each ``jupyterlab`` metadata field in ``package.json`` for source extensions below.
  122. * ``extension``: :ref:`main_entry_point`
  123. * ``mimeExtension``: :ref:`mimeExtension`
  124. * ``themePath``: :ref:`themePath`
  125. * ``schemaDir``: :ref:`schemaDir`
  126. * ``disabledExtensions``: :ref:`disabledExtensions`
  127. * ``sharedPackages``: :ref:`deduplication`
  128. * ``discovery``: :ref:`ext-author-companion-packages`
  129. A JupyterLab extension must have at least one of ``jupyterlab.extension`` or ``jupyterlab.mimeExtension`` set.
  130. .. _main_entry_point:
  131. Application Plugins
  132. ^^^^^^^^^^^^^^^^^^^
  133. 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::
  134. "jupyterlab": {
  135. "extension": true
  136. }
  137. .. _mimeExtension:
  138. Mime Renderer Plugins
  139. ^^^^^^^^^^^^^^^^^^^^^
  140. 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.
  141. .. _themePath:
  142. Theme path
  143. ^^^^^^^^^^
  144. 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::
  145. "jupyterlab": {
  146. "extension": true,
  147. "themePath": "style/index.css"
  148. }
  149. An extension cannot bundle multiple theme plugins, and any other CSS in the package is ignored (so any other application or mimeRenderer plugins in the package cannot have associated CSS).
  150. 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.
  151. .. _schemaDir:
  152. Plugin Settings
  153. ^^^^^^^^^^^^^^^
  154. JupyterLab exposes a plugin settings system that can be used to provide
  155. 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.
  156. The setting system relies on plugin ids following the convention ``<source_package_name>:<plugin_name>``. The settings schema file for the plugin ``plugin_name`` is ``<schemaDir>/<plugin_name>.json``.
  157. For example, the JupyterLab ``filebrowser-extension`` package exports the ``@jupyterlab/filebrowser-extension:browser`` plugin. In the ``package.json`` for ``@jupyterlab/filebrowser-extension``, we have::
  158. "jupyterlab": {
  159. "schemaDir": "schema",
  160. }
  161. 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 <https://github.com/jupyterlab/jupyterlab/blob/master/packages/filebrowser-extension/schema/browser.json>`__).
  162. See the
  163. `fileeditor-extension <https://github.com/jupyterlab/jupyterlab/tree/master/packages/fileeditor-extension>`__
  164. for another example of an extension that uses settings.
  165. Please ensure that the schema files are included in the ``files`` metadata in ``package.json``.
  166. A system administrator or user can override default values provided in a plugin's settings schema file with the :ref:`overrides.json <overridesjson>` file.
  167. .. _disabledExtensions:
  168. Disabling other extensions
  169. ^^^^^^^^^^^^^^^^^^^^^^^^^^
  170. 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 <page_configjson>`. 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" <copy_shareable_link>` functionality in the file browser, it can automatically disable the ``@jupyterlab/filebrowser-extension:share-file`` plugin with::
  171. "jupyterlab": {
  172. "disabledExtensions": ["@jupyterlab/filebrowser-extension:share-file"]
  173. }
  174. To disable all plugins in an extension, give the extension package name, e.g., ``"@jupyterlab/filebrowser-extension"`` in the above example.
  175. .. _deduplication:
  176. Deduplication of Dependencies
  177. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  178. The ``jupyterlab.sharedPackages`` field controls how dependencies are bundled, shared, and deduplicated among extensions.
  179. 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 <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.
  180. JupyterLab automatically deduplicates the entire dependency tree between source extensions when it rebuilds itself during a source extension installation. This deduplication is relatively straightforward because the entire dependency tree is known at one time (during the rebuild), and all deduplication is figured out once on the server. Deduplication between source and prebuilt extensions, or between prebuilt extensions themselves, is a more nuanced problem that needs to happen in the browser every time JuptyerLab is run, and is discussed below.
  181. TODO: insert a paragraph here with the summary of how to use sharedPackages in common case, then refer the reader to the below for an explanation of deduplication and understanding the concepts better.
  182. Deduplication with prebuilt extensions
  183. """"""""""""""""""""""""""""""""""""""
  184. Prebuilt extensions need to deduplicate many of their dependencies with other prebuilt extensions and with source extensions. This deduplication happens in two phases:
  185. 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.
  186. 2. A source or prebuilt extension can import a dependency from the cache while JupyterLab is running.
  187. 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.
  188. * ``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).
  189. ``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.
  190. ``strictVersion`` - if true, throw an error if we would be using the wrong version of a dependency.
  191. In general, strictVersion defaults to true - we want to make sure we are using a required version of a dependency.
  192. JupyterLab has a default list of singleton packages (including core Lumino and JupyterLab packages).
  193. We suggest when dealing with tokens, bundled is true if providing a token or consuming an optional token, and false if consuming a required token (i.e., in that case, we just want to use the copy of the package provided by whoever is providing the token to the system).
  194. The basic tool we have available is a JavaScript package cache available in a running JupyterLab. Extensions can provide copies of dependencies to the cache, and can ask for specific version ranges. Depending on how an extension is configured, it will generally get the highest version of the dependency provided to the system.
  195. In general, the highest version satisfying the requirements
  196. Goal here is to explain the three main options in ``jupyterlab.sharedPackages``: ``bundled``, ``singleton``, and ``strictVersion``.
  197. idea behind deduplication here is that a dependency (and its entire dependency tree) is bundled together and placed in a cache of shared Javascript packages.
  198. By default, JupyterLab shares direct dependencies of source and prebuilt extensions in a way that other prebuilt extensions can deduplicate with them. The ``jupyterlab.sharedpackages`` configuration affects how this deduplication works.
  199. The ``jupyterlab.sharedPackages`` configuration enables you to control how dependencies are bundled with your extension when building JupyterLab (or when building your extension when creating a prebuilt extension). ``sharedPackages`` is an object where the keys are JavaScript package names and values are sharing configuration. Set the value to ``false`` to not share a dependency with other packages. Set the value to an object to control how it is shared.
  200. The ``jupyterlab.sharedPackages`` field is an object where keys are JavaScript package names and values are sharing configuration options from the Webpack 5 module federation
  201. * ``bundled``: ``true`` if the dependency should be bundled, ``false`` if it should not be bundled.
  202. * ``singleton``: ``true`` if only one version is allowed
  203. Usually the only fields needed here are ``bundled: false`` to not bundle a dependency (but rely on another extension to bundle the dependency). Do this if you import a token from the dependency,
  204. To ensure that a consumer plugin 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).
  205. For those curious about implementation details, JupyterLab relies on the Webpack 5.0 `module federation system <https://webpack.js.org/concepts/module-federation/>`__ to implement this module sharing between source and prebuilt, or between prebuilt extensions.
  206. .. _ext-author-companion-packages:
  207. Companion packages
  208. ^^^^^^^^^^^^^^^^^^
  209. If your extension depends on the presence of one or more packages in the
  210. kernel, or on a notebook server extension, you can add metadata to indicate
  211. this to the extension manager by adding metadata to your package.json file.
  212. The full options available are::
  213. "jupyterlab": {
  214. "discovery": {
  215. "kernel": [
  216. {
  217. "kernel_spec": {
  218. "language": "<regexp for matching kernel language>",
  219. "display_name": "<regexp for matching kernel display name>" // optional
  220. },
  221. "base": {
  222. "name": "<the name of the kernel package>"
  223. },
  224. "overrides": { // optional
  225. "<manager name, e.g. 'pip'>": {
  226. "name": "<name of kernel package on pip, if it differs from base name>"
  227. }
  228. },
  229. "managers": [ // list of package managers that have your kernel package
  230. "pip",
  231. "conda"
  232. ]
  233. }
  234. ],
  235. "server": {
  236. "base": {
  237. "name": "<the name of the server extension package>"
  238. },
  239. "overrides": { // optional
  240. "<manager name, e.g. 'pip'>": {
  241. "name": "<name of server extension package on pip, if it differs from base name>"
  242. }
  243. },
  244. "managers": [ // list of package managers that have your server extension package
  245. "pip",
  246. "conda"
  247. ]
  248. }
  249. }
  250. }
  251. A typical setup for e.g. a jupyter-widget based package will then be::
  252. "keywords": [
  253. "jupyterlab-extension",
  254. "jupyter",
  255. "widgets",
  256. "jupyterlab"
  257. ],
  258. "jupyterlab": {
  259. "extension": true,
  260. "discovery": {
  261. "kernel": [
  262. {
  263. "kernel_spec": {
  264. "language": "^python",
  265. },
  266. "base": {
  267. "name": "myipywidgetspackage"
  268. },
  269. "managers": [
  270. "pip",
  271. "conda"
  272. ]
  273. }
  274. ]
  275. }
  276. }
  277. Currently supported package managers are ``pip`` and ``conda``.
  278. CSS Imports
  279. ^^^^^^^^^^^
  280. - Discuss how JupyterLab puts your extension CSS on the page
  281. - Cover duplication of CSS issues
  282. - Mention the package.json styleModule key
  283. JupyterLab will prefer to put the ``styleModule`` key, then the style key, if they exist.
  284. Prebuilt Extensions
  285. -------------------
  286. package.json metadata
  287. ^^^^^^^^^^^^^^^^^^^^^
  288. In addition to the package metadata for source extensions, prebuilt extensions have extra ``jupyterlab`` metadata for where the prebuilt assets should go.
  289. * ``outputDir``: :ref:`outputDir`
  290. * ``webpackConfig``: :ref:`webpackConfig`
  291. .. _outputDir:
  292. Output Directory
  293. """"""""""""""""
  294. When JupyterLab builds the prebuilt extension, it creates a JavaScript bundle which can then be copied into the appropriate location. The ``jupyterlab.outputDir`` field gives the relative path to the directory where this JavaScript bundle should be placed:
  295. .. code-block:: json
  296. "jupyterlab": {
  297. "outputDir": "mypackage/labextension"
  298. }
  299. .. _webpackConfig:
  300. Custom webpack config
  301. """""""""""""""""""""
  302. .. warning::
  303. 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.
  304. The prebuilt extension system uses ``webpack`` to build extensions, relying on the
  305. `Module Federation System <https://webpack.js.org/concepts/module-federation/>`_ added in webpack 5. 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:
  306. .. code-block:: json
  307. "jupyterlab": {
  308. "webpackConfig": "./webpack.config.js"
  309. }
  310. Here is an example of a ``webpack.config.js`` custom config that enables the async WebAssembly and top-level ``await`` experimental features of webpack:
  311. .. code-block:: javascript
  312. module.exports = {
  313. experiments: {
  314. topLevelAwait: true,
  315. asyncWebAssembly: true,
  316. }
  317. };
  318. This custom config will be merged with the `prebuilt extension config <https://github.com/jupyterlab/jupyterlab/blob/master/builder/src/extensionConfig.ts>`_
  319. when building the prebuilt extension.
  320. Packaging Information
  321. ^^^^^^^^^^^^^^^^^^^^^
  322. Since prebuilt extensions are distributed in many ways (Python pip packages, conda packages, and potentially in many other packaging systems), packages 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 is put in the appropriate location by the packaging system distributing the prebuilt extension, and 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::
  323. {
  324. "packageManager": "python",
  325. "packageName": "mypackage",
  326. "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package mypackage"
  327. }
  328. * ``packageManager``: This is the package manager that was used to install the prebuilt extension, for example, ``python``, ``pip``, ``conda``, ``debian``, ``system administrator``, etc.
  329. * ``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``.
  330. * ``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.
  331. Steps for building
  332. ^^^^^^^^^^^^^^^^^^
  333. - We provide a ``jupyter labextension build`` script that is used to build prebuilt bundles
  334. - The command produces a set of static assets that are shipped along with a package (notionally on ``pip``/``conda``)
  335. - It is a Python cli so that it can use the dependency metadata from the active JupyterLab
  336. - The assets include a module federation ``remoteEntry.*.js``, generated bundles, and some other files that we use
  337. - ``package.json`` is the original ``package.json`` file that we use to gather metadata about the package, with some included build metadata
  338. - we use the previously existing ``@jupyterlab/builder -> build`` to generate the ``imports.css``, ``schemas`` and ``themes`` file structure
  339. - We provide a ``labextensions`` handler in ``jupyterlab_server`` that loads static assets from ``labextensions`` paths, following a similar logic to how ``nbextensions`` are discovered and loaded from disk
  340. - The ``settings`` and ``themes`` handlers in ``jupyterlab_server`` has been updated to load from the new ``labextensions`` locations, favoring the prebuilt extension locations over the bundled ones
  341. - A ``labextension develop`` command has been added to install an in-development extension into JupyterLab. The default behavior is to create a symlink in the ``sys-prefix/share/jupyter/labextensions/package-name`` to the static directory of the extension
  342. - We provide a ``cookiecutter`` that handles all of the scaffolding for an extension author, including the shipping of ``data_files`` so that when the user installs the package, the static assets end up in ``share/jupyter/labextensions``
  343. Implementation details
  344. ^^^^^^^^^^^^^^^^^^^^^^
  345. How prebuilt extensions work
  346. """"""""""""""""""""""""""""
  347. Directory walkthrough
  348. """""""""""""""""""""
  349. Packaging extensions
  350. --------------------
  351. Most extensions are single JavaScript packages, and can be shipped on npmjs.org.
  352. This makes them discoverable by the JupyterLab extension manager, provided they
  353. have the ``jupyterlab-extension`` keyword in their ``package.json``. If the package also
  354. contains a server extension (Python package), the author has two options.
  355. The server extension and the JupyterLab extension can be shipped in a single package,
  356. or they can be shipped separately.
  357. The JupyterLab extension can be bundled in a package on PyPI and conda-forge so
  358. that it ends up in the user's application directory. Note that the user will still have to run ``jupyter lab build``
  359. (or build when prompted in the UI) in order to use the extension.
  360. The general idea is to pack the Jupyterlab extension using ``npm pack``, and then
  361. use the ``data_files`` logic in ``setup.py`` to ensure the file ends up in the
  362. ``<jupyterlab_application>/share/jupyter/lab/extensions``
  363. directory.
  364. Note that even if the JupyterLab extension is unusable without the
  365. server extension, as long as you use the companion package metadata it is still
  366. useful to publish it to npmjs.org so it is discoverable by the JupyterLab extension manager.
  367. The server extension can be enabled on install by using ``data_files``.
  368. an example of this approach is `jupyterlab-matplotlib <https://github.com/matplotlib/jupyter-matplotlib/tree/ce9cc91e52065d33e57c3265282640f2aa44e08f>`__. The file used to enable the server extension is `here <https://github.com/matplotlib/jupyter-matplotlib/blob/ce9cc91e52065d33e57c3265282640f2aa44e08f/jupyter-matplotlib.json>`__. The logic to ship the JS tarball and server extension
  369. enabler is in `setup.py <https://github.com/matplotlib/jupyter-matplotlib/blob/ce9cc91e52065d33e57c3265282640f2aa44e08f/setup.py>`__. Note that the ``setup.py``
  370. file has additional logic to automatically create the JS tarball as part of the
  371. release process, but this could also be done manually.
  372. Runtime configuration
  373. ---------------------
  374. - We handle disabling of lab extensions using a trait on the ``LabApp`` class, so it can be set by admins and overridden by users. Extensions are automatically enabled when installed, and must be explicitly disabled. The disabled config can consist of a package name or a plugin regex pattern
  375. - ``page_config`` and ``overrides`` are also handled with traits so that admins can provide defaults and users can provide overrides
  376. Development workflow
  377. --------------------
  378. We encourage extension authors to add the `jupyterlab-extension GitHub topic
  379. <https://github.com/search?utf8=%E2%9C%93&q=topic%3Ajupyterlab-extension&type=Repositories>`__ to any GitHub extension repository.
  380. While authoring the extension, you can use the command:
  381. .. code-block:: bash
  382. npm install # install npm package dependencies
  383. npm run build # optional build step if using TypeScript, babel, etc.
  384. jupyter labextension install # install the current directory as an extension
  385. This causes the builder to re-install the source folder before building
  386. the application files. You can re-build at any time using
  387. ``jupyter lab build`` and it will reinstall these packages.
  388. You can also link other local ``npm`` packages that you are working on
  389. simultaneously using ``jupyter labextension link``; they will be re-installed
  390. but not considered as extensions. Local extensions and linked packages are
  391. included in ``jupyter labextension list``.
  392. When using local extensions and linked packages, you can run the command
  393. ::
  394. jupyter lab --watch
  395. This will cause the application to incrementally rebuild when one of the
  396. linked packages changes. Note that only compiled JavaScript files (and
  397. the CSS files) are watched by the WebPack process. This means that if
  398. your extension is in TypeScript you'll have to run a ``jlpm run build``
  399. before the changes will be reflected in JupyterLab. To avoid this step
  400. you can also watch the TypeScript sources in your extension which is
  401. usually assigned to the ``tsc -w`` shortcut. If WebPack doesn't seem to
  402. detect the changes, this can be related to `the number of available watches <https://github.com/webpack/docs/wiki/troubleshooting#not-enough-watchers>`__.
  403. Note that the application is built against **released** versions of the
  404. core JupyterLab extensions. If your extension depends on JupyterLab
  405. packages, it should be compatible with the dependencies in the
  406. ``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
  407. specific patch release of one of the core JupyterLab packages you can
  408. temporarily pin that requirement to a specific version in your own
  409. dependencies.
  410. If you must install an extension into a development branch of JupyterLab, you have to graft it into the source tree of JupyterLab itself. This may be done using the command
  411. ::
  412. jlpm run add:sibling <path-or-url>
  413. in the JupyterLab root directory, where ``<path-or-url>`` refers either
  414. to an extension ``npm`` package on the local file system, or a URL to a git
  415. repository for an extension ``npm`` package. This operation may be
  416. subsequently reversed by running
  417. ::
  418. jlpm run remove:package <extension-dir-name>
  419. This will remove the package metadata from the source tree and delete
  420. all of the package files.
  421. The package should export EMCAScript 6 compatible JavaScript. It can
  422. import CSS using the syntax ``require('foo.css')``. The CSS files can
  423. also import CSS from other packages using the syntax
  424. ``@import url('~foo/index.css')``, where ``foo`` is the name of the
  425. package.
  426. The following file types are also supported (both in JavaScript and
  427. CSS): ``json``, ``html``, ``jpg``, ``png``, ``gif``, ``svg``,
  428. ``js.map``, ``woff2``, ``ttf``, ``eot``.
  429. If your package uses any other file type it must be converted to one of
  430. the above types or `include a loader in the import statement <https://webpack.js.org/concepts/loaders/#inline>`__.
  431. If you include a loader, the loader must be importable at build time, so if
  432. it is not already installed by JupyterLab, you must add it as a dependency
  433. of your extension.
  434. If your JavaScript is written in any other dialect than
  435. EMCAScript 6 (2015) it should be converted using an appropriate tool.
  436. You can use Webpack to pre-build your extension to use any of it's features
  437. not enabled in our build configuration. To build a compatible package set
  438. ``output.libraryTarget`` to ``"commonjs2"`` in your Webpack configuration.
  439. (see `this <https://github.com/saulshanabrook/jupyterlab-webpack>`__ example repo).
  440. Another option to try out your extension with a local version of JupyterLab is to add it to the
  441. list of locally installed packages and to have JupyterLab register your extension when it starts up.
  442. You can do this by adding your extension to the ``jupyterlab.externalExtensions`` key
  443. in the ``dev_mode/package.json`` file. It should be a mapping
  444. of extension name to version, just like in ``dependencies``. Then run ``jlpm run integrity``
  445. and these extensions should be added automatically to the ``dependencies`` and pulled in.
  446. When you then run ``jlpm run build && jupyter lab --dev`` or ``jupyter lab --dev --watch`` this extension
  447. will be loaded by default. For example, this is how you can add the Jupyter Widgets
  448. extensions:
  449. ::
  450. "externalExtensions": {
  451. "@jupyter-widgets/jupyterlab-manager": "2.0.0"
  452. },
  453. If you publish your extension on ``npm.org``, users will be able to install
  454. it as simply ``jupyter labextension install <foo>``, where ``<foo>`` is
  455. the name of the published ``npm`` package. You can alternatively provide a
  456. script that runs ``jupyter labextension install`` against a local folder
  457. path on the user's machine or a provided tarball. Any valid
  458. ``npm install`` specifier can be used in
  459. ``jupyter labextension install`` (e.g. ``foo@latest``, ``bar@3.0.0.0``,
  460. ``path/to/folder``, and ``path/to/tar.gz``).
  461. Testing your extension
  462. ^^^^^^^^^^^^^^^^^^^^^^
  463. There are a number of helper functions in ``testutils`` in this repo (which
  464. is a public ``npm`` package called ``@jupyterlab/testutils``) that can be used when
  465. writing tests for an extension. See ``tests/test-application`` for an example
  466. of the infrastructure needed to run tests. There is a ``karma`` config file
  467. that points to the parent directory's ``karma`` config, and a test runner,
  468. ``run-test.py`` that starts a Jupyter server.
  469. If you are using `jest <https://jestjs.io/>`__ to test your extension, you will
  470. need to transpile the jupyterlab packages to ``commonjs`` as they are using ES6 modules
  471. that ``node`` does not support.
  472. To transpile jupyterlab packages, you need to install the following package:
  473. ::
  474. jlpm add --dev jest@^24 @types/jest@^24 ts-jest@^24 @babel/core@^7 @babel/preset-env@^7
  475. Then in `jest.config.js`, you will specify to use babel for js files and ignore
  476. all node modules except the jupyterlab ones:
  477. ::
  478. module.exports = {
  479. preset: 'ts-jest/presets/js-with-babel',
  480. moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  481. transformIgnorePatterns: ['/node_modules/(?!(@jupyterlab/.*)/)'],
  482. globals: {
  483. 'ts-jest': {
  484. tsConfig: 'tsconfig.json'
  485. }
  486. },
  487. ... // Other options useful for your extension
  488. };
  489. Finally, you will need to configure babel with a ``babel.config.js`` file containing:
  490. ::
  491. module.exports = {
  492. presets: [
  493. [
  494. '@babel/preset-env',
  495. {
  496. targets: {
  497. node: 'current'
  498. }
  499. }
  500. ]
  501. ]
  502. };