Skip to content

Commit a78b32a

Browse files
authored
Merge pull request #22 from rstudio/application-html-dependencies
2 parents 4ac956f + f9b8cbb commit a78b32a

File tree

7 files changed

+57
-8
lines changed

7 files changed

+57
-8
lines changed

htmltools/_core.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222
if sys.version_info >= (3, 8):
2323
from typing import TypedDict, SupportsIndex, Protocol, runtime_checkable, Literal
2424
else:
25-
from typing_extensions import TypedDict, SupportsIndex, Protocol, runtime_checkable, Literal
25+
from typing_extensions import (
26+
TypedDict,
27+
SupportsIndex,
28+
Protocol,
29+
runtime_checkable,
30+
Literal,
31+
)
2632

2733
from packaging.version import Version
2834

@@ -32,6 +38,7 @@
3238
_package_dir, # type: ignore
3339
_html_escape, # type: ignore
3440
_flatten, # type: ignore
41+
hash_deterministic,
3542
)
3643

3744
__all__ = (
@@ -793,7 +800,19 @@ def _hoist_head_content(
793800
# Put <meta charset="utf-8"> at beginning of head, and other hoisted tags at the
794801
# end. This matters only if the <head> tag starts out with some children.
795802
head.insert(0, Tag("meta", charset="utf-8"))
803+
804+
# Add some metadata about the dependencies so that shiny.js' renderDependency
805+
# logic knows not to re-render them.
796806
deps = x.get_dependencies()
807+
if len(deps) > 0:
808+
head.append(
809+
Tag(
810+
"script",
811+
";".join([d.name + "[" + str(d.version) + "]" for d in deps]),
812+
type="application/html-dependencies",
813+
)
814+
)
815+
797816
head.extend(
798817
[
799818
d.as_html_tags(lib_prefix=lib_prefix, include_version=include_version)
@@ -1113,6 +1132,14 @@ def head_content(*args: TagChildArg) -> HTMLDependency:
11131132
*args
11141133
The content to place in the ``<head>``.
11151134
1135+
Note
1136+
----
1137+
If the same content, ``x``, is included in a document multiple times via
1138+
``head_content(x)``, ``x`` will only appear once in the final HTML document's
1139+
``<head>``. More often than not, this is desirable behavior, but if you need the
1140+
same content included multiple times, you can add some irrelevant/empty tags (e.g.,
1141+
``TagList(x, Tag("meta"))``) to make sure ``x`` is included multiple times.
1142+
11161143
Example
11171144
-------
11181145
>>> from htmltools import *
@@ -1132,7 +1159,7 @@ def head_content(*args: TagChildArg) -> HTMLDependency:
11321159
head = TagList(*args)
11331160
head_str = head.get_html_string()
11341161
# Create unique ID to use as name
1135-
name = "headcontent_{:x}".format(abs(hash(head_str)))
1162+
name = "headcontent_" + hash_deterministic(head_str)
11361163
return HTMLDependency(name=name, version="0.0", head=head)
11371164

11381165

htmltools/_util.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import hashlib
2+
import importlib
13
import os
24
import re
3-
import importlib
45
import tempfile
56
from typing import (
67
Any,
@@ -127,6 +128,13 @@ def _package_dir(package: str) -> str:
127128
return os.path.dirname(pkg_file)
128129

129130

131+
def hash_deterministic(s: str) -> str:
132+
"""
133+
Returns a deterministic hash of the given string.
134+
"""
135+
return hashlib.sha1(s.encode('utf-8')).hexdigest()
136+
137+
130138
class _HttpServerInfo(NamedTuple):
131139
port: int
132140
thread: Thread

tests/snapshots/snap_test_deps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<html>
1212
<head>
1313
<meta charset="utf-8"/>
14+
<script type="application/html-dependencies">a[1.1]</script>
1415
<script src="a-1.1/a1.js"></script>
1516
</head>
1617
<body>
@@ -23,6 +24,7 @@
2324
<html>
2425
<head>
2526
<meta charset="utf-8"/>
27+
<script type="application/html-dependencies">a[1.2]</script>
2628
<script src="a-1.2/a2.js"></script>
2729
</head>
2830
<body>
@@ -35,6 +37,7 @@
3537
<html>
3638
<head>
3739
<meta charset="utf-8"/>
40+
<script type="application/html-dependencies">a[1.1]</script>
3841
<script src="a-1.1/a1.js"></script>
3942
</head>
4043
<body>
@@ -49,6 +52,7 @@
4952
<html>
5053
<head>
5154
<meta charset="utf-8"/>
55+
<script type="application/html-dependencies">a[1.1]</script>
5256
<script src="a-1.1/a1.js"></script>
5357
</head>
5458
<body>
@@ -61,6 +65,7 @@
6165
<html>
6266
<head>
6367
<meta charset="utf-8"/>
68+
<script type="application/html-dependencies">a[1.1]</script>
6469
<script src="a-1.1/a1.js"></script>
6570
</head>
6671
<body>

tests/test_deps.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import os
2-
from tempfile import TemporaryDirectory
3-
from typing import Union, Optional
41
import textwrap
52

63
from htmltools import *
@@ -32,6 +29,7 @@ def test_dep_resolution():
3229
<html>
3330
<head>
3431
<meta charset="utf-8"/>
32+
<script type="application/html-dependencies">a[1.2.1];b[1.10];c[1.0]</script>
3533
<script src="a-1.2.1/a3.js"></script>
3634
<script src="b-1.10/b2.js"></script>
3735
<script src="c-1.0/c1.js"></script>
@@ -46,6 +44,7 @@ def test_dep_resolution():
4644
<html>
4745
<head>
4846
<meta charset="utf-8"/>
47+
<script type="application/html-dependencies">a[1.2.1];b[1.10];c[1.0]</script>
4948
<script src="libfoo/a-1.2.1/a3.js"></script>
5049
<script src="libfoo/b-1.10/b2.js"></script>
5150
<script src="libfoo/c-1.0/c1.js"></script>
@@ -93,6 +92,7 @@ def test_append_deps():
9392
<html>
9493
<head>
9594
<meta charset="utf-8"/>
95+
<script type="application/html-dependencies">a[1.2];b[1.0]</script>
9696
<script src="a-1.2/a2.js"></script>
9797
<script src="b-1.0/b1.js"></script>
9898
</head>
@@ -138,6 +138,7 @@ def fake_dep(**kwargs):
138138
<html>
139139
<head>
140140
<meta charset="utf-8"/>
141+
<script type="application/html-dependencies">a[1.0]</script>
141142
<link href="lib/a-1.0/css/bar%20foo.css" rel="stylesheet"/>
142143
<script src="lib/a-1.0/js/foo%20bar.js"></script>
143144
</head>

tests/test_html_document.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def test_html_document_html_input():
3030
<head>
3131
<meta charset="utf-8"/>
3232
<title>Title</title>
33+
<script type="application/html-dependencies">headcontent_81fe8bfe87576c3ecb22426f8e57847382917acf[0.0]</script>
3334
abcd
3435
</head>
3536
<body>
@@ -49,6 +50,7 @@ def test_html_document_html_input():
4950
<html lang="en">
5051
<head>
5152
<meta charset="utf-8"/>
53+
<script type="application/html-dependencies">headcontent_81fe8bfe87576c3ecb22426f8e57847382917acf[0.0]</script>
5254
abcd
5355
</head>
5456
<body>
@@ -68,6 +70,7 @@ def test_html_document_html_input():
6870
<html lang="en">
6971
<head>
7072
<meta charset="utf-8"/>
73+
<script type="application/html-dependencies">headcontent_81fe8bfe87576c3ecb22426f8e57847382917acf[0.0]</script>
7174
abcd
7275
</head>
7376
<body>
@@ -96,6 +99,7 @@ def test_html_document_head_hoisting():
9699
<html>
97100
<head>
98101
<meta charset="utf-8"/>
102+
<script type="application/html-dependencies">headcontent_f51fa154cb6a6ca2ef221e02b00e3f2e48570fe7[0.0];headcontent_59a6679e93d43c2db5b3ef7e865480dc61a63cb3[0.0]</script>
99103
<script>alert('1')</script>
100104
<style>span {color: red;}</style>
101105
<script>alert('2')</script>

tests/test_jsx_tags.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def test_jsx_tags():
1515
<html>
1616
<head>
1717
<meta charset="utf-8"/>
18+
<script type="application/html-dependencies">react[%s];react-dom[%s]</script>
1819
<script src="lib/react-%s/react.production.min.js"></script>
1920
<script src="lib/react-dom-%s/react-dom.production.min.js"></script>
2021
</head>
@@ -33,7 +34,7 @@ def test_jsx_tags():
3334
</script>
3435
</body>
3536
</html>"""
36-
% (react_ver, react_dom_ver)
37+
% (react_ver, react_dom_ver, react_ver, react_dom_ver)
3738
)
3839

3940
# Only the "top-level" tag gets wrapped in <script> tags
@@ -43,6 +44,7 @@ def test_jsx_tags():
4344
<html>
4445
<head>
4546
<meta charset="utf-8"/>
47+
<script type="application/html-dependencies">react[%s];react-dom[%s]</script>
4648
<script src="lib/react-%s/react.production.min.js"></script>
4749
<script src="lib/react-dom-%s/react-dom.production.min.js"></script>
4850
</head>
@@ -64,7 +66,7 @@ def test_jsx_tags():
6466
</script>
6567
</body>
6668
</html>"""
67-
% (react_ver, react_dom_ver)
69+
% (react_ver, react_dom_ver, react_ver, react_dom_ver)
6870
)
6971

7072
x = Foo(

tests/test_tags.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ def test_html_save():
235235
<html>
236236
<head>
237237
<meta charset="utf-8"/>
238+
<script type="application/html-dependencies">foo[1.0]</script>
238239
<link href="foo-1.0/testdep/testdep.css" rel="stylesheet"/>
239240
<script src="foo-1.0/testdep/testdep.js"></script>
240241
</head>
@@ -253,6 +254,7 @@ def test_html_save():
253254
<html lang="en">
254255
<head>
255256
<meta charset="utf-8"/>
257+
<script type="application/html-dependencies">foo[1.0]</script>
256258
<link href="lib/foo-1.0/testdep/testdep.css" rel="stylesheet"/>
257259
<script src="lib/foo-1.0/testdep/testdep.js"></script>
258260
</head>

0 commit comments

Comments
 (0)