Repository: bokeh/bokeh

Issue:
Save does not work with custom models [BUG]
Custom models built using the new build infrastructure do not seem to work when saved using bokeh's `save` function and/or `file_html`. I don't know how to make a very simple example so all I can suggest is installing panel master, building it with `python setup.py develop` and then running:

```python
import panel as pn
from bokeh.io import save
pn.extension('vega')

vegalite = {
  "$schema": "https://vega.github.io/schema/vega-lite/v3.json",
  "data": {"url": "https://raw.githubusercontent.com/vega/vega/master/docs/data/barley.json"},
  "mark": "bar",
  "encoding": {
    "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
    "y": {"field": "variety", "type": "nominal"},
    "color": {"field": "site", "type": "nominal"}
  }
}
vgl_pane = pn.panel(vegalite, height=240)
bokeh_model = vgl_pane.get_root()

save(bokeh_model, 'vega.html')
```

```
bokeh-1.4.0rc2.min.js:81 Uncaught Error: Cannot find module '@bokehjs/models/widgets/widget'
    at require (bokeh-1.4.0rc2.min.js:81)
    at Object._ (vega.html:158)
    at require (bokeh-1.4.0rc2.min.js:88)
    at Object._ (vega.html:156)
    at require (bokeh-1.4.0rc2.min.js:88)
    at Object._ (vega.html:155)
    at require (bokeh-1.4.0rc2.min.js:88)
    at Object.main.register_plugin (bokeh-1.4.0rc2.min.js:115)
    at vega.html:149
    at vega.html:154
require @ bokeh-1.4.0rc2.min.js:81
_ @ vega.html:158
require @ bokeh-1.4.0rc2.min.js:88
_ @ vega.html:156
require @ bokeh-1.4.0rc2.min.js:88
_ @ vega.html:155
require @ bokeh-1.4.0rc2.min.js:88
main.register_plugin @ bokeh-1.4.0rc2.min.js:115
(anonymous) @ vega.html:149
(anonymous) @ vega.html:154
(anonymous) @ vega.html:144
(anonymous) @ vega.html:145
bokeh-1.4.0rc2.min.js:243 Uncaught Error: Model 'panel.models.vega.VegaPlot' does not exist. This could be due to a widget or a custom model not being registered before first usage.
    at Object.o.Models (bokeh-1.4.0rc2.min.js:243)
    at Function.e._instantiate_object (bokeh-1.4.0rc2.min.js:242)
    at Function.e._instantiate_references_json (bokeh-1.4.0rc2.min.js:242)
    at Function.e.from_json (bokeh-1.4.0rc2.min.js:242)
    at v (bokeh-1.4.0rc2.min.js:240)
    at bokeh-1.4.0rc2.min.js:240
```


Discussion:
The problem is that when loading panel.js, widgets are needed, but bokeh's embed code didn't include bokeh-widgets bundle in the saved HTML file. I will fix this in a few hours when I finish my travel.

Resolving patch:
diff --git a/bokeh/embed/bundle.py b/bokeh/embed/bundle.py
--- a/bokeh/embed/bundle.py
+++ b/bokeh/embed/bundle.py
@@ -191,6 +191,26 @@ def bundle_for_objs_and_resources(objs, resources):
 # Private API
 #-----------------------------------------------------------------------------
 
+def _query_extensions(objs, query):
+    names = set()
+
+    for obj in _all_objs(objs):
+        if hasattr(obj, "__implementation__"):
+            continue
+        name = obj.__view_module__.split(".")[0]
+        if name == "bokeh":
+            continue
+        if name in names:
+            continue
+        names.add(name)
+
+        for model in Model.model_class_reverse_map.values():
+            if model.__module__.startswith(name):
+                if query(model):
+                    return True
+
+    return False
+
 def _bundle_extensions(objs, resources):
     names = set()
     extensions = []
@@ -271,7 +291,7 @@ def _use_tables(objs):
 
     '''
     from ..models.widgets import TableWidget
-    return _any(objs, lambda obj: isinstance(obj, TableWidget))
+    return _any(objs, lambda obj: isinstance(obj, TableWidget)) or _ext_use_tables(objs)
 
 def _use_widgets(objs):
     ''' Whether a collection of Bokeh objects contains a any Widget
@@ -284,7 +304,15 @@ def _use_widgets(objs):
 
     '''
     from ..models.widgets import Widget
-    return _any(objs, lambda obj: isinstance(obj, Widget))
+    return _any(objs, lambda obj: isinstance(obj, Widget)) or _ext_use_widgets(objs)
+
+def _ext_use_tables(objs):
+    from ..models.widgets import TableWidget
+    return _query_extensions(objs, lambda cls: issubclass(cls, TableWidget))
+
+def _ext_use_widgets(objs):
+    from ..models.widgets import Widget
+    return _query_extensions(objs, lambda cls: issubclass(cls, Widget))
 
 #-----------------------------------------------------------------------------
 # Code


Test patch: