开发扩展#

JupyterLab 应用程序由一个核心应用程序对象和一组扩展组成。JupyterLab 扩展提供了 JupyterLab 中几乎所有功能,包括笔记本、文档编辑器和查看器、代码控制台、终端、主题、文件浏览器、上下文帮助系统、调试器和设置编辑器。扩展甚至提供了应用程序更基本的部分,例如菜单系统、状态栏以及与服务器的底层通信机制。

JupyterLab 扩展是一个包含多个 JupyterLab 插件的包。我们将讨论如何编写插件,然后如何将一组插件打包成 JupyterLab 扩展。

有关更详细的信息,请参阅下面的部分,或浏览此页面的其余部分以获取概述。

警告

您的扩展可能会在 JupyterLab 的新版本中出现故障。如 向后兼容性、版本和重大更改 中所述,JupyterLab 的开发和发布周期遵循语义版本控制,因此我们建议您规划您的开发流程以考虑可能发生的未来重大更改,这些更改可能会破坏您的扩展的用户。考虑在您的项目中为用户记录您的维护计划,或在您项目的包元数据中为您的扩展兼容的 JupyterLab 版本设置上限。

其他资源#

在我们开始之前,这里有一些资源可用于动手练习或更深入的参考文档。

教程#

使用这些指南了解如何编写 JupyterLab 扩展

扩展模板#

我们提供几个模板来创建 JupyterLab 扩展

API 参考文档#

以下是 JupyterLab 和 Lumino 包的自动生成的 API 文档。

扩展概述#

JupyterLab 插件是 JupyterLab 可扩展性的基本单元。扩展是一个包含一个或多个 JupyterLab 插件的包。扩展可以通过两种方式分发。

  • 预构建扩展(自 JupyterLab 3.0 起)分发一个从源扩展预构建的 JavaScript 代码包,该代码包可以加载到 JupyterLab 中而无需重建 JupyterLab。在这种情况下,扩展开发人员使用 JupyterLab 提供的工具将源扩展编译成一个 JavaScript 包,该包包含非 JupyterLab JavaScript 依赖项,然后将生成的包分发到例如 Python pip 或 conda 包中。安装预构建扩展不需要 Node.js。

  • [已弃用] 源扩展是一个导出一个或多个插件的 JavaScript(npm)包。安装源扩展需要用户重建 JupyterLab。此重建步骤需要 Node.js,并且可能需要大量时间和内存,因此某些用户可能无法安装源扩展。但是,与使用预构建扩展相比,传递到用户浏览器中的 JupyterLab 代码的总大小可能会减少。有关重建 JupyterLab 时安装源扩展的技术原因,请参见 依赖项的去重

扩展可以同时发布为 NPM 上的源扩展和预构建扩展(例如,发布为 Python 包)。在某些情况下,系统管理员甚至可以选择通过将预构建包直接复制到适当的目录来安装预构建扩展,从而绕过创建 Python 包的需要。如果在 JupyterLab 中安装了具有相同名称的源扩展和预构建扩展,则预构建扩展优先。

由于预构建扩展不需要 JupyterLab 重建,因此它们在 JupyterLab 安装在系统级别的多用户系统中具有明显的优势。在这些系统上,只有系统管理员有权重建 JupyterLab 并安装源扩展。由于预构建扩展可以在每个用户级别、每个环境级别或系统级别安装,因此每个用户都可以拥有自己的独立的预构建扩展集,这些扩展集在他们的浏览器中动态加载到系统范围的 JupyterLab 之上。

提示

我们建议在 Python 包中发布预构建扩展,以方便用户。

插件#

JupyterLab 插件是 JupyterLab 可扩展性的基本单元。JupyterLab 支持几种类型的插件。

  • 应用程序插件:应用程序插件是 JupyterLab 功能的基本构建块。应用程序插件通过要求其他插件提供的服务与 JupyterLab 和其他插件交互,并可以选择向系统提供自己的服务。核心 JupyterLab 中的应用程序插件包括主菜单系统、文件浏览器以及笔记本、控制台和文件编辑器组件。

  • Mime 渲染器插件:Mime 渲染器插件是扩展 JupyterLab 以在笔记本和文件中渲染自定义 mime 数据的简化、受限的方式。当这些插件加载时,JupyterLab 会自动将它们转换为等效的应用程序插件。核心 JupyterLab 中附带的 mime 渲染器插件示例包括 pdf 查看器、JSON 查看器和 Vega 查看器。

  • 主题插件:主题插件提供了一种通过更改可主题化值(即 CSS 变量值)以及向 JupyterLab 提供其他字体和图形来自定义 JupyterLab 外观的方式。JupyterLab 附带浅色和深色主题插件。

