Rendering custom widgets: `Bokeh.register_plugin is not a function`

Hi everyone,

I am in the process of upgrading an application from Bokeh 0.12 (!) to 2.2.3.

I have defined a custom widget, which compiles correctly. On the backend, I generate a Bokeh document and export it in two parts: as JSON (using bokeh_embed.json_item) and JavaScript (for our custom models, using bundle_models). On the frontend, I then do the following:

import * as Bokeh from "@bokeh/bokehjs";

const { bokehJSON, modelJS } = plotData;

window.Bokeh = Bokeh;
const root = { Bokeh: window.Bokeh };
eval(modelJS.replace("this", "root"));

Bokeh.embed.embed_item(bokehJSON, `bokeh-${bokehJSON.root_id}`);

(The bokeh-xyz div is created elsewhere in the code.)

A similar scheme worked under 0.12, but now I see the following error:

Bokeh.register_plugin is not a function

I am curious whether:

  1. I am trying to do something I am not supposed to (export JSON + JS and render on frontend);
  2. importing Bokeh the way I am is supported; and
  3. if so, whether exposing register_plugin would be an option?

Thank you for your advice!

@Philipp_Rudiger how does hv/panel register or inject its extensions? also cc @mateusz

Looking at the Panel source code, it looks like they use:

import * as Panel from "./models"
export {Panel}

import {register_models} from "@bokehjs/base"
register_models(Panel as any)

I don’t have my models defined externally, although that is a route I can investigate. It would be useful to be able to register models written in Python in __implementation__ clauses, though.

import * as Bokeh from "@bokeh/bokehjs";

@bokeh/bokehjs is a non-module JS bundle (it has no exports), so Bokeh will be {} (give or take; assuming webpack is being used to package things, there may be some webpack’s cruft there). However, window.Bokeh (well, global Bokeh) should be set up properly nevertheless. So, this:

window.Bokeh = Bokeh;

is definitively not necessary.

const root = { Bokeh: window.Bokeh };
eval(modelJS.replace("this", "root"));

If bokeh generated modelJS and you are issuing Bokeh.embed.embed_item(), then you are doing the work twice, because bokeh’s generated JS boils down to calling embed_item().

@mateusz Thank you for your response.

I am confused, though: as I wrote, I use bokeh_embed.json_item and bundle_models, e.g.:

const modelJSON = bokeh_embed.json_item(tabs);
const modelJS = bundle_models([CheckboxWithLegendGroup]);

I don’t see how to skip one of the above and how to only apply the other. If I try not to call modelJS, then it complains that my custom model (CheckboxWithLegendGroup) has not been registered.

(…) and bundle_models

I didn’t register that part. So, this should work:

import "@bokeh/bokehjs";

const { bokehJSON, modelJS } = plotData;
eval(modelJS);
Bokeh.embed.embed_item(bokehJSON, `bokeh-${bokehJSON.root_id}`)

Essentially you’re replacing a <script> tag with an import, but the rest of the code is the same in both cases. Not ideal, but I don’t think we can get anything better right now (at least not without improving bokeh and/or bokehjs).

Actually that’s not going to work because we define the scope of a model bundle as this, which will be bound to local module scope in this case, instead of the global scope. Try this instead:

import "@bokeh/bokehjs";

const { bokehJSON, modelJS } = plotData;
Function(modelJS).bind(window)();
Bokeh.embed.embed_item(bokehJSON, `bokeh-${bokehJSON.root_id}`)

Thanks, @mateusz

In this configuration, I will have to include Bokeh via a script tag, I presume? Otherwise, I see:

Uncaught Error: Cannot find Bokeh. You have to load it prior to loading plugins.

For completeness, what I see on the import * as Bokeh from "@bokeh/bokehjs"; statement is everything from index.js:

export { version } from "./version";
export { index } from "./embed";
export * as embed from "./embed";
export * as protocol from "./protocol";
export * as _testing from "./testing";
export { logger, set_log_level } from "./core/logging";
export { settings } from "./core/settings";
export { Models } from "./base";
export { documents } from "./document";
export { safely } from "./safely";

But register_plugins is not included there.

I managed to solve my problem as follows:

  1. Moved custom models into a separate file, BokehModels.js. Since I am not using TypeScript, I had to translate my custom model from TypeScript to JavaScript.
  2. Inside my JavaScript plotting component, I do:
import * as Bokeh from "@bokeh/bokehjs";
import * as Models from "./BokehModels";  // my custom models

// `plotData` is shipped to the frontend by my Python backend
// which generates it with `bokeh_embed.json_item(layout)`
const { bokehJSON } = plotData;

Bokeh.Models.register_models(Models);
window.Bokeh = Bokeh;
Bokeh.embed.embed_item(bokehJSON, `bokeh-${bokehJSON.root_id}`);

The full set of changes can be seen in my pull request to SkyPortal.

Thank you to the Panel project, whose source code provided me with a route to the solution.