Prebuilt Bokeh Extensions

In this document I will describe how I got prebuilt bokeh model extensions setup as a part of the awesome-panel package. I needed it temporarily while waiting for the WebComponent PR to be reviewed and released by Panel.

Setting up prebuilt extensions using Bokeh init --interactive is briefly described in the Bokeh Docs. See Bokeh Pre-built extensions.

I hope this description can help others who would like to create prebuilt custom bokeh models for Bokeh or Panel.

Steps

I navigated to the root of the awesome-panel package

cd awesome-panel/package

ran bokeh init --interactive

$ bokeh init --interactive
Working directory: C:\repos\private\awesome-panel\package\awesome_panel
Wrote C:\repos\private\awesome-panel\package\awesome_panel\bokeh.ext.json
Create package.json? This will allow you to specify external dependencies. [y/n] y
  What's the extension's name? [awesome_panel]
  What's the extension's version? [0.0.1]
  What's the extension's description? []
Wrote C:\repos\private\awesome-panel\package\awesome_panel\package.json
Create tsconfig.json? This will allow for customized configuration and improved IDE experience. [y/n] y
Wrote C:\repos\private\awesome-panel\package\awesome_panel\tsconfig.json
Created empty index.ts. This is the entry point of your extension.
You can build your extension with bokeh build
All done.

In the package.json I had to replace

"dependencies": {
    "bokehjs": "^2.0.2"
  },

with

"dependencies": {
    "@bokeh/bokehjs": "^2.0.2"
  },

See bokeh init issue.

I also replaced the tsconfig.json contents with

{
  "compilerOptions": {
    "noImplicitAny": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "strictNullChecks": true,
    "strictBindCallApply": false,
    "strictFunctionTypes": false,
    "strictPropertyInitialization": false,
    "alwaysStrict": true,
    "noErrorTruncation": true,
    "noEmitOnError": false,
    "declaration": true,
    "sourceMap": true,
    "importHelpers": false,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "target": "ES2017",
    "lib": ["es2017", "dom", "dom.iterable"],
    "baseUrl": ".",
    "outDir": "./dist/lib",
    "paths": {
      "@bokehjs/*": [
        "./node_modules/@bokeh/bokehjs/build/js/lib/*",
        "./node_modules/@bokeh/bokehjs/build/js/types/*"
      ]
    }
  },
  "include": ["./**/*.ts"]
}

At least including the path section is needed to be able to import { div, label } from "@bokehjs/core/dom" like @philippjfr does in Panel.

In the index.ts file I imported my models

import * as AwesomePanel from "./express/models/"
export {AwesomePanel}

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

In the express/models/index.ts file I exported the WebComponent.

export {WebComponent} from "./web_component"

Then I could build my extension

$ panel build
Working directory: C:\repos\private\awesome-panel\package\awesome_panel
Using C:\repos\private\awesome-panel\package\awesome_panel\tsconfig.json
Compiling TypeScript (3 files)
Linking modules
Output written to C:\repos\private\awesome-panel\package\awesome_panel\dist
All done.

The result is in the dist folder.

I discovered I did not even have to serve the awesome_panel.js file.

I could panel serve something like

import param
import panel as pn
from awesome_panel.express.pane.web_component import WebComponent

MWC_ICONS = [
    None,
    "accessibility",
    "code",
    "favorite",
]  # For more icons see https://material.io/resources/icons/?style=baseline

MATERIAL = "https://cdn.jsdelivr.net/gh/marcskovmadsen/awesome-panel@be59521090b7c9d9ba5eb16e936034e412e2c86b/assets/js/mwc.bundled.js"
pn.config.js_files["material"]=MATERIAL
font_pane = pn.pane.HTML(
    """
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
    """, width=0, height=0, margin=0,
)

class MWCButton(WebComponent):
    html = param.String("<mwc-button></mwc-button")
    attributes_to_watch = param.Dict({"label": "name", "icon": "icon", "raised": "raised"})
    events_to_watch = param.Dict({"click": "clicks"})

    raised = param.Boolean(default=True)
    icon = param.ObjectSelector(default="favorite", objects=MWC_ICONS, allow_None=True)
    clicks = param.Integer()

    height = param.Integer(default=30)

button = MWCButton(name="Thanks Bokeh and Panel")

pn.Column(font_pane, button, pn.Param(button.param.clicks)).servable()

and get

_images/custom_bokeh_model.pngCustom Bokeh Model