应用程序插件#

应用程序插件是一个具有多个元数据字段的 JavaScript 对象。典型的应用程序插件在 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
};

idactivate 字段是必需的,其他字段可以省略。有关如何使用 requiresoptionalprovides 字段的更多信息,请参见 插件相互交互

  • id 是一个必需的唯一字符串。约定是使用 NPM 扩展包名称、冒号,然后是标识扩展内插件的字符串。

  • description 是一个可选字符串。它允许记录插件的目的。

  • autostart 指示您的插件是否应该在应用程序启动时激活。通常这应该为 true。如果为 false 或省略,您的插件将在任何其他插件请求您的插件提供的令牌时激活。

  • requiresoptional令牌 的列表,对应于其他插件提供的服务。这些服务将在插件激活时作为参数传递给 activate 函数。如果 requires 服务未在 JupyterLab 中注册,则会抛出错误,并且插件不会激活。

  • provides 是与您的插件提供给系统的服务关联的 令牌。如果您的插件没有向系统提供服务,请省略此字段,并且不要从您的 activate 函数返回任何值。

  • activate 是在您的插件激活时调用的函数。参数按顺序是 应用程序对象、与 requires 令牌对应的服务,然后是与 optional 令牌对应的服务(如果该特定 optional 令牌未在系统中注册,则为 null)。如果给出了 provides 令牌,则 activate 函数的返回值(如果返回的是 Promise,则为解析后的返回值)将被注册为与该令牌关联的服务。

应用程序对象#

Jupyter 前端应用程序对象作为第一个参数传递给插件的 activate 函数。应用程序对象具有许多用于与应用程序交互的属性和方法,包括

  • commands - 一个可扩展的注册表,用于在应用程序中添加和执行命令。

  • docRegistry - 一个可扩展的注册表,包含应用程序能够读取和呈现的文档类型。

  • restored - 当应用程序完成加载时解析的 Promise。

  • serviceManager - 用于与 Jupyter REST API 交互的低级管理器。

  • shell - 一个通用的 Jupyter 前端 shell 实例,它包含应用程序的用户界面。有关更多详细信息,请参阅 Jupyter 前端 Shell

有关更多详细信息,请参阅 JupyterLab API 参考文档中的 JupyterFrontEnd 类。

插件相互交互#

(这里解释了提供者-消费者模式)

JupyterLab 插件系统的一个基本功能是,应用程序插件可以通过向系统提供服务并要求其他插件提供的服务来与其他插件交互。服务可以是任何 JavaScript 值,通常是一个具有方法和数据属性的 JavaScript 对象。例如,提供 JupyterLab 主菜单的核心插件向系统提供了一个 主菜单 服务对象,该对象具有添加新顶级菜单和与现有顶级应用程序菜单交互的属性。

在以下讨论中,向系统提供服务的插件是提供者插件,而要求和使用服务的插件是消费者插件。请注意,这些类型的提供者消费者插件是 JupyterLab 提供者-消费者模式(这是一种 依赖注入 模式)的基本部分。

令牌#

插件提供的服务由一个令牌标识,即 Lumino 令牌类的具体实例。提供者插件在其插件元数据 provides 字段中列出该令牌,并从其 activate 函数返回关联的服务。

消费者插件导入令牌(例如,从提供者插件的扩展 JavaScript 包中,或从同时为提供者和消费者导出令牌的第三方包中),并在其插件元数据 requiresoptional 字段中列出令牌。当 JupyterLab 通过调用其 activate 函数实例化消费者插件时,它将传递与令牌关联的服务作为参数。如果服务不可用(即令牌尚未注册到 JupyterLab),则 JupyterLab 将要么抛出错误并不会激活消费者(如果令牌在 requires 中列出),要么将相应的 activate 参数设置为 null(如果令牌在 optional 中列出)。JupyterLab 按顺序激活插件,以确保服务的提供者在消费者之前被激活。一个令牌只能在系统中注册一次。

