extension_dev.rst 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. .. _developer_extensions:
  2. Extension Developer Guide
  3. -------------------------
  4. JupyterLab can be extended in three ways via:
  5. - **application plugins (top level):** Application plugins extend the
  6. functionality of JupyterLab itself.
  7. - **mime renderer extension (top level):** Mime Renderer extensions are
  8. a convenience for creating an extension that can render mime data and
  9. potentially render files of a given type.
  10. - document widget extensions (lower level): Document widget extensions
  11. extend the functionality of document widgets added to the
  12. application, and we cover them in :ref:`documents`.
  13. See :ref:`xkcd_extension_tutorial` to learn how to make a simple JupyterLab extension.
  14. A JupyterLab application is comprised of:
  15. - A core Application object
  16. - Plugins
  17. Plugins
  18. ~~~~~~~
  19. A plugin adds a core functionality to the application:
  20. - A plugin can require other plugins for operation.
  21. - A plugin is activated when it is needed by other plugins, or when
  22. explicitly activated.
  23. - Plugins require and provide ``Token`` objects, which are used to
  24. provide a typed value to the plugin's ``activate()`` method.
  25. - The module providing plugin(s) must meet the
  26. `JupyterLab.IPluginModule <http://jupyterlab.github.io/jupyterlab/interfaces/_application_src_index_.jupyterlab.ipluginmodule.html>`__
  27. interface, by exporting a plugin object or array of plugin objects as
  28. the default export.
  29. We provide two cookie cutters to create JuptyerLab plugin extensions in
  30. `CommonJS <https://github.com/jupyterlab/extension-cookiecutter-js>` and
  31. `TypeScript <https://github.com/jupyterlab/extension-cookiecutter-ts>`.
  32. The default plugins in the JupyterLab application include:
  33. - `Terminal <https://github.com/jupyterlab/jupyterlab/blob/master/packages/terminal-extension/src/index.ts>`__
  34. - Adds the ability to create command prompt terminals.
  35. - `Shortcuts <https://github.com/jupyterlab/jupyterlab/blob/master/packages/shortcuts-extension/src/index.ts>`__
  36. - Sets the default set of shortcuts for the application.
  37. - `Images <https://github.com/jupyterlab/jupyterlab/blob/master/packages/imageviewer-extension/src/index.ts>`__
  38. - Adds a widget factory for displaying image files.
  39. - `Help <https://github.com/jupyterlab/jupyterlab/blob/master/packages/help-extension/src/index.ts>`__
  40. - Adds a side bar widget for displaying external documentation.
  41. - `File
  42. Browser <https://github.com/jupyterlab/jupyterlab/blob/master/packages/filebrowser-extension/src/index.ts>`__
  43. - Creates the file browser and the document manager and the file
  44. browser to the side bar.
  45. - `Editor <https://github.com/jupyterlab/jupyterlab/blob/master/packages/fileeditor-extension/src/index.ts>`__
  46. - Add a widget factory for displaying editable source files.
  47. - `Console <https://github.com/jupyterlab/jupyterlab/blob/master/packages/console-extension/src/index.ts>`__
  48. - Adds the ability to launch Jupyter Console instances for
  49. interactive kernel console sessions.
  50. A dependency graph for the core JupyterLab plugins (along with links to
  51. their source) is shown here: |dependencies|
  52. Application Object
  53. ~~~~~~~~~~~~~~~~~~
  54. The JupyterLab Application object is given to each plugin in its
  55. ``activate()`` function. The Application object has a:
  56. - commands - used to add and execute commands in the application.
  57. - keymap - used to add keyboard shortcuts to the application.
  58. - shell - a JupyterLab shell instance.
  59. JupyterLab Shell
  60. ~~~~~~~~~~~~~~~~
  61. The JupyterLab
  62. `shell <http://jupyterlab.github.io/jupyterlab/classes/_application_src_shell_.applicationshell.html>`__
  63. is used to add and interact with content in the application. The
  64. application consists of:
  65. - A top area for things like top level menus and toolbars
  66. - Left and right side bar areas for collapsible content
  67. - A main area for user activity.
  68. - A bottom area for things like status bars
  69. Phosphor
  70. ~~~~~~~~
  71. The Phosphor library is used as the underlying architecture of
  72. JupyterLab and provides many of the low level primitives and widget
  73. structure used in the application. Phosphor provides a rich set of
  74. widgets for developing desktop-like applications in the browser, as well
  75. as patterns and objects for writing clean, well-abstracted code. The
  76. widgets in the application are primarily **Phosphor widgets**, and
  77. Phosphor concepts, like message passing and signals, are used
  78. throughout. **Phosphor messages** are a *many-to-one* interaction that
  79. enables information like resize events to flow through the widget
  80. hierarchy in the application. **Phosphor signals** are a *one-to-many*
  81. interaction that enable listeners to react to changes in an observed
  82. object.
  83. Extension Authoring
  84. ~~~~~~~~~~~~~~~~~~~
  85. An Extension is a valid `npm
  86. package <https://docs.npmjs.com/getting-started/what-is-npm>`__ that
  87. meets the following criteria:
  88. - Exports one or more JupyterLab plugins as the default export in its
  89. main file.
  90. - Has a ``jupyterlab`` key in its ``package.json`` which has
  91. ``"extension"`` metadata. The value can be ``true`` to use the main
  92. module of the package, or a string path to a specific module (e.g.
  93. ``"lib/foo"``).
  94. While authoring the extension, you can use the command:
  95. .. code:: bash
  96. npm install # install npm package dependencies
  97. npm run build # optional build step if using TypeScript, babel, etc.
  98. jupyter labextension install # install the current directory as an extension
  99. This causes the builder to re-install the source folder before building
  100. the application files. You can re-build at any time using
  101. ``jupyter lab build`` and it will reinstall these packages. You can also
  102. link other local npm packages that you are working on simultaneously
  103. using ``jupyter labextension link``; they will be re-installed but not
  104. considered as extensions. Local extensions and linked packages are
  105. included in ``jupyter labextension list``.
  106. When using local extensions and linked packages, you can run the command
  107. ::
  108. jupyter lab --watch
  109. This will cause the application to incrementally rebuild when one of the
  110. linked packages changes. Note that only compiled JavaScript files (and
  111. the CSS files) are watched by the WebPack process.
  112. Note that the application is built against **released** versions of the
  113. core JupyterLab extensions. If your extension depends on JupyterLab
  114. packages, it should be compatible with the dependencies in the
  115. ``jupyterlab/static/package.json`` file. If you must install a extension
  116. into a development branch of JupyterLab, you have to graft it into the
  117. source tree of JupyterLab itself. This may be done using the command
  118. ::
  119. jlpm run add:sibling <path-or-url>
  120. in the JupyterLab root directory, where ``<path-or-url>`` refers either
  121. to an extension npm package on the local filesystem, or a URL to a git
  122. repository for an extension npm package. This operation may be
  123. subsequently reversed by running
  124. ::
  125. jlpm run remove:package <extension-dir-name>
  126. This will remove the package metadata from the source tree, but wil
  127. **not** remove any files added by the ``addsibling`` script, which
  128. should be removed manually.
  129. The package should export EMCAScript 5 compatible JavaScript. It can
  130. import CSS using the syntax ``require('foo.css')``. The CSS files can
  131. also import CSS from other packages using the syntax
  132. ``@import url('~foo/index.css')``, where ``foo`` is the name of the
  133. package.
  134. The following file types are also supported (both in JavaScript and
  135. CSS): json, html, jpg, png, gif, svg, js.map, woff2, ttf, eot.
  136. If your package uses any other file type it must be converted to one of
  137. the above types. If your JavaScript is written in any other dialect than
  138. EMCAScript 5 it must be converted using an appropriate tool.
  139. If you publish your extension on npm.org, users will be able to install
  140. it as simply ``jupyter labextension install <foo>``, where ``<foo>`` is
  141. the name of the published npm package. You can alternatively provide a
  142. script that runs ``jupyter labextension install`` against a local folder
  143. path on the user's machine or a provided tarball. Any valid
  144. ``npm install`` specifier can be used in
  145. ``jupyter labextension install`` (e.g. ``foo@latest``, ``bar@3.0.0.0``,
  146. ``path/to/folder``, and ``path/to/tar.gz``).
  147. Mime Renderer Extensions
  148. ~~~~~~~~~~~~~~~~~~~~~~~~
  149. Mime Renderer extensions are a convenience for creating an extension
  150. that can render mime data and potentially render files of a given type.
  151. We provide cookiecutters for Mime render extensions in
  152. `JavaScript <https://github.com/jupyterlab/mimerender-cookiecutter>`__ and
  153. `TypeScript <https://github.com/jupyterlab/mimerender-cookiecutter-ts>`__.
  154. Mime renderer extensions are more declarative than standard extensions.
  155. The extension is treated the same from the command line perspective
  156. (``jupyter labextension install`` ), but it does not directly create
  157. JupyterLab plugins. Instead it exports an interface given in the
  158. `rendermime-interfaces <http://jupyterlab.github.io/jupyterlab/interfaces/_rendermime_interfaces_src_index_.irendermime.iextension.html>`__
  159. package.
  160. The JupyterLab repo has an example mime renderer extension for
  161. `pdf <https://github.com/jupyterlab/jupyterlab/tree/master/packages/pdf-extension>`__
  162. files. It provides a mime renderer for pdf data and registers itself as
  163. a document renderer for pdf file types.
  164. The ``rendermime-interfaces`` package is intended to be the only
  165. JupyterLab package needed to create a mime renderer extension (using the
  166. interfaces in TypeScript or as a form of documentation if using plain
  167. JavaScript).
  168. The only other difference from a standard extension is that has a
  169. ``jupyterlab`` key in its ``package.json`` with ``"mimeExtension"``
  170. metadata. The value can be ``true`` to use the main module of the
  171. package, or a string path to a specific module (e.g. ``"lib/foo"``).
  172. The mime renderer can update its data by calling ``.setData()`` on the
  173. model it is given to render. This can be used for example to add a
  174. ``png`` representation of a dynamic figure, which will be picked up by a
  175. notebook model and added to the notebook document. When using
  176. ``IDocumentWidgetFactoryOptions``, you can update the document model by
  177. calling ``.setData()`` with updated data for the rendered MIME type. The
  178. document can then be saved by the user in the usual manner.
  179. Themes
  180. ~~~~~~
  181. A theme is a JupyterLab extension that uses a ``ThemeManager`` and can
  182. be loaded and unloaded dynamically. The package must include all static
  183. assets that are referenced by ``url()`` in its CSS files. Local URLs can
  184. be used to reference files relative to the location of the referring CSS
  185. file in the theme directory. For example ``url('images/foo.png')`` or
  186. ``url('../foo/bar.css')``\ can be used to refer local files in the
  187. theme. Absolute URLs (starting with a ``/``) or external URLs (e.g.
  188. ``https:``) can be used to refer to external assets. The path to the
  189. theme assets is specified ``package.json`` under the ``"jupyterlab"``
  190. key as ``"themeDir"``. See the `JupyterLab Light
  191. Theme <https://github.com/jupyterlab/jupyterlab/tree/master/packages/theme-light-extension>`__
  192. for an example. Ensure that the theme files are included in the
  193. ``"files"`` metadata in package.json. A theme can optionally specify an
  194. ``embed.css`` file that can be consumed outside of a JupyterLab
  195. application.
  196. To quickly create a theme based on the JupyterLab Light Theme, follow
  197. the instructions in the `contributing
  198. guide <CONTRIBUTING.html#setting-up-a-development-environment>`__ and
  199. then run ``jlpm run create:theme`` from the repository root directory.
  200. Once you select a name, title and a description, a new theme folder will
  201. be created in the current directory. You can move that new folder to a
  202. location of your choice, and start making desired changes.
  203. The theme extension is installed the same as a regular extension (see
  204. [extension authoring](#Extension Authoring)).
  205. Standard (General-Purpose) Extensions
  206. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  207. See the example, `How to Extend the Notebook
  208. Plugin <./notebook.html#how-to-extend-the-notebook-plugin>`__. Notice
  209. that the mime renderer and themes extensions above use a limited,
  210. simplified interface to JupyterLab's extension system. Modifying the
  211. notebook plugin requires the full, general-purpose interface to the
  212. extension system.
  213. Extension Settings
  214. ~~~~~~~~~~~~~~~~~~
  215. An extension can specify user settings using a JSON Schema. The schema
  216. definition should be in a file that resides in the ``schemaDir``
  217. directory that is specified in the ``package.json`` file of the
  218. extension. The actual file name should use is the part that follows the
  219. package name of extension. So for example, the JupyterLab
  220. ``apputils-extension`` package hosts several plugins:
  221. - ``'@jupyterlab/apputils-extension:menu'``
  222. - ``'@jupyterlab/apputils-extension:palette'``
  223. - ``'@jupyterlab/apputils-extension:settings'``
  224. - ``'@jupyterlab/apputils-extension:themes'``
  225. And in the ``package.json`` for ``@jupyterlab/apputils-extension``, the
  226. ``schemaDir`` field is a directory called ``schema``. Since the
  227. ``themes`` plugin requires a JSON schema, its schema file location is:
  228. ``schema/themes.json``. The plugin's name is used to automatically
  229. associate it with its settings file, so this naming convention is
  230. important. Ensure that the schema files are included in the ``"files"``
  231. metadata in ``package.json``.
  232. See the
  233. `fileeditor-extension <https://github.com/jupyterlab/jupyterlab/tree/master/packages/fileeditor-extension>`__
  234. for another example of an extension that uses settings.
  235. Storing Extension Data
  236. ~~~~~~~~~~~~~~~~~~~~~~
  237. In addition to the file system that is accessed by using the
  238. ``@jupyterlab/services`` package, JupyterLab offers two ways for
  239. extensions to store data: a client-side state database that is built on
  240. top of ``localStorage`` and a plugin settings system that provides for
  241. default setting values and user overrides.
  242. State Database
  243. ^^^^^^^^^^^^^^
  244. The state database can be accessed by importing ``IStateDB`` from
  245. ``@jupyterlab/coreutils`` and adding it to the list of ``requires`` for
  246. a plugin:
  247. .. code:: typescript
  248. const id = 'foo-extension:IFoo';
  249. const IFoo = new Token<IFoo>(id);
  250. interface IFoo {}
  251. class Foo implements IFoo {}
  252. const plugin: JupyterLabPlugin<IFoo> = {
  253. id,
  254. requires: [IStateDB],
  255. provides: IFoo,
  256. activate: (app: JupyterLab, state: IStateDB): IFoo => {
  257. const foo = new Foo();
  258. const key = `${id}:some-attribute`;
  259. // Load the saved plugin state and apply it once the app
  260. // has finished restoring its former layout.
  261. Promise.all([state.fetch(key), app.restored])
  262. .then(([saved]) => { /* Update `foo` with `saved`. */ });
  263. // Fulfill the plugin contract by returning an `IFoo`.
  264. return foo;
  265. },
  266. autoStart: true
  267. };
  268. Context Menus
  269. ^^^^^^^^^^^^^
  270. JupyterLab has an application-wide context menu available as
  271. ``app.contextMenu``. See the Phosphor
  272. `docs <http://phosphorjs.github.io/phosphor/api/widgets/interfaces/contextmenu.iitemoptions.html>`__
  273. for the item creation options. If you wish to preempt the the
  274. application context menu, you can use a 'contextmenu' event listener and
  275. call ``event.stopPropagation`` to prevent the application context menu
  276. handler from being called (it is listening in the bubble phase on the
  277. ``document``). At this point you could show your own Phosphor
  278. `contextMenu <http://phosphorjs.github.io/phosphor/api/widgets/classes/contextmenu.html>`__,
  279. or simply stop propagation and let the system context menu be shown.
  280. This would look something like the following in a ``Widget`` subclass:
  281. .. code:: javascript
  282. // In `onAfterAttach()`
  283. this.node.addEventListener('contextmenu', this);
  284. // In `handleEvent()`
  285. case 'contextmenu':
  286. event.stopPropagation();
  287. .. |dependencies| image:: dependency-graph.svg