迁移自定义小部件库#

这些迁移指南专门针对第三方小部件的开发者。

从版本7.x迁移到8.0#

在本节中,我们讨论如何将自定义小部件从ipywidgets 7迁移到 ipywidgets 8,或使用相同代码库同时支持ipywidgets 7和ipywidgets 8。

有关影响自定义小部件作者的变更摘要列表,请参阅8.0版本更新日志中的“开发者”部分。

请考虑通过从JavaScript widget cookiecutter生成一个新的widget并调整代码到您的widget来更新您的widget,因为cookiecutter已更新以使用Python打包和Jupyter Widget基础设施的最佳实践。

例如迁移,请查看这些PR:

更新 setup.py#

首先在你的 setup.pysetup.cfg 中更新依赖以支持 8.x 版本。

例如

 install_requires=[
-    'ipywidgets>=7,<8',
+    'ipywidgets>=7,<9',
 ],

更新 package.json#

接下来,你应该更新JavaScript依赖项。你需要更新你的@jupyter-widgets/base依赖项和@jupyter-widgets/controls如果你依赖它的话)。

如果你想继续支持 ipywidgets<8,差异将如下所示:

- "@jupyter-widgets/base": "^2 || ^3 || ^4",
+ "@jupyter-widgets/base": "^2 || ^3 || ^4 || ^5 || ^6",

如果您从现在开始只想支持 ipywidgets==8,也可以应用以下差异:

- "@jupyter-widgets/base": "^2 || ^3 || ^4",
+ "@jupyter-widgets/base": "^6",

注意,“@jupyter-widgets/base”版本5是为JupyterLab 4上的ipywidgets 7支持预留的,“@jupyter-widgets/base”版本6是随ipywidgets 8发布的版本。

ManagerBase 类已被拆分为一个接口类型 IWidgetManager,该接口仍保留在 @jupyter-widgets/base 包中,而其实现在则移至新的 @jupyter-widgets/base-manager 包中。因此,如果你要继承 ManagerBase 类,你需要在你的 package.json 中添加如下新的依赖项,并相应地更新你的导入语句。

+ "@jupyter-widgets/base-manager": "^1",

更新webpack publicPath配置#

我们强烈建议您更新您的widget的webpack配置中的publicPath,它用于生成AMD模块,采用类似于这些更改的变更。这些更改允许您的AMD模块托管在任何地方,而不是硬编码某个特定的CDN,如unpkg.com,并且通过消除为notebook扩展生成的AMD模块和为CDN生成的AMD模块之间的差异来简化事情。

更新浏览器代码#

Phosphor -> Lumino#

Phosphor库已被归档。它已被分叉并重命名为Lumino,维护工作现由JupyterLab治理下进行。

如果您曾经从@jupyter-widgets/base库中导入类,如JupyterPhosphorPanelWidgetJupyterPhosphorWidget,您将需要更新它们:

- import { JupyterPhosphorPanelWidget, JupyterPhosphorWidget } from '@jupyter-widgets/base';
+ import { JupyterLuminoPanelWidget, JupyterLuminoWidget } from '@jupyter-widgets/base';

DOMWidgetView.pWidget 属性已重命名为 DOMWidgetView.luminoWidget(虽然为方便起见,仍提供 pWidget 的别名):

- this.pWidget
+ this.luminoWidget

DOMWidgetView.processPhosphorMessage 方法已重命名为 DOMWidgetView.processLuminoMessage。如果您想同时支持 ipywidgets 7.x 和 8.x,您应该同时实现这两个方法并调用正确的父类方法:

- processPhosphorMessage(msg: Message): void {
-     super.processPhosphorMessage(msg);
-     switch (msg.type) {
-     case 'resize':
-         this.resize();
-         break;
-     }
- }
+ _processLuminoMessage(msg: Message, _super: (msg: Message) => void): void {
+     _super.call(this, msg);
+     switch (msg.type) {
+     case 'resize':
+         this.resize();
+         break;
+     }
+ }
+
+ processPhosphorMessage(msg: Message): void {
+     this._processLuminoMessage(msg, super.processPhosphorMessage);
+ }
+
+ processLuminoMessage(msg: Message): void {
+     this._processLuminoMessage(msg, super.processLuminoMessage);
+ }

如果你放弃对ipywidgets 7.x的支持,可以简单地将processPhosphorMessage方法重命名为processLuminoMessage

小组件管理器导入#

如前所述,如果你依赖ManagerBase类,你将需要更新导入:

- import { ManagerBase } from '@jupyter-widgets/base';
+ import { ManagerBase } from '@jupyter-widgets/base-manager';

或者,切换到在 base 包中使用新的 IWidgetManager 接口:

- import { ManagerBase } from '@jupyter-widgets/base';
+ import { IWidgetManager } from '@jupyter-widgets/base';

选择哪一个取决于您如何使用它。如果您将其作为您自己实现的小部件管理器的基础类,并希望子类化它以重用该实现中的方法/逻辑,您应该依赖base-manager包。如果您只对小部件管理器的TypeScript类型感兴趣,例如用于反序列化器函数的参数中,您应该使用IWidgetManager接口类型。

Typescript 技巧: 如果您需要同时支持 ipywidgets 7 及更旧版本和新版本 8 的反序列化器函数,可以将反序列化器函数改为以下签名:

- import { ManagerBase } from '@jupyter-widgets/base';
+ import { unpack_models } from '@jupyter-widgets/base';

export async function myDeserializer(
  obj: MyObjectType,
-  manager?: ManagerBase
+  manager?: Parameters<typeof unpack_models>[1]
): Promise<JSONValue> {

Backbone 扩展#

ipywidgets所依赖的Backbone.js版本已从1.2.3更改为1.4.0。如果您之前使用var CustomWidgetModel = Widget.extend({ ... });扩展基础控件模型,现在需要使用ES6语法更新类定义:

- var CustomWidgetModel = Widget.extend({
-     ...
- });
+ class CustomWidgetModel extends Widget {
+     ...
+ }

如果你之前使用.extend(),你还需要更改模型属性默认值的定义方式。模型默认值现在由一个返回默认值并包含父类默认值的函数提供。例如,Output小部件模型looks like this

export const OUTPUT_WIDGET_VERSION = '1.0.0';

export class OutputModel extends DOMWidgetModel {
  defaults() {
    return {
      ...super.defaults(),
      _model_name: 'OutputModel',
      _view_name: 'OutputView',
      _model_module: '@jupyter-widgets/output',
      _view_module: '@jupyter-widgets/output',
      _model_module_version: OUTPUT_WIDGET_VERSION,
      _view_module_version: OUTPUT_WIDGET_VERSION,
    };
  }
}

自定义标签名称#

如果您通过定义tagName属性来更改小部件的基HTML标签,现在可以在ipywidgets 8的preinitialize方法中完成(例如,请参阅这里查看核心小部件的变更示例):

- get tagName() {
-   return 'button';
- }
+ preinitialize() {
+   this.tagName = 'button';
+ }

如果你需要与ipywidgets 7兼容,请继续使用get tagName访问器而非preinitialize。然而,新版本的Typescript会警告你正在用一个函数覆盖属性。如果你想同时保持与ipywidgets 7和ipywidgets 8的兼容性,并且正在使用Typescript,你可以添加一个ts-ignore指令来安抚Typescript,就像在ipydatawidgets中做的那样:

+ // @ts-ignore: 2611
  get tagName() {
    return 'button';
  }

从 6.0 迁移到 7.0#

例如迁移,请查看这些PR:

为了避免将您的开发周期与ipywidgets绑定,我们建议在一个分支上开始迁移,并保持该分支开放,直到ipywidgets 7.0发布。

我们还建议在一个全新的notebook中测试迁移,而不是在包含使用ipywidgets 6.0实例化的小部件的notebook中。

更新 setup.py#

首先在您的setup.py中将依赖项更新至最新版本。要 找到正确的版本号,请前往 Github 上的发布页面并 浏览标签,直到看到最新的 7.0.0 标签。

更新 package.json#

接下来,我们应该更新 JavaScript 依赖项。对于 widget 开发者来说,最重要的变化是 jupyter-widgets 的 JavaScript 包已经被拆分到 @jupyter-widgets/base@jupyter-widgets/controls

  • @jupyter-widgets/base 包含基础控件类和布局类

  • @jupyter-widgets/controls 包含面向用户的小部件类。

在你的package.json中,从依赖项中移除jupyter-js-widgets 并添加@jupyter-widgets/base。要找到正确的版本,请前往 发布页面并 循环查看标签,直到看到名为 @jupyter-widgets/base@<version>的标签。如果你认为你依赖它(例如,如果你 创建继承自VBoxHBox或其他面向用户的小部件的控件),则对 @jupyter-widgets/controls执行相同的操作。

更新Webpack配置#

如果您使用Webpack来构建客户端库,您的Webpack 配置文件(可能位于js/webpack.config.json)声明了 jupyter-js-widgets作为外部依赖项。您需要在notebook的捆绑包和 可嵌入的捆绑包中都更改此项。如果您只需要@jupyter-widgets/base,您的外部依赖项应为:

externals: ['@jupyter-widgets/base']

如果你同时需要 @jupyter-widgets/base@jupyter-widgets/controls,请在数组中包含这两个包。

cookiecutter 模板提供了一个示例配置。

更新客户端代码#

如果你现在构建库的客户端代码,将会收到许多关于缺少 jupyter-js-widgets 依赖项的错误。你需要将每个 jupyter-js-widgets 的导入替换为 @jupyter-widgets/base 的导入(或者可能是 @jupyter-widgets/controls 的导入)。

您的导入语句现在应该类似于以下其中一种(取决于您通常如何导入其他模块):

widgets = require('@jupyter-widgets/base');
require(['@jupyter-widgets/base'], function (widgets) {});
import * as widgets from '@jupyter-widgets/base';

所有的窗口小部件模型也应该声明属性 _view_module_version_model_module_version。一个最小模型现在看起来是这样的:

var HelloModel = widgets.DOMWidgetModel.extend({
  defaults: _.extend(widgets.DOMWidgetModel.prototype.defaults(), {
    _model_name: 'HelloModel',
    _view_name: 'HelloView',
    _model_module: 'example_module',
    _view_module: 'example_module',
    _model_module_version: '~1.0.0',
    _view_module_version: '~1.0.0',
  }),
});

要使嵌入功能正常工作,模块版本需要是语义版本范围 ,与NPM上的发布版本匹配。最常见的方式是通过使用 ~{{ version number }}来请求与您当前package.json中版本兼容的版本 ,用于_model_module_version_view_module_version。详情请参阅cookiecutter 模板

由于您可能希望避免在每个组件中重复模块版本,常见的做法是从package.json导入版本并 添加一个~前缀。请参阅 ipyvolume 作为示例。如果您这样做,请确保您的webpack配置 包含了JSON加载器。请参阅ipyvolume的 Webpack配置作为示例。

更新笔记本扩展#

之前,笔记本扩展(通常是 js/src/extension.js)需要在 requirejs 的配置中定义 jupyter-js-widgets。这 不再需要。请查看 cookiecutter 模板 以获取正确 requirejs 配置的示例。

更新Python代码#

所有小部件需要声明以下六个特征:

class ExampleWidget(widgets.Widget):
    _model_name = Unicode('name of model in JS')
    _view_name = Unicode('name of view in JS')
    _model_module = Unicode('name your JS package')
    _view_module = Unicode('name your JS package')
    _model_module_version = Unicode('version of your JS bundle')
    _view_module_version = Unicode('version of your JS bundle')

您的部件可能已经声明了 _model_name_view_name_model_module_view_module_model_module_view_module 应该是您在 NPM 上的包名称(即 package.jsonname 字段的值)。_model_module_version_view_module_version 应该是您的 JavaScript 客户端的版本(即 package.jsonversion 字段的值)。

_model_module_version_view_module_version 用于在嵌入部件时查找您的 JavaScript 包。嵌入管理器将在找到部件时寻找位于 https://cdn.jsdelivr.net/npm/<module-name>@<module-version>/dist/index.js 的包。

更新嵌入式组件#

现在有两种选项可将小部件嵌入到笔记本之外的HTML页面中。

嵌入标准组件#

如果您只是嵌入ipywidgets附带的默认小部件,那么您只需包含以下脚本标签:

<script
  src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@*/dist/embed.js"
  crossorigin="anonymous"
></script>

如果你想使用特定版本的embedder,你可以将@*替换为一个semver范围,例如@^0.9.0

使用RequireJS嵌入自定义小部件#

为了嵌入第三方小部件,您可以使用基于RequireJS的嵌入方式。首先,确保RequireJS已在页面上加载,例如:

<!-- Load require.js. Delete this if your page already loads require.js -->
<script
  src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"
  integrity="sha256-Ae2Vz/4ePdIu6ZyI/5ZGsYnb+m0JlOmKPjt6XZ9JJkA="
  crossorigin="anonymous"
></script>

然后包含以下脚本,它定义了嵌入库并运行渲染小部件的函数:

<script
  src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@*/dist/embed-amd.js"
  crossorigin="anonymous"
></script>

如果你想使用特定版本的embedder,你可以将@*替换为一个semver范围,例如@^0.9.0

如果你需要在不使用RequireJS的情况下嵌入自定义部件,你需要编译包含第三方库的自定义嵌入javascript。