消费者可能会将令牌列为 optional,当它标识的服务对消费者来说不是至关重要的,但如果服务可用,则会很好。例如,消费者可能会将状态栏服务列为可选,以便它可以在状态栏可用时添加一个指示器,但仍然允许运行没有状态栏的自定义 JupyterLab 发行版的用户使用消费者插件。

在 TypeScript 中定义的令牌也可以为与令牌关联的服务定义一个 TypeScript 接口。如果使用令牌的包使用 TypeScript,则在包编译为 JavaScript 时,服务将根据此接口进行类型检查。

注意

JupyterLab 使用令牌来标识服务(而不是字符串,例如),以防止标识符之间的冲突,并在使用 TypeScript 时启用类型检查。

发布令牌#

由于消费者需要导入提供者使用的令牌,因此令牌应在发布的 JavaScript 包中导出。令牌需要在 JupyterLab 中进行去重——有关更多详细信息,请参阅 依赖项的去重

核心 JupyterLab 中的一种模式是从第三方包中创建和导出令牌,提供者和消费者扩展都导入该包,而不是在提供者的包中定义令牌。这使用户能够将提供者扩展替换为提供相同令牌但具有替代服务实现的不同扩展。例如,核心 JupyterLab filebrowser 包导出一个表示文件浏览器服务的令牌(启用与文件浏览器的交互)。filebrowser-extension 包包含一个插件,该插件在 JupyterLab 中实现文件浏览器,并将文件浏览器服务提供给 JupyterLab(使用从 filebrowser 包中导入的令牌标识)。想要与文件浏览器交互的 JupyterLab 中的扩展不需要对 filebrowser-extension 包具有 JavaScript 依赖关系,而只需要从 filebrowser 包中导入令牌。这种模式使用户能够通过编写自己的扩展来无缝地更改 JupyterLab 中的文件浏览器,该扩展从 filebrowser 包中导入相同的令牌,并使用他们自己的替代文件浏览器服务将其提供给系统。

MIME 渲染器插件#

MIME 渲染器插件是创建插件的便捷方式,该插件可以在笔记本和给定 MIME 类型的文件中渲染 MIME 数据。MIME 渲染器插件比标准插件更具声明性,也更受限制。MIME 渲染器插件是一个对象,其字段列在 rendermime-interfaces IExtension 对象中。

例如,JupyterLab 有一个 pdf mime 渲染器扩展。在核心 JupyterLab 中,它用于查看 pdf 文件和查看笔记本中的 pdf 数据 MIME 数据。

我们有一个 MIME 渲染器示例,它逐步介绍了如何创建一个 MIME 渲染器扩展,该扩展将 mp4 视频渲染添加到 JupyterLab。扩展模板 支持 MIME 渲染器扩展。

MIME 渲染器可以通过调用它被赋予渲染的模型上的 .setData() 来更新其数据。例如,这可以用于添加动态图形的 png 表示,该表示将被笔记本模型拾取并添加到笔记本文档中。当使用 IDocumentWidgetFactoryOptions 时,您可以通过使用渲染的 MIME 类型的更新数据调用 .setData() 来更新文档模型。然后,用户可以以通常的方式保存文档。

主题插件#

主题是一个特殊的应用程序插件,它向 ThemeManager 服务注册一个主题。主题 CSS 资产在扩展中以特殊方式捆绑(参见 主题路径),以便在激活主题时可以卸载或加载它们。由于 stylestyleModule 键引用的 CSS 文件会自动捆绑并在页面上加载,因此主题文件不应由这些键引用。

包含主题插件的扩展包必须包含其主题 CSS 文件中 @import 引用的所有静态资产。可以使用本地 URL 来引用相对于引用兄弟 CSS 文件位置的文件。例如,url('images/foo.png')url('../foo/bar.css') 可用于引用主题中的本地文件。可以使用绝对 URL(以 / 开头)或外部 URL(例如 https:)来引用外部资产。

参见 JupyterLab Light Theme 作为示例。

参见 TypeScript 扩展模板(选择 theme 作为 kind)以快速开始开发主题插件。

源扩展#

源扩展是一个 JavaScript(npm)包,它导出一个或多个插件。所有 JupyterLab 扩展都作为源扩展开发(例如,预构建扩展是从源扩展构建的)。

