Use pre-compiled Custom-Widget with (external) resources offline

Hi there,

I created a custom widget and am using a few external CSS and JS files for it, but I want to use it offline.

  1. I realized the compilation of the widget takes some time everytime I start the application , is there some way to use a pre-compiled version of the widget-resources?

  2. I downloaded the external Resources, is there a bokeh-way of making __js__ and __css__ read from a file instead of a CDN?

Thank you

@mateusz or @Philipp_Rudiger can you comment on this?

hey @kaprisonne you can download the files and serve up locally. just grab the css/js from the cdn and embed them locally. by the way we had the same issue and had a work around to prevent Bokeh from trying to compile the components i put our snippet here:

i think it’s the same problem you are facing. this way we managed to delete node from the server and cut that timely step of it compiling things while you build the server up.


Compiled components : Final step - help needed - removing nodejs from the build process

That looks promising, I’m gonna try it out once I got the more important stuff work properly.

you can download the files and serve up locally

That’s what I was trying to achieve…, however I don’t use a template, just plain bokeh. Any idea how I would embed that downloaded script there?

Sorry for the delay. The most logical next step is - add a template. If that’s not an option - we “enhanced” things on our end further as we wanted to let bokeh manage the version of the bokeh js but just append our code (without it recompiling it).

but this is way out of the box and i’m assuming at some point as Bokeh changes things it will break our code but we are happy to take that risk for now to achieve our needed goals.

this is way out of the box and there is probably a better way of doing it but just in case it’s useful for you or anyone else (and we are assuming it will break at some point with a future update of bokeh).

the way Bokeh mounts the JS resources is using this array:


it then appends a version to the resources through this handler:


if you include your extra components into them it would enable Bokeh to inject it directly without needing to create a template. - again probably not recommended or the right way but how we resolved things on our end.

Hi there,

I’ve spent some time digging through bokeh’s server (and some tornado code) to actually understand what’s going on there. (I had mediocre success)

Your way (appending to _js_components and _static_hashes) would require copying my js-files into the bokeh/server/static/js if I’m not mistaken?

I managed to mount the js (in a probably very weird way) by editing bokeh.embed.bundle:

extension_dirs: Dict[str, str] = {'jquery': 'c:\\path\\to\\folder\\containing\\jquery'}
  1. at the end of the _bundle_extensions-method
bundles.insert(0, ExtensionEmbed('c:\\path\\to\\folder\\containing\\jquery\\jquery.js',
                   'static/extensions/jquery/jquery.js', None))

This solution doesn’t require moving any files around but it really seems like it is less than suboptimal. I’ll continue digging around, especially since I have no clue how to embed css with my method.

i think this is as far as i looked behind the hood :wink:
on our end it works well. but we do have a template for our css and other js that is not components). our steps in short are:

  1. leave bokeh to manage it’s own js/css
  2. inject our components (js) into bokeh when they are compiled already.
  3. tell bokeh to skip compiling components

i think all the steps are critical for it to work. as we don’t want Bokeh to compile components on build. any other css/js we have hard coded into the templates.

1 Like

I created a monkeypatch, so if anyone in the future really does not want to use templates they can use this as a lead. This does not work for notebooks, but I’m sure I can manage to make it work there, too.

import os
root = os.path.dirname(os.path.abspath(__file__))

server_path = "static/extensions/"
static_path = "models\\static"

def monkeypatch_serve_local_resources():
    from bokeh.server.urls import extension_dirs
    extension_dirs['static_js'] = os.path.join(root, static_path, "js")
    extension_dirs['static_css'] = os.path.join(root, static_path, "css")

    import bokeh.embed.bundle as beb

    beb_bundle_of = beb.Bundle.of

    def monkeypatch(js_files, js_raw, css_files, css_raw, hashes):
        bundle = beb_bundle_of(js_files, js_raw, css_files, css_raw, hashes)
        js_files = bundle.js_files
        # TODO: make this dynamically get the local resources
        js_files.insert(0, server_path+"static_js/jquery.min.js")
        js_files.insert(1, server_path+"static_js/jstree.min.js")
        js_files.insert(2, server_path+"static_js/jstreegrid.min.js")
        css_files = bundle.css_files
        css_files.extend([server_path+"static_css/bootstrap.min.css", server_path+"static_css/style.min.css"])
        return bundle

    beb.Bundle.of = monkeypatch


I feel dirty now.