File: extension_dev.rst

package info (click to toggle)
jupyterlab 4.0.11%2Bds1%2B~cs11.25.27-7
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 43,496 kB
  • sloc: javascript: 18,395; python: 8,932; sh: 399; makefile: 95; perl: 33; xml: 1
file content (798 lines) | stat: -rw-r--r-- 50,193 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
.. 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_migration

Other resources
---------------

Before we get started, here are some resources for hands-on practice or more in-depth reference documentation.

Tutorials
^^^^^^^^^

We provide a set of guides to get started writing extensions for JupyterLab:

- :ref:`extension_tutorial`: A tutorial to learn how to make a simple JupyterLab extension.
- 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.
- :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 <https://github.com/jupyterlab/extension-template>`_: Create a JupyterLab extension using `copier <https://copier.readthedocs.io/>`_
- [DEPRECATED] `extension-cookiecutter-ts <https://github.com/jupyterlab/extension-cookiecutter-ts>`_: Create a JupyterLab extension using `cookiecutter <https://cookiecutter.readthedocs.io>`_ (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 <https://lumino.readthedocs.io/en/latest/api/index.html>`_

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<MyToken> = {
     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 <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 <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.
- ``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.

.. _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.

.. _services:

Plugins Interacting with Each Other
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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.

.. _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 <https://github.com/jupyterlab/jupyterlab/tree/4.0.x/packages/pdf-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 <https://github.com/jupyterlab/extension-examples/tree/master/mimerenderer>`__  walking through creating a mime renderer extension which adds mp4 video rendering to JupyterLab. The `extension template <https://github.com/jupyterlab/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 <https://github.com/jupyterlab/jupyterlab/tree/4.0.x/packages/theme-light-extension>`__ for an example.

See the `TypeScript extension template <https://github.com/jupyterlab/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 <https://github.com/jupyterlab/jupyterlab/blob/4.0.x/builder/metadata_schema.json>`__ 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 ``<source_package_name>:<plugin_name>``. The settings schema file for the plugin ``plugin_name`` is ``<schemaDir>/<plugin_name>.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 <https://github.com/jupyterlab/jupyterlab/blob/4.0.x/packages/filebrowser-extension/schema/browser.json>`__).

See the
`fileeditor-extension <https://github.com/jupyterlab/jupyterlab/tree/4.0.x/packages/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 <overridesjson>` 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 <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::

        "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 <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 <https://webpack.js.org/concepts/module-federation/>`__). 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 <dedup_require_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": "<regexp for matching kernel language>",
              "display_name": "<regexp for matching kernel display name>"   // optional
            },
            "base": {
              "name": "<the name of the kernel package>"
            },
            "overrides": {   // optional
              "<manager name, e.g. 'pip'>": {
                "name": "<name of kernel package on pip, if it differs from base name>"
              }
            },
            "managers": [   // list of package managers that have your kernel package
                "pip",
                "conda"
            ]
          }
        ],
        "server": {
          "base": {
            "name": "<the name of the server extension package>"
          },
          "overrides": {   // optional
            "<manager name, e.g. 'pip'>": {
              "name": "<name of server extension package on pip, if it differs from base 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 <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 <distributing_prebuilt_extensions>`. 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 <https://webpack.js.org/concepts/module-federation/>`_. 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 <https://github.com/jupyterlab/jupyterlab/blob/4.0.x/builder/src/extensionConfig.ts>`_
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.<hash>.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 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 <https://www.typescriptlang.org/tsconfig#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 <https://github.com/jupyterlab/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 <https://github.com/jupyterlab/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 <outputDir>` to a location where JupyterLab will find it, typically  ``<sys-prefix>/share/jupyter/labextensions/<package-name>``, where ``<package-name>`` 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 ``<sys-prefix>/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 ``<sys-prefix>/share/jupyter/labextensions/<package-name>/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 <https://pypi.org/classifiers>`__. These improve the browsing
experience for users on `PyPI <https://pypi.org/search>`__. 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 <https://pypi.org/search/?c=Framework+%3A%3A+Jupyter+%3A%3A+JupyterLab+%3A%3A+Extensions+%3A%3A+Themes>`__.

.. _source_dev_workflow:

Development workflow for source extensions
------------------------------------------

:ref:`Developing prebuilt extensions <prebuilt_dev_workflow>` 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 <https://github.com/webpack/docs/wiki/troubleshooting#not-enough-watchers>`__.

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 <prebuilt_dev_workflow>` 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 <https://webpack.js.org/concepts/loaders/#inline>`__.
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 <https://github.com/saulshanabrook/jupyterlab-webpack>`__ example repo).

If you publish your extension on ``npm.org``, users will be able to install
it as simply ``jupyter labextension install <foo>``, where ``<foo>`` 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
<https://github.com/search?utf8=%E2%9C%93&q=topic%3Ajupyterlab-extension&type=Repositories>`__ to any GitHub extension repository.

.. _testing_with_jest:

Testing your extension
----------------------

.. note::

    We highly recommend using the `extension template <https://github.com/jupyterlab/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 <https://jestjs.io/>`__ 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');