源扩展在其 package.json 文件的 jupyterlab 字段中具有元数据。元数据的 JSON 架构@jupyterlab/builder 包中分发。

我们将在下面讨论源扩展的 package.json 中的每个 jupyterlab 元数据字段。

JupyterLab 扩展必须至少设置 jupyterlab.extensionjupyterlab.mimeExtension 之一。

应用程序插件#

jupyterlab.extension 字段表示该包导出一个或多个 JupyterLab 应用程序插件。如果主包模块的默认导出(即 package.jsonmain 键中列出的文件)是应用程序插件或应用程序插件列表,则将该值设置为 true。如果您的插件作为不同模块的默认导出导出,则将其设置为该模块的相对路径(例如,"lib/foo")。示例

"jupyterlab": {
  "extension": true
}

MIME 渲染器插件#

字段 jupyterlab.mimeExtension 表示该包导出 MIME 渲染器插件。类似于 jupyterlab.extension 字段,其值可以是布尔值(表示 MIME 渲染器插件或 MIME 渲染器插件列表是 main 字段的默认导出),也可以是字符串,表示导出一个或多个 MIME 渲染器插件(作为默认导出)的模块的相对路径。

主题路径#

主题插件资产(例如,CSS 文件)需要与典型应用程序插件的资产分开打包,以便在激活或停用主题时可以加载和卸载它们。如果扩展导出主题插件,它应该在 jupyterlab.themePath 字段中给出主题资产的相对路径。

"jupyterlab": {
  "extension": true,
  "themePath": "style/theme.css"
}

扩展不能捆绑多个主题插件。

确保主题路径文件包含在 package.json 中的 files 元数据中。如果你想使用 SCSS、SASS 或 LESS 文件,你必须将它们编译成 CSS,并将 jupyterlab.themePath 指向 CSS 文件。

插件设置#

JupyterLab 提供了一个插件设置系统,可用于提供默认设置值和用户覆盖。插件的设置使用 JSON 模式文件指定。 package.json 中的 jupyterlab.schemaDir 字段给出包含插件设置模式文件的目录的相对位置。

设置系统依赖于插件 ID 遵循约定 <source_package_name>:<plugin_name>。插件 plugin_name 的设置模式文件是 <schemaDir>/<plugin_name>.json

例如,JupyterLab filebrowser-extension 包导出 @jupyterlab/filebrowser-extension:browser 插件。在 @jupyterlab/filebrowser-extensionpackage.json 中,我们有

"jupyterlab": {
  "schemaDir": "schema",
}

文件浏览器设置模式文件(它指定了文件浏览器的某些默认键盘快捷键和其他设置)位于 schema/browser.json 中(参见 此处)。

请参阅 fileeditor-extension,了解使用设置的另一个扩展示例。

请确保模式文件包含在 package.json 中的 files 元数据中。

在声明对 JupyterLab 包的依赖项时,请在包版本之前使用 ^ 运算符,以便构建系统安装给定主要版本的最新补丁或次要版本。例如,^4.0.0 将安装版本 4.0.0、4.0.1、4.1.0 等。

系统管理员或用户可以使用 overrides.json 文件覆盖插件设置模式文件中提供的默认值。

禁用其他扩展#

字段 jupyterlab.disabledExtensions 给出了在安装此扩展时要禁用的扩展或插件列表,其语义与 page_config.jsondisabledExtensions 字段相同。如果你的扩展覆盖了内置扩展,这将很有用。例如,如果扩展替换了 @jupyterlab/filebrowser-extension:share-file 插件以 覆盖文件浏览器中的“复制可共享链接” 功能,它可以使用以下方法自动禁用 @jupyterlab/filebrowser-extension:share-file 插件:

"jupyterlab": {
  "disabledExtensions": ["@jupyterlab/filebrowser-extension:share-file"]
}

要禁用扩展中的所有插件,请给出扩展包名称,例如,在上面的示例中为 "@jupyterlab/filebrowser-extension"

依赖项的去重#

字段 jupyterlab.sharedPackages 控制着依赖项如何与预构建的扩展捆绑、共享和去重。

