Migrating custom widget libraries#
These are migration guides specifically for developers of third-party widgets.
Migrating from 7.x to 8.0#
In this section, we discuss migrating a custom widget from ipywidgets 7 to ipywidgets 8 or supporting both ipywidgets 7 and ipywidgets 8 with the same codebase.
For a summarized list of changes affecting custom widget authors, please see the “Developers” section of the changelog for 8.0.
Please consider updating your widget by generating a new widget from the JavaScript widget cookiecutter and adapting the code to your widget, since the cookiecutter has been updated to use best practices in Python packaging and Jupyter Widget infrastructure.
For example migrations, see these PRs:
Updating setup.py#
Start by updating the dependency in your setup.py
or setup.cfg
to support 8.x.
e.g.
install_requires=[
- 'ipywidgets>=7,<8',
+ 'ipywidgets>=7,<9',
],
Updating package.json#
Next, you should update the JavaScript dependencies. You will need to update
your @jupyter-widgets/base
dependency and the @jupyter-widgets/controls
if
you depend on it.
The diff will look like the following in case you still want to support ipywidgets<8:
- "@jupyter-widgets/base": "^2 || ^3 || ^4",
+ "@jupyter-widgets/base": "^2 || ^3 || ^4 || ^5 || ^6",
You can also apply the following diff if you only want to support ipywidgets==8 from now on:
- "@jupyter-widgets/base": "^2 || ^3 || ^4",
+ "@jupyter-widgets/base": "^6",
Note that “@jupyter-widgets/base” version 5 is reserved for ipywidgets 7 support on JupyterLab 4, “@jupyter-widgets/base” version 6 is the version released with ipywidgets 8.
The ManagerBase
class has been split into an interface type IWidgetManager
which remains in the @jupyter-widgets/base
package, and its implementation which has moved to the new @jupyter-widgets/base-manager
package. So if you subclass the ManagerBase
class, you will need to add a new dependency in your package.json
as following, and update your imports accordingly.
+ "@jupyter-widgets/base-manager": "^1",
Updating the webpack publicPath
configuration#
We highly encourage you to update your widget’s webpack configuration for publicPath
, which is used in generating AMD modules, with changes similar to these changes. These changes allow your AMD module to be hosted anywhere, rather than hardcoding the a particular CDN like unpkg.com
, and they simplify things by removing the differences between the AMD module generated for the notebook extension and the AMD module generated for the CDN.
Updating the browser code#
Phosphor -> Lumino#
The Phosphor library has been archived. It has been forked and renamed Lumino, and the maintenance is now done under the JupyterLab governance.
If you used to import classes like JupyterPhosphorPanelWidget
and JupyterPhosphorWidget
from the @jupyter-widgets/base
library, you will need to update them:
- import { JupyterPhosphorPanelWidget, JupyterPhosphorWidget } from '@jupyter-widgets/base';
+ import { JupyterLuminoPanelWidget, JupyterLuminoWidget } from '@jupyter-widgets/base';
The DOMWidgetView.pWidget
property has been renamed DOMWidgetView.luminoWidget
(though an alias for pWidget
is available for conveniance):
- this.pWidget
+ this.luminoWidget
The DOMWidgetView.processPhosphorMessage
method has been renamed DOMWidgetView.processLuminoMessage
. If you want to support both ipywidgets 7.x and 8.x, you should implement both methods and call the correct super method:
- 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);
+ }
If you’re dropping ipywidgets 7.x support, you can simply rename the processPhosphorMessage
method to processLuminoMessage
.
Widget manager import#
As mentioned before, if you depend on the ManagerBase
class, you will either need to update the import:
- import { ManagerBase } from '@jupyter-widgets/base';
+ import { ManagerBase } from '@jupyter-widgets/base-manager';
or, switch to using the new IWidgetManager
interface in the base
package:
- import { ManagerBase } from '@jupyter-widgets/base';
+ import { IWidgetManager } from '@jupyter-widgets/base';
Which one to pick depends on how you use it. If you are using it as the base class for your own implementation of a widget manager, and want to subclass it in order to reuse the methods/logic in that implementation, you should depend on the base-manager
package. If you are only interested in the TypeScript type for a widget manager, e.g. for use in the arguments of a deserializer function, you should use the IWidgetManager
interface type.
Typescript trick: If you need to support a deserializer function against both ipywidgets 7 and older and the new version 8, you can change your deserializer function to have the following signature:
- 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 extend#
The version of Backbone.js that ipywidgets depends on has changed from 1.2.3 to 1.4.0. If you were extending the base widget model with var CustomWidgetModel = Widget.extend({ ... });
you will need to update the class definition using the ES6 notation:
- var CustomWidgetModel = Widget.extend({
- ...
- });
+ class CustomWidgetModel extends Widget {
+ ...
+ }
If you were using .extend()
, you will also need to change how your model attribute defaults are defined. The model defaults are now given by a function that returns the defaults and includes the superclass defaults. For example, the Output widget model 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,
};
}
}
Custom tag names#
If you were changing the base HTML tag for your widget by defining the tagName
property, this can now be done in ipywidgets 8 in the preinitialize
method (see here for example changes in core widgets):
- get tagName() {
- return 'button';
- }
+ preinitialize() {
+ this.tagName = 'button';
+ }
If you need compatibility with ipywidgets 7, continue using the get tagName
accessor instead of preinitialize
. However, newer versions of Typescript will complain that you are overriding a property with a function. If you want to maintain compatibility with both ipywidgets 7 and ipywidgets 8, and you are using Typescript, you can add a ts-ignore
directive to mollify Typescript, like is done in ipydatawidgets:
+ // @ts-ignore: 2611
get tagName() {
return 'button';
}
Migrating from 6.0 to 7.0#
For example migrations, see these PRs:
To avoid tying your development cycle to ipywidgets, we recommend starting the migration on a branch and keeping that branch open until ipywidgets 7.0 is released.
We also recommend testing the migration in a completely new notebook, rather than one that contains widgets that you instantiated with ipywidgets 6.0.
Updating setup.py#
Start by updating the dependency in your setup.py
to the latest release. To
find the correct version number, go to the releases
page on Github and
cycle through the tags until you see the latest 7.0.0 tag.
Updating package.json#
Next, we should update the JavaScript dependencies. The most important change
for widget developers is that the JavaScript package for jupyter-widgets has
been split between @jupyter-widgets/base
and @jupyter-widgets/controls
:
@jupyter-widgets/base
contains the base widget classes and the layout classes@jupyter-widgets/controls
contains the widget classes for the user-facing widgets.
In your package.json
, remove jupyter-js-widgets
from your dependencies
and add @jupyter-widgets/base
. To find the correct version, go to the
releases page and
cycle through the tags until you see a tag called
@jupyter-widgets/base@<version>
. Do the same for
@jupyter-widgets/controls
if you think you have a dependency on it (e.g. if
you create widgets that inherit from VBox
or HBox
or another user-facing widget).
Updating Webpack configuration#
If you use Webpack to build the client-side library, your Webpack
configuration file (probably at js/webpack.config.json
) declares
jupyter-js-widgets
as an external dependency. You will need to change this
in both the bundle for the notebook and the embeddable bundle. If you just
need @jupyter-widgets/base
, your external dependencies would be:
externals: ['@jupyter-widgets/base']
If you need both @jupyter-widgets/base
and @jupyter-widgets/controls
, include
both packages in the array.
The cookiecutter template provides a sample configuration.
Updating the client-side code#
If you now build the client-side code of your library, you will get many
errors about missing jupyter-js-widgets
dependencies. You need to replace
every import of jupyter-js-widgets
with an import of
@jupyter-widgets/base
(or, possibly, an import of @jupyter-widgets/controls
).
Your imports should now look like one of the following (depending on how you normally import other modules):
widgets = require('@jupyter-widgets/base');
require(['@jupyter-widgets/base'], function (widgets) {});
import * as widgets from '@jupyter-widgets/base';
All your widget models should also declare the attributes
_view_module_version
and _model_module_version
. A minimal model now looks like:
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',
}),
});
For embedding to work correctly, the module version needs to be a semantic
version range
that matches a release on NPM. The most common pattern is to request a
version compatible with the version currently in your package.json
by using,
~{{ version number }}
for _model_module_version
and _view_module_version
. See the cookiecutter
template
for details.
Since you probably want to avoid repeating the module version in every
widget, a common pattern is to import the version from package.json
and
prepend a ~
. See
ipyvolume
for an example. If you do this, make sure that your webpack configuration
includes a JSON loader. See the Webpack configuration for
ipyvolume
for an example.
Updating the notebook extension#
Previously, the notebook extension (normally js/src/extension.js
) required
defining jupyter-js-widgets
in the configuration for requirejs
. This is
no longer needed. See the cookiecutter
template
for an example of the correct requirejs
configuration.
Updating the Python code#
All widgets need to declare the following six traitlets:
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')
It is likely that your widgets already declared a _model_name
,
_view_name
, _model_module
and _view_module
. The _model_module
and
_view_module
should be the name of your package on NPM (the value of the
name
field in your package.json
). The _model_module_version
and
_view_module_version
should be the version of your JavaScript client (the
values of the version
field in your package.json
).
The _model_module_version
and _view_module_version
are used to find your
JavaScript bundle when embedding widgets. The embed manager will look for the
bundle at https://cdn.jsdelivr.net/npm/<module-name>@<module-version>/dist/index.js
when it finds a widget.
Updating embedded widgets#
There are now two options for embedding widgets in an HTML page outside of the notebook.
Embedding the standard widgets#
If you are just embedding the standard widgets that come with ipywidgets, then you can simply include the following script tag:
<script
src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@*/dist/embed.js"
crossorigin="anonymous"
></script>
If you want to use a specific version of the embedder, you replace the @*
with a semver range, such as @^0.9.0
Embedding custom widgets with RequireJS#
In order to embed third-party widgets, you can use the RequireJS-based embedding. First, make sure that RequireJS is loaded on the page, for example:
<!-- 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>
Then include the following script, which defines the embedding libraries and runs the function to render widgets:
<script
src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@*/dist/embed-amd.js"
crossorigin="anonymous"
></script>
If you want to use a specific version of the embedder, you replace the @*
with a semver range, such as @^0.9.0
If you need to embed custom widgets without using RequireJS, you’ll need to compile your own embedding javascript that includes the third-party libraries.