Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve notebook resource and extension loading #4752

Merged
merged 9 commits into from
May 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 97 additions & 36 deletions panel/_templates/autoload_panel_js.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ calls it with the rendered model.
}

var force = {{ force|default(False)|json }};
var py_version = '{{ version }}';
var is_dev = py_version.indexOf("+") !== -1 || py_version.indexOf("-") !== -1;
var reloading = {{ reloading|default(False)|json }};
var Bokeh = root.Bokeh;
var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));

if (typeof root._bokeh_onload_callbacks === "undefined" || force === true) {
root._bokeh_onload_callbacks = [];
root._bokeh_is_loading = undefined;
}

if (typeof (root._bokeh_timeout) === "undefined" || force === true) {
if (typeof (root._bokeh_timeout) === "undefined" || force) {
root._bokeh_timeout = Date.now() + {{ timeout|default(0)|json }};
root._bokeh_failed_load = false;
}
Expand All @@ -42,7 +42,7 @@ calls it with the rendered model.
callback();
});
} finally {
delete root._bokeh_onload_callbacks
delete root._bokeh_onload_callbacks;
}
console.debug("Bokeh: all callbacks have finished");
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
}
Expand All @@ -54,6 +54,7 @@ calls it with the rendered model.
if (js_exports == null) js_exports = {};

root._bokeh_onload_callbacks.push(callback);

if (root._bokeh_is_loading > 0) {
console.debug("Bokeh: BokehJS is being loaded, scheduling callback at", now());
return null;
Expand All @@ -62,7 +63,9 @@ calls it with the rendered model.
run_callbacks();
return null;
}
console.debug("Bokeh: BokehJS not loaded, scheduling load and callback at", now());
if (!reloading) {
console.debug("Bokeh: BokehJS not loaded, scheduling load and callback at", now());
}

function on_load() {
root._bokeh_is_loading--;
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -77,18 +80,6 @@ calls it with the rendered model.
console.error("failed to load " + url);
}

for (var i = 0; i < css_urls.length; i++) {
var url = css_urls[i];
const element = document.createElement("link");
element.onload = on_load;
element.onerror = on_error;
element.rel = "stylesheet";
element.type = "text/css";
element.href = url;
console.debug("Bokeh: injecting link tag for BokehJS stylesheet: ", url);
document.body.appendChild(element);
}

var skip = [];
if (window.requirejs) {
window.requirejs.config({{ config|conffilter }});
Expand All @@ -104,6 +95,31 @@ calls it with the rendered model.
} else {
root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;
}

var existing_stylesheets = []
var links = document.getElementsByTagName('link')
for (var i = 0; i < links.length; i++) {
var link = links[i]
if (link.href != null) {
existing_stylesheets.push(link.href)
}
}
for (var i = 0; i < css_urls.length; i++) {
var url = css_urls[i];
if (existing_stylesheets.indexOf(url) !== -1) {
on_load()
continue;
}
const element = document.createElement("link");
element.onload = on_load;
element.onerror = on_error;
element.rel = "stylesheet";
element.type = "text/css";
element.href = url;
console.debug("Bokeh: injecting link tag for BokehJS stylesheet: ", url);
document.body.appendChild(element);
}