JupyterLab 扩展系统中一个重要的关注点和挑战是去重扩展的依赖项,而不是让扩展使用它们自己的捆绑的依赖项副本。例如,JupyterLab 依赖的 Lumino 小部件系统用于跨应用程序进行通信,它要求所有包使用 @lumino/widgets 包的同一副本。标识插件服务的 令牌 也需要在服务的提供者和消费者之间共享,因此导出令牌的依赖项需要去重。

JupyterLab 在源扩展安装期间重建自身时,会自动去重源扩展之间的整个依赖项树。源扩展和预构建扩展之间,或预构建扩展本身之间的去重是一个更细致的问题(对于那些对实现细节感兴趣的人来说,JupyterLab 中的这种去重由 Webpack 5.0 的 模块联合系统 提供支持)。JupyterLab 带有一个合理的默认策略,用于去重预构建扩展的依赖项。扩展的 package.json 中的 jupyterlab.sharedPackages 对象使扩展作者能够使用三个布尔选项修改给定依赖项的默认去重策略。此对象的键是依赖项包名称,值是 false(表示不应共享/去重依赖项),或者最多包含三个字段的对象

  • bundled:如果为 true(默认值),则依赖项将与扩展捆绑在一起,并作为 JupyterLab 可用的副本之一提供。如果为 false,则依赖项不会与扩展捆绑在一起,因此扩展将使用来自另一个扩展的依赖项版本。

  • singleton:如果为 true,则扩展将始终优先使用其他扩展正在使用的依赖项副本,而不是使用可用的最高版本。默认值为 false

  • strictVersion:如果为 true,则扩展将始终确保它正在使用的依赖项副本满足它所需的依赖项版本范围。

默认情况下,JupyterLab 会将预构建扩展的直接依赖项与其他源扩展和预构建扩展的直接依赖项去重,选择 JupyterLab 可用的最高依赖项版本。JupyterLab 在使用来自核心 JupyterLab 包的令牌和服务时会选择合理的默认选项。我们建议在使用来自核心 JupyterLab 包以外的包提供的令牌时使用以下 sharedPackages 配置(有关使用令牌的更多详细信息,请参阅 插件相互交互)。

提供服务#

当扩展(“提供者”)提供由从依赖项 token-package 导入的令牌标识的服务时,提供者应将依赖项配置为单例。这确保了提供者使用与其他人导入的相同的令牌来标识服务。如果 token-package 不是核心包,它将与提供者捆绑在一起,如果消费者 需要该服务,则可供消费者导入。

"jupyterlab": {
  "sharedPackages": {
    "token-package": {
      "singleton": true
     }
   }
 }

需要服务#

当扩展(“消费者”)需要由另一个扩展(“提供者”)提供的服务时,该服务由从包(token-package,它可能与提供者相同)导入的令牌标识,消费者应将依赖项 token-package 配置为单例,以确保消费者获得与提供者用于标识服务的完全相同的令牌。此外,由于提供者正在提供 token-package 的副本,因此消费者可以将其从其捆绑包中排除。

"jupyterlab": {
  "sharedPackages": {
    "token-package": {
      "bundled": false,
      "singleton": true
     }
   }
 }

可选地使用服务#

当扩展(“消费者”)选择性地使用由从包(token-package)导入的令牌标识的服务时,无法保证提供者将可用且捆绑 token-package。在这种情况下,消费者应仅将 token-package 配置为单例。

"jupyterlab": {
  "sharedPackages": {
    "token-package": {
      "singleton": true
     }
   }
 }

配套包#

如果您的扩展依赖于内核中一个或多个包的存在,或者依赖于笔记本服务器扩展,您可以通过在您的 package.json 文件中添加元数据来向扩展管理器指示这一点。可用的完整选项是

"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"
      ]
    }
  }
}

例如,基于 jupyter-widget 的包的典型设置将是

"keywords": [
    "jupyterlab-extension",
    "jupyter",
    "widgets",
    "jupyterlab"
],
"jupyterlab": {
  "extension": true,
  "discovery": {
    "kernel": [
      {
        "kernel_spec": {
          "language": "^python",
        },
        "base": {
          "name": "myipywidgetspackage"
        },
        "managers": [
            "pip",
            "conda"
        ]
      }
    ]
  }
}

当前支持的包管理器是 pipconda

扩展 CSS#

