diff --git a/solara/__main__.py b/solara/__main__.py index 172f6d47e..ee10d3246 100644 --- a/solara/__main__.py +++ b/solara/__main__.py @@ -595,7 +595,7 @@ def staticbuild(): include_nbextensions = True if include_nbextensions: directories = solara.server.server.get_nbextensions_directories() - nbextensions = solara.server.server.get_nbextensions() + nbextensions, ignore = solara.server.server.get_nbextensions() for name in nbextensions: for directory in directories: if (directory / (name + ".js")).exists(): diff --git a/solara/server/server.py b/solara/server/server.py index 64da8a532..ec507633a 100644 --- a/solara/server/server.py +++ b/solara/server/server.py @@ -5,7 +5,7 @@ import sys import time from pathlib import Path -from typing import Dict, List, Optional, TypeVar +from typing import Dict, List, Optional, Tuple, TypeVar import ipykernel import ipyvue @@ -262,9 +262,9 @@ def read_root(path: str, root_path: str = "", render_kwargs={}, use_nbextensions if not router.possible_match: return None if use_nbextensions: - nbextensions = get_nbextensions() + nbextensions, nbextensions_hashes = get_nbextensions() else: - nbextensions = [] + nbextensions, nbextensions_hashes = [], {} from markupsafe import Markup @@ -328,6 +328,7 @@ def include_js(path: str, module=False) -> Markup: resources = { "theme": "light", "nbextensions": nbextensions, + "nbextensions_hashes": nbextensions_hashes, "include_css": include_css, "include_js": include_js, } @@ -397,7 +398,7 @@ def get_nbextensions_directories() -> List[Path]: @solara.memoize(storage=cache_memory) -def get_nbextensions() -> List[str]: +def get_nbextensions() -> Tuple[List[str], Dict[str, Optional[str]]]: from jupyter_core.paths import jupyter_config_path paths = [Path(p) / "nbconfig" for p in jupyter_config_path()] @@ -411,8 +412,15 @@ def exists(name): logger.info(f"nbextension {name} not found") return False - nbextensions = [name for name, enabled in load_extensions.items() if enabled and (name not in nbextensions_ignorelist) and exists(name)] - return nbextensions + def hash_file(name): + for directory in nbextensions_directories: + if (directory / (name + ".js")).exists(): + return solara.util.get_file_hash(directory / (name + ".js"))[1] + return None + + nbextensions: List[str] = [name for name, enabled in load_extensions.items() if enabled and (name not in nbextensions_ignorelist) and exists(name)] + nbextensions_hashes = {name: hash_file(name) for name in nbextensions} + return nbextensions, nbextensions_hashes nbextensions_directories = get_nbextensions_directories() diff --git a/solara/server/templates/solara.html.j2 b/solara/server/templates/solara.html.j2 index 8fb899d83..94bdb8cb6 100644 --- a/solara/server/templates/solara.html.j2 +++ b/solara/server/templates/solara.html.j2 @@ -379,6 +379,7 @@ {% if 'jupyter-vuetify/extension' in resources.nbextensions -%} window.enable_nbextensions = true; {% endif -%} + nbextensionHashes = {{ resources.nbextensions_hashes | tojson | safe }}; requirejs.config({ baseUrl: '{{root_path}}/static/', waitSeconds: 3000, @@ -391,7 +392,16 @@ 'jupyter-vuetify': 'nbextensions/jupyter-vuetify/nodeps', {% endif -%} }, - } + }, + urlArgs: function (id, url) { + id = id.replace("/static/nbextensions/", "").replace(".js", ""); + if (nbextensionHashes[id] !== undefined) { + console.log('nbextensionHashes', id, nbextensionHashes[id]); + return (url.indexOf('?') === -1 ? '?' : '&') + nbextensionHashes[id]; + } else { + return ''; + } + }, }); requirejs([ {% for ext in resources.nbextensions if ext != 'jupyter-vuetify/extension' and ext != 'jupyter-vue/extension' -%}