Repository: bokeh/bokeh

Issue:
Autoload with widgets fails if earlier autoload does not specify widgets
Some of the User's Guide examples with widgets do not render:

https://docs.bokeh.org/en/dev/docs/user_guide/layout.html#multiple-objects

Error is:

> Uncaught (in promise) Error: Model 'Select' does not exist. This could be due to a widget or a custom model not being registered before first usage.


By contrast, all the widgets on this page do render:

https://docs.bokeh.org/en/dev/docs/user_guide/interaction/widgets.html

Important observations:

* recently, bundle computation was made narrower: content that did not reference widgets no longer results in a widgets bundle being added to `js_urls`

* On the page that works, the first autoload script *does* have widgets. On the page that fails, the first autoload script does *not* have widgets.

My supposition is that the autoload script is broken, and will not load any additional bundles past whatever the first autoload script on a page requests. I further suppose this has in fact been broken for a long time, but was masked by the fact that until recently we unconditionally included all possible bundles in autoload, whether they were needed or not. 

A "quick fix" would be to return to the unconditional behavior, but hopefully we can find a real fix. 

cc @mattpap @philippjfr 


Discussion:

Resolving patch:
diff --git a/bokeh/embed/standalone.py b/bokeh/embed/standalone.py
--- a/bokeh/embed/standalone.py
+++ b/bokeh/embed/standalone.py
@@ -97,7 +97,7 @@ def autoload_static(model: Union[Model, Document], resources: Resources, script_
     with OutputDocumentFor(models):
         (docs_json, [render_item]) = standalone_docs_json_and_render_items([model])
 
-    bundle = bundle_for_objs_and_resources(models, resources)
+    bundle = bundle_for_objs_and_resources(None, resources)
     bundle.add(Script(script_for_render_items(docs_json, [render_item])))
 
     (_, elementid) = list(render_item.roots.to_json().items())[0]


Test patch:
diff --git a/tests/unit/bokeh/embed/test_standalone.py b/tests/unit/bokeh/embed/test_standalone.py
--- a/tests/unit/bokeh/embed/test_standalone.py
+++ b/tests/unit/bokeh/embed/test_standalone.py
@@ -53,15 +53,6 @@ def test_plot() -> None:
     test_plot.circle([1, 2], [2, 3])
     return test_plot
 
-@pytest.fixture
-def test_plot_and_widget() -> None:
-    from bokeh.plotting import figure
-    from bokeh.layouts import column
-    from bokeh.models import Div
-    test_plot = figure(title="'foo'")
-    test_plot.circle([1, 2], [2, 3])
-    return column(Div(text="foo"), test_plot)
-
 PAGE = Template("""
 <!DOCTYPE html>
 <html lang="en">
@@ -90,17 +81,6 @@ def test_script_attrs(self, test_plot) -> None:
         js, tag = bes.autoload_static(test_plot, CDN, "some/path")
         html = bs4.BeautifulSoup(tag, "html.parser")
         scripts = html.findAll(name='script')
-        assert "bokeh-widgets" not in js
-        assert len(scripts) == 1
-        attrs = scripts[0].attrs
-        assert set(attrs) == set(['src', 'id'])
-        assert attrs['src'] == 'some/path'
-
-    def test_script_attrs_with_widgets(self, test_plot_and_widget) -> None:
-        js, tag = bes.autoload_static(test_plot_and_widget, CDN, "some/path")
-        html = bs4.BeautifulSoup(tag, "html.parser")
-        scripts = html.findAll(name='script')
-        assert "bokeh-widgets" in js
         assert len(scripts) == 1
         attrs = scripts[0].attrs
         assert set(attrs) == {"src", "id"}
@@ -122,7 +102,7 @@ def test_js_dev_cdn(self, version, monkeypatch, driver, test_file_path_and_url,
         driver.get(url)
 
         scripts = driver.find_elements_by_css_selector('head script')
-        assert len(scripts) == 1
+        assert len(scripts) == 3
         for script in scripts:
             assert script.get_attribute("crossorigin") == None
             assert script.get_attribute("integrity") == ""
@@ -144,29 +124,7 @@ def test_js_release_cdn(self, monkeypatch, driver, test_file_path_and_url, test_
         scripts = driver.find_elements_by_css_selector('head script')
         for x in scripts:
             print(x.get_attribute("src"))
-        assert len(scripts) == 1
-        for script in scripts:
-            assert script.get_attribute("crossorigin") == "anonymous"
-            assert script.get_attribute("integrity").startswith("sha384-")
-
-    @pytest.mark.selenium
-    def test_js_release_cdn_with_widgets(self, monkeypatch, driver, test_file_path_and_url, test_plot_and_widget) -> None:
-        monkeypatch.setattr(buv, "__version__", "2.0.0")
-        monkeypatch.setattr(resources, "__version__", "2.0.0")
-        js, tag = bes.autoload_static(test_plot_and_widget, CDN, "some/path")
-
-        page = PAGE.render(js=js, tag=tag)
-
-        path, url = test_file_path_and_url
-        with open(path, "w") as f:
-            f.write(page)
-
-        driver.get(url)
-
-        scripts = driver.find_elements_by_css_selector('head script')
-        for x in scripts:
-            print(x.get_attribute("src"))
-        assert len(scripts) == 2  # 2 to include widgets bundle
+        assert len(scripts) == 3
         for script in scripts:
             assert script.get_attribute("crossorigin") == "anonymous"
             assert script.get_attribute("integrity").startswith("sha384-")
@@ -188,7 +146,7 @@ def test_js_release_dev_cdn(self, monkeypatch, driver, test_file_path_and_url, t
         scripts = driver.find_elements_by_css_selector('head script')
         for x in scripts:
             print(x.get_attribute("src"))
-        assert len(scripts) == 1
+        assert len(scripts) == 3
         for script in scripts:
             assert script.get_attribute("crossorigin") == "anonymous"
             assert script.get_attribute("integrity").startswith("sha384-")
@@ -208,7 +166,7 @@ def test_js_release_server(self, monkeypatch, driver, test_file_path_and_url, te
         driver.get(url)
 
         scripts = driver.find_elements_by_css_selector('head script')
-        assert len(scripts) == 1
+        assert len(scripts) == 3
         for script in scripts:
             assert script.get_attribute("crossorigin") == None
             assert script.get_attribute("integrity") == ""