如果您的扩展在 package.json 中具有顶级 style 键,则它指向的 CSS 文件将自动包含在页面中。

JupyterLab 中用于对页面上的 CSS 进行去重的约定是,如果您的扩展在 package.json 中具有顶级 styleModule 键,该键提供可以导入的 JavaScript 模块,则它将被导入(作为 JavaScript 模块),而不是将 style 键 CSS 文件作为 CSS 文件导入。

预构建扩展#

package.json 元数据#

除了 源扩展 的包元数据之外,预构建扩展还具有额外的 jupyterlab 元数据。

输出目录#

当 JupyterLab 构建预构建扩展时,它会创建一个文件目录,然后可以将其复制到 适当的安装位置jupyterlab.outputDir 字段给出这些 JavaScript 文件和其他文件应放置到的目录的相对路径。 package.json 文件的副本,其中包含额外的构建元数据,将被放置在 outputDir 中,将要提供的 JavaScript 文件和其他文件将被放置在 static 子目录中。

"jupyterlab": {
  "outputDir": "mypackage/labextension"
}

自定义 webpack 配置#

警告

此功能是实验性的,可能会在未经通知的情况下更改,因为它公开了内部实现细节(即 webpack)。在使用它时要小心,因为配置错误可能会破坏预构建扩展系统。

预构建扩展系统使用 Webpack 模块联合系统。通常,这是一个预构建扩展作者无需担心的实现细节,但有时扩展作者会希望调整用于构建其扩展的配置以启用各种 webpack 功能。扩展作者可以使用 package.json 中的 jupyterlab.webpackConfig 字段指定一个自定义 webpack 配置文件,该文件将与预构建扩展系统生成的 webpack 配置合并。该值应为配置文件的相对路径。

"jupyterlab": {
  "webpackConfig": "./webpack.config.js"
}

自定义 webpack 配置可用于启用 webpack 功能,配置其他文件加载器,以及许多其他用途。以下是一个 webpack.config.js 自定义配置的示例,它启用了 webpack 的异步 WebAssembly 和顶级 await 实验功能。

module.exports = {
  experiments: {
      topLevelAwait: true,
      asyncWebAssembly: true,
  }
};

在构建预构建扩展时,此自定义配置将与 预构建扩展配置 合并。

开发预构建扩展#

使用 jupyter labextension build 命令构建预构建扩展。此命令使用来自活动 JupyterLab 的依赖项元数据,从源扩展生成一组文件,这些文件构成预构建扩展。这些文件包括一个主入口点 remoteEntry.<hash>.js、捆绑到 JavaScript 文件中的依赖项、package.json(包含一些额外的构建元数据),以及插件设置和主题目录结构(如果需要)。

在编写预构建扩展时,可以使用 labextension develop 命令创建指向预构建输出目录的链接,类似于 pip install -e

jupyter labextension develop . --overwrite

然后重新构建扩展并在浏览器中刷新 JupyterLab 应该会拾取预构建扩展源代码中的更改。

如果使用 Windows,您可能需要为上面描述的 develop 命令配置您的操作系统才能正常工作,请参阅说明:Windows 用户重要说明

如果您正在针对 JupyterLab 源代码库开发预构建扩展,您可以使用 jupyter lab --dev-mode --extensions-in-dev-mode 运行 JupyterLab,以使开发版本的 JupyterLab 加载预构建扩展。最好记住,您的扩展依赖的 JupyterLab 包可能与已发布的包不同;这意味着您的扩展不会使用来自您 node_modules 文件夹的 JupyterLab 依赖项进行构建,而是使用 JupyterLab 源代码中的依赖项。

如果您使用的是 TypeScript,TypeScript 编译器会报错,因为您的扩展的依赖项可能与 JupyterLab 中的依赖项不同。为此,您需要在您的 tsconfig.json 中添加要搜索这些依赖项的路径,方法是添加选项 paths

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
        "@jupyterlab/*": ["../jupyterlab/packages/*"],
        "*": ["node_modules/*"]
    }
  },
}

添加查找 JupyterLab 依赖项的路径时,可能会导致项目中其他依赖项(如 lumino 或 react)出现问题,因为 JupyterLab 包将从 JupyterLab node_modules 文件夹获取其依赖项。相反,您的包将从您的 node_modules 文件夹获取依赖项。要解决此问题,您需要将有冲突的依赖项添加到您的 package.json 中的 resolutions 中。这样,两个项目(JupyterLab 和您的扩展)都使用相同版本的重复依赖项。

