diff --git a/datasette/app.py b/datasette/app.py
index bd62fd3b..f8549fac 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -864,19 +864,23 @@ class Datasette:
if isinstance(url_or_dict, dict):
url = url_or_dict["url"]
sri = url_or_dict.get("sri")
+ module = bool(url_or_dict.get("module"))
else:
url = url_or_dict
sri = None
+ module = False
if url in seen_urls:
continue
seen_urls.add(url)
if url.startswith("/"):
# Take base_url into account:
url = self.urls.path(url)
+ script = {"url": url}
if sri:
- output.append({"url": url, "sri": sri})
- else:
- output.append({"url": url})
+ script["sri"] = sri
+ if module:
+ script["module"] = True
+ output.append(script)
return output
def app(self):
diff --git a/datasette/templates/base.html b/datasette/templates/base.html
index 3ed67164..3f3d4507 100644
--- a/datasette/templates/base.html
+++ b/datasette/templates/base.html
@@ -8,7 +8,7 @@
{% endfor %}
{% for url in extra_js_urls %}
-
+
{% endfor %}
{% block extra_head %}{% endblock %}
diff --git a/docs/custom_templates.rst b/docs/custom_templates.rst
index d37bb729..a7236873 100644
--- a/docs/custom_templates.rst
+++ b/docs/custom_templates.rst
@@ -5,6 +5,8 @@ Custom pages and templates
Datasette provides a number of ways of customizing the way data is displayed.
+.. _customization_css_and_javascript:
+
Custom CSS and JavaScript
-------------------------
@@ -25,7 +27,12 @@ Your ``metadata.json`` file can include links that look like this:
]
}
-The extra CSS and JavaScript files will be linked in the ``
`` of every page.
+The extra CSS and JavaScript files will be linked in the ```` of every page:
+
+.. code-block:: html
+
+
+
You can also specify a SRI (subresource integrity hash) for these assets:
@@ -46,9 +53,39 @@ You can also specify a SRI (subresource integrity hash) for these assets:
]
}
+This will produce:
+
+.. code-block:: html
+
+
+
+
Modern browsers will only execute the stylesheet or JavaScript if the SRI hash
matches the content served. You can generate hashes using `www.srihash.org `_
+Items in ``"extra_js_urls"`` can specify ``"module": true`` if they reference JavaScript that uses `JavaScript modules `__. This configuration:
+
+.. code-block:: json
+
+ {
+ "extra_js_urls": [
+ {
+ "url": "https://example.datasette.io/module.js",
+ "module": true
+ }
+ ]
+ }
+
+Will produce this HTML:
+
+.. code-block:: html
+
+
+
CSS classes on the
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst
index 72b09367..d465307b 100644
--- a/docs/plugin_hooks.rst
+++ b/docs/plugin_hooks.rst
@@ -182,7 +182,7 @@ This can be a list of URLs:
@hookimpl
def extra_css_urls():
return [
- 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css'
+ "https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
]
Or a list of dictionaries defining both a URL and an
@@ -190,21 +190,17 @@ Or a list of dictionaries defining both a URL and an
.. code-block:: python
- from datasette import hookimpl
-
@hookimpl
def extra_css_urls():
return [{
- 'url': 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css',
- 'sri': 'sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4',
+ "url": "https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css",
+ "sri": "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4",
}]
This function can also return an awaitable function, useful if it needs to run any async code:
.. code-block:: python
- from datasette import hookimpl
-
@hookimpl
def extra_css_urls(datasette):
async def inner():
@@ -233,8 +229,8 @@ return a list of URLs, a list of dictionaries or an awaitable function that retu
@hookimpl
def extra_js_urls():
return [{
- 'url': 'https://code.jquery.com/jquery-3.3.1.slim.min.js',
- 'sri': 'sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo',
+ "url": "https://code.jquery.com/jquery-3.3.1.slim.min.js",
+ "sri": "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo",
}]
You can also return URLs to files from your plugin's ``static/`` directory, if
@@ -242,12 +238,21 @@ you have one:
.. code-block:: python
- from datasette import hookimpl
-
@hookimpl
def extra_js_urls():
return [
- '/-/static-plugins/your-plugin/app.js'
+ "/-/static-plugins/your-plugin/app.js"
+ ]
+
+If your code uses `JavaScript modules `__ you should include the ``"module": True`` key. See :ref:`customization_css_and_javascript` for more details.
+
+.. code-block:: python
+
+ @hookimpl
+ def extra_js_urls():
+ return [{
+ "url": "/-/static-plugins/your-plugin/app.js",
+ "module": True
]
Examples: `datasette-cluster-map `_, `datasette-vega `_
diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py
index 2e653e2b..1c86b4bc 100644
--- a/tests/plugins/my_plugin.py
+++ b/tests/plugins/my_plugin.py
@@ -61,6 +61,7 @@ def extra_js_urls():
"sri": "SRIHASH",
},
"https://plugin-example.datasette.io/plugin1.js",
+ {"url": "https://plugin-example.datasette.io/plugin.module.js", "module": True},
]
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 212de2b5..648e7abd 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -118,16 +118,19 @@ def test_hook_extra_css_urls(app_client, path, expected_decoded_object):
def test_hook_extra_js_urls(app_client):
response = app_client.get("/")
scripts = Soup(response.body, "html.parser").findAll("script")
- assert [
- s
- for s in scripts
- if s.attrs
- == {
+ script_attrs = [s.attrs for s in scripts]
+ for attrs in [
+ {
"integrity": "SRIHASH",
"crossorigin": "anonymous",
"src": "https://plugin-example.datasette.io/jquery.js",
- }
- ]
+ },
+ {
+ "src": "https://plugin-example.datasette.io/plugin.module.js",
+ "type": "module",
+ },
+ ]:
+ assert any(s == attrs for s in script_attrs), "Expected: {}".format(attrs)
def test_plugins_with_duplicate_js_urls(app_client):