{%- for lib, urls in skip_imports.items() %}
if (((window['{{ lib }}'] !== undefined) && (!(window['{{ lib }}'] instanceof HTMLElement))) || window.requirejs) {
var urls = {{ urls }};
Expand All @@ -112,9 +128,17 @@ calls it with the rendered model.
}
}
{%- endfor %}
var existing_scripts = []
var scripts = document.getElementsByTagName('script')
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i]
if (script.src != null) {
existing_scripts.push(script.src)
}
}
for (var i = 0; i < js_urls.length; i++) {
var url = js_urls[i];
if (skip.indexOf(url) >= 0) {
if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {
if (!window.requirejs) {
on_load();
}
Expand All @@ -130,7 +154,7 @@ calls it with the rendered model.
}
for (var i = 0; i < js_modules.length; i++) {
var url = js_modules[i];
if (skip.indexOf(url) >= 0) {
if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {
if (!window.requirejs) {
on_load();
}
Expand All @@ -147,7 +171,7 @@ calls it with the rendered model.
}
for (const name in js_exports) {
var url = js_exports[name];
if (skip.indexOf(url) >= 0) {
if (skip.indexOf(url) >= 0 || root[name] != null) {
if (!window.requirejs) {
on_load();
}
Expand Down Expand Up @@ -186,19 +210,33 @@ calls it with the rendered model.
inject_raw_css({{ css|json }});
},
{%- endfor %}
{%- for js in bundle.js_raw %}
function(Bokeh) {
{{ js|indent(6) }}
},
{% endfor -%}
function(Bokeh) {} // ensure no trailing comma for IE
];
// If bokeh is loaded we do not need to load raw JS (assumes only Bokeh/Panel are inlined)
if (!bokeh_loaded || is_dev) {
{% for js in bundle.js_raw %}
inline_js.push(function(Bokeh) {
{{ js|indent(6) }}
})
{% endfor %}
}

function run_inline_js() {
if ((root.Bokeh !== undefined) || (force === true)) {
for (var i = 0; i < inline_js.length; i++) {
inline_js[i].call(root, root.Bokeh);
}
// Cache old bokeh versions
if (Bokeh != undefined && !reloading) {
var NewBokeh = root.Bokeh;
if (Bokeh.versions === undefined) {
Bokeh.versions = new Map();
}
if (NewBokeh.version !== Bokeh.version) {
Bokeh.versions.set(NewBokeh.version, NewBokeh)
}
root.Bokeh = Bokeh;
}
{%- if elementid -%}
if (force === true) {
display_loaded();
Expand All @@ -210,15 +248,38 @@ calls it with the rendered model.
console.log("Bokeh: BokehJS failed to load within specified timeout.");
root._bokeh_failed_load = true;
}
root._bokeh_is_initializing = false
}

if (root._bokeh_is_loading === 0) {
console.debug("Bokeh: BokehJS loaded, going straight to plotting");
run_inline_js();
} else {
load_libs(css_urls, js_urls, js_modules, js_exports, function() {
console.debug("Bokeh: BokehJS plotting callback run at", now());
run_inline_js();
});
function load_or_wait() {
// Implement a backoff loop that tries to ensure we do not load multiple
// versions of Bokeh and its dependencies at the same time.
// In recent versions we use the root._bokeh_is_initializing flag
// to determine whether there is an ongoing attempt to initialize
// bokeh, however for backward compatibility we also try to ensure
// that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version
// before older versions are fully initialized.
if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {
root._bokeh_is_initializing = false;
console.log("Bokeh: BokehJS was loaded multiple times but one version failed to initialize.");
load_or_wait();
} else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === "undefined" && root._bokeh_onload_callbacks !== undefined)) {
setTimeout(load_or_wait, 100);
} else {
Bokeh = root.Bokeh;
bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));
root._bokeh_is_initializing = true
root._bokeh_onload_callbacks = []
if (!reloading && (!bokeh_loaded || is_dev)) {
root.Bokeh = undefined;
}
load_libs(css_urls, js_urls, js_modules, js_exports, function() {
console.debug("Bokeh: BokehJS plotting callback run at", now());
run_inline_js();
});
}
}
// Give older versions of the autoload script a head-start to ensure
// they initialize before we start loading newer version.
setTimeout(load_or_wait, 100)
}(window));
34 changes: 29 additions & 5 deletions panel/_templates/doc_nb_js.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
(function(root) {
var docs_json = {{ docs_json }};
var render_items = {{ render_items }};
var docs = Object.values(docs_json)
if (!docs) {
return
}
const py_version = docs[0].version
const is_dev = py_version.indexOf("+") !== -1 || py_version.indexOf("-") !== -1
function embed_document(root) {
var docs_json = {{ docs_json }};
var render_items = {{ render_items }};
root.Bokeh.embed.embed_items_notebook(docs_json, render_items);
var Bokeh = get_bokeh(root)
Bokeh.embed.embed_items_notebook(docs_json, render_items);
for (const render_item of render_items) {
for (const root_id of render_item.root_ids) {
const id_el = document.getElementById(root_id)
Expand All @@ -13,12 +20,29 @@
}
}
}
if (root.Bokeh !== undefined && root.Bokeh.Panel !== undefined{% for reqs in requirements %} && ({% for req in reqs %}{% if loop.index0 > 0 %}||{% endif %} root['{{ req }}'] !== undefined{% endfor %}){% endfor %}{% if ipywidget %}&& (root.Bokeh.Models.registered_names().indexOf("ipywidgets_bokeh.widget.IPyWidget") > -1){% endif %}) {
function get_bokeh(root) {
if (root.Bokeh === undefined) {
return null
} else if (root.Bokeh.version !== py_version && !is_dev) {
if (root.Bokeh.versions === undefined || !root.Bokeh.versions.has(py_version)) {
return null
}
return root.Bokeh.versions.get(py_version);
} else if (root.Bokeh.version === py_version) {
return root.Bokeh
}
return null
}
function is_loaded(root) {
var Bokeh = get_bokeh(root)
return (Bokeh != null && Bokeh.Panel !== undefined{% for reqs in requirements %} && ({% for req in reqs %}{% if loop.index0 > 0 %}&&{% endif %} root['{{ req }}'] !== undefined{% endfor %}){% endfor %}{% if ipywidget %}&& (Bokeh.Models.registered_names().indexOf("ipywidgets_bokeh.widget.IPyWidget") > -1){% endif %})
}
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
if (is_loaded(root)) {
embed_document(root);
} else {
var attempts = 0;
var timer = setInterval(function(root) {
if (root.Bokeh !== undefined && root.Bokeh.Panel !== undefined{% for reqs in requirements %} && ({% for req in reqs %}{% if loop.index0 > 0 %} || {% endif %}root['{{ req }}'] !== undefined{% endfor %}){% endfor %}{% if ipywidget %}&& (root.Bokeh.Models.registered_names().indexOf("ipywidgets_bokeh.widget.IPyWidget") > -1){% endif %}) {
if (is_loaded(root)) {
clearInterval(timer);
embed_document(root);
} else if (document.readyState == "complete") {
Expand Down
46 changes: 13 additions & 33 deletions panel/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ def __call__(self, *args, **params):
reactive_exts = {
v._extension_name: v for k, v in param.concrete_descendents(ReactiveHTML).items()
}
newly_loaded = [arg for arg in args if arg not in panel_extension._loaded_extensions]
if state.curdoc and state.curdoc not in state._extensions_:
state._extensions_[state.curdoc] = []
for arg in args:
Expand Down Expand Up @@ -662,7 +663,8 @@ def __call__(self, *args, **params):
raise ValueError('%s should be supplied as a list, '
'not as a %s type.' %
(k, type(v).__name__))
getattr(config, k).extend(v)
existing = getattr(config, k)
existing.extend([new for new in v if new not in existing])
elif k == 'js_files':
getattr(config, k).update(v)
else:
Expand Down Expand Up @@ -694,6 +696,9 @@ def __call__(self, *args, **params):
else:
hv.Store.current_backend = backend

if config.load_entry_points:
self._load_entry_points()

# Abort if IPython not found
try:
ip = params.pop('ip', None) or get_ipython() # noqa (get_ipython)
Expand All @@ -704,31 +709,7 @@ def __call__(self, *args, **params):

self._detect_comms(params)

newly_loaded = [arg for arg in args if arg not in panel_extension._loaded_extensions]
custom_resources = [
resource for resource in ('css_files', 'js_files', 'js_modules', 'raw_css')
if getattr(config, resource)
]
if loaded and newly_loaded:
self.param.warning(
"A HoloViz extension was loaded previously. This means "
"the extension is already initialized and the following "
f"Panel extensions could not be properly loaded: {newly_loaded}."
"If you are loading custom extensions with pn.extension(...) "
"ensure that this is called before any other HoloViz "
"extension such as hvPlot or HoloViews."
)
elif loaded and custom_resources:
resources_string = ', '.join(custom_resources)
self.param.warning(
"A HoloViz extension was loaded previously. This means the "
f"extension is already initialized and custom {resources_string} "
"resources could not be loaded. If you are loading custom "
"extensions with pn.extension(...) ensure that this is called"
"before any other HoloViz extension such as hvPlot or HoloViews."
)
else:
panel_extension._loaded_extensions += newly_loaded
panel_extension._loaded_extensions += newly_loaded

if hasattr(ip, 'kernel') and not self._loaded and not config._doc_build:
# TODO: JLab extension and pyviz_comms should be changed
Expand All @@ -754,22 +735,21 @@ def __call__(self, *args, **params):
# Disable simple ids, old state and multiple tabs in notebooks can cause IDs to clash
bk_settings.simple_ids.set_value(False)

if not nb_loaded and hasattr(ip, 'kernel'):
load_notebook(config.inline)
if hasattr(ip, 'kernel'):
load_notebook(
config.inline, reloading=nb_loaded
)
panel_extension._loaded = True

if config.browser_info and state.browser_info:
if not nb_loaded and config.browser_info and state.browser_info:
doc = Document()
comm = state._comm_manager.get_server_comm()
model = state.browser_info._render_model(doc, comm)
bundle, meta = state.browser_info._render_mimebundle(model, doc, comm)
display(bundle, metadata=meta, raw=True) # noqa
if config.notifications:
if not nb_loaded and config.notifications:
display(state.notifications) # noqa

if config.load_entry_points:
self._load_entry_points()

def _detect_comms(self, params):
called_before = self._comms_detected_before
self._comms_detected_before = True
Expand Down
Loading