我们提供了一个 扩展模板,它处理扩展作者的所有脚手架,包括适当数据文件的交付,以便当用户安装包时,预构建扩展最终会出现在 share/jupyter/labextensions

分发预构建扩展#

预构建扩展可以通过任何可以将预构建资产复制到 JupyterLab 可以找到的适当位置的系统进行分发。 官方扩展模板 展示了如何通过 Python pip 或 conda 包分发预构建扩展。系统包管理器,甚至只是一个复制目录的管理脚本,也可以使用。

要分发预构建扩展,请将其 输出目录 复制到 JupyterLab 可以找到的位置,通常是 <sys-prefix>/share/jupyter/labextensions/<package-name>,其中 <package-name>package.json 中的 JavaScript 包名称。例如,如果您的 JavaScript 包名称是 @my-org/my-package,那么相应的目录应该是 <sys-prefix>/share/jupyter/labextensions/@my-org/my-package

JupyterLab 服务器通过 /labextensions/ 服务器处理程序提供 static/ 文件。服务器中的设置和主题处理程序也会从预构建的扩展目录加载设置和主题。如果预构建的扩展与源扩展具有相同的名称,则优先使用预构建的扩展。

打包信息#

由于预构建的扩展以多种方式分发(Python pip 包、conda 包以及可能在许多其他打包系统中),因此预构建的扩展目录可以包含一个额外的文件,install.json,它可以帮助用户了解预构建的扩展是如何安装的以及如何卸载它。此文件应由分发预构建扩展的打包系统复制到顶层目录,例如 <sys-prefix>/share/jupyter/labextensions/<package-name>/install.json

install.json 文件由 JupyterLab 用于帮助用户了解如何管理扩展。例如,jupyter labextension list 包含来自此文件的信息,而 jupyter labextension uninstall 可以打印有用的卸载说明。以下是一个示例 install.json 文件

{
  "packageManager": "python",
  "packageName": "mypackage",
  "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package mypackage"
}
  • packageManager:这是用于安装预构建扩展的包管理器,例如,pythonpipcondadebiansystem administrator 等。

  • packageName:这是上述包管理器中预构建扩展的包名,它可能与 package.json 中的包名不同。

  • uninstallInstructions:这是一个简短的文本块,为用户提供卸载预构建扩展的说明。例如,它可能会指示他们使用系统包管理器或与系统管理员联系。

PyPI Trove 分类器#

作为 Python 包分发的扩展可以在 trove 分类器 的形式中声明额外的元数据。这些可以改善用户在 PyPI 上的浏览体验。虽然包含许可证、开发状态、支持的 Python 版本和其他主题分类器对许多受众都有用,但以下分类器是特定于 Jupyter 和 JupyterLab 的。

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

在您的 setup.pysetup.cfgpyproject.toml 中包含每个相关的分类器(及其父级),以帮助在您的 setup.pysetup.cfgpyproject.toml 中描述您的包为潜在用户提供的功能。特别是 Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt 被扩展管理器用于从 PyPI.org 获取可用的扩展。

提示

例如,一个主题,仅与 JupyterLab 3 兼容,并作为一个现成的、预构建的扩展分发,可能看起来像这样

# 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",
    ]
)

这将可以通过例如 PyPI 对主题扩展的搜索 发现。

源扩展的开发工作流程#

开发预构建扩展 通常要容易得多,因为它们不需要重新构建 JupyterLab 才能看到更改。如果您需要开发一个源扩展,这里有一些关于开发工作流程的提示。

在编写源扩展时,您可以使用命令

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

这会导致构建器在构建应用程序文件之前重新安装源文件夹。您可以随时使用 jupyter lab build 重新构建,它将重新安装这些包。

您还可以使用 jupyter labextension link 链接您正在同时处理的其他本地 npm 包;它们将被重新安装,但不会被视为扩展。本地扩展和链接的包包含在 jupyter labextension list 中。

当使用本地扩展和链接的包时,您可以运行命令

jupyter lab --watch

当链接的包之一发生更改时,这将导致应用程序增量重建。请注意,WebPack 进程仅监视编译后的 JavaScript 文件(以及 CSS 文件)。这意味着如果您的扩展是用 TypeScript 编写的,您需要运行 jlpm run build 才能使更改反映在 JupyterLab 中。为了避免此步骤,您也可以监视扩展中的 TypeScript 源代码,这通常分配给 tsc -w 快捷方式。如果 webpack 似乎没有检测到更改,这可能与 可用监视器的数量 相关。

请注意,应用程序是针对 JupyterLab 扩展的已发布版本构建的。您应该使用 ^ 运算符指定版本,例如 ^4.0.0,以便构建系统可以使用具有特定主要版本的包的较新次要版本和修补版本。如果您的扩展依赖于 JupyterLab 包,它应该与 jupyterlab/static/package.json 文件中的依赖项兼容。请注意,构建将始终使用满足 JupyterLab 本身以及任何已安装扩展的依赖项要求的最新 JavaScript 包。如果您希望针对某个核心 JupyterLab 包的特定修补版本进行测试,您可以在自己的依赖项中暂时将该要求固定到特定版本。

如果您想针对 JupyterLab 的未发布版本测试源扩展,您可以运行以下命令:

jupyter lab --watch --splice-source

此命令将本地 packages 目录拼接进应用程序目录,允许您针对当前开发源代码构建源扩展。要静态构建拼接的源代码,请使用 jupyter lab build --splice-source。创建拼接构建后,任何后续对 jupyter labextension build 的调用默认情况下将处于拼接模式。可以通过调用 jupyter labextension build --splice-source 强制执行拼接构建。请注意,开发预构建扩展 针对 JupyterLab 的开发版本通常比源包构建更容易。

该包应该导出与 EMCAScript 6 兼容的 JavaScript。它可以使用语法 require('foo.css') 导入 CSS。CSS 文件也可以使用语法 @import url('~foo/index.css') 从其他包中导入 CSS,其中 foo 是包的名称。

以下文件类型也受支持(在 JavaScript 和 CSS 中):jsonhtmljpgpnggifsvgjs.mapwoff2ttfeot

如果您的包使用任何其他文件类型,则必须将其转换为上述类型之一,或者 在导入语句中包含加载器。如果您包含加载器,则加载器必须在构建时可导入,因此如果 JupyterLab 尚未安装它,则必须将其添加为扩展的依赖项。

如果您的 JavaScript 使用除 EMCAScript 6 (2015) 之外的任何其他方言编写,则应使用适当的工具进行转换。您可以使用 Webpack 预构建您的扩展以使用其在我们的构建配置中未启用的任何功能。要构建兼容的包,请在您的 Webpack 配置中将 output.libraryTarget 设置为 "commonjs2"。(参见 示例仓库)。

如果您在 npm.org 上发布您的扩展,用户将能够像这样简单地安装它:jupyter labextension install <foo>,其中 <foo> 是已发布的 npm 包的名称。您也可以提供一个脚本,该脚本在用户机器上的本地文件夹路径或提供的压缩包上运行 jupyter labextension install。任何有效的 npm install 指定符都可以在 jupyter labextension install 中使用(例如 foo@latest[email protected]path/to/folderpath/to/tar.gz)。

我们鼓励扩展作者将 jupyterlab-extension GitHub 主题 添加到任何 GitHub 扩展存储库中。

测试您的扩展#

注意

我们强烈建议使用 扩展模板 来设置测试配置。

此存储库中的 testutils 中有许多辅助函数(这是一个名为 @jupyterlab/testutils 的公共 npm 包),这些函数可以在编写扩展测试时使用。请参阅 tests/test-application 以了解运行测试所需的架构示例。

如果您使用 jest 来测试您的扩展,您将需要将 jupyterlab 包转译为 commonjs,因为它们使用的是 node 不支持的 ES6 模块。

要转译 jupyterlab 包,您需要安装以下包

jlpm add --dev jest @types/jest ts-jest @babel/core@^7 @babel/preset-env@^7

然后在 jest.config.js 中,您将指定使用 babel 处理 js 文件并忽略所有节点模块,除了 ES6 模块

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}).+`
  ]
};

最后,您需要使用包含以下内容的 babel.config.js 文件配置 babel

module.exports = require('@jupyterlab/testutils/lib/babel.config');