diff --git a/setup.cfg b/setup.cfg
index ddb7da9e..08aedd7e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,2 @@
[metadata]
-description_file = README.rst
+description_file = README.md
diff --git a/setup.py b/setup.py
index 8029f869..ca6b28ad 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
import os
import re
-from setuptools import setup
+from setuptools import find_packages, setup
def rel(*parts):
@@ -9,7 +9,7 @@ def rel(*parts):
return os.path.abspath(os.path.join(os.path.dirname(__file__), *parts))
-with open("README.md", "r") as handler:
+with open(rel("README.md"), "r", encoding="utf-8") as handler:
README = handler.read()
with open(rel("webpack_loader", "__init__.py")) as handler:
@@ -20,11 +20,7 @@ def rel(*parts):
setup(
name="django-webpack-loader",
- packages=[
- "webpack_loader",
- "webpack_loader/templatetags",
- "webpack_loader/contrib",
- ],
+ packages=find_packages(),
version=VERSION,
license="MIT License",
description="Transparently use webpack with django",
diff --git a/tests/app/tests/test_webpack.py b/tests/app/tests/test_webpack.py
index 383eefaf..4ff080ff 100644
--- a/tests/app/tests/test_webpack.py
+++ b/tests/app/tests/test_webpack.py
@@ -23,6 +23,7 @@
WebpackLoaderBadStatsError,
WebpackLoaderTimeoutError,
)
+from webpack_loader.loaders import WebpackLoader
from webpack_loader.templatetags.webpack_loader import _WARNING_MESSAGE
from webpack_loader.utils import get_as_tags, get_loader, get_as_url_to_tag_dict
@@ -158,35 +159,37 @@ def test_templatetags(self):
view = TemplateView.as_view(template_name='home.html')
request = self.factory.get('/')
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
''),
- result.rendered_content)
+ 'rel="stylesheet"/>'),
+ rendered_content)
self.assertIn((
''), result.rendered_content)
+ 'async charset="UTF-8">'), rendered_content)
self.assertIn((
''), result.rendered_content)
+ 'rel="stylesheet"/>'), rendered_content)
self.assertIn((
- ''), result.rendered_content)
+ ''), rendered_content)
self.assertIn(
- '
', result.rendered_content)
+ '
', rendered_content)
- self.assertIn('
All from getFiles already rendered', result.rendered_content)
+ self.assertIn('All from getFiles already rendered', rendered_content)
request = self.factory.get('/')
view = TemplateView.as_view(template_name='only_files.html')
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
"var contentCss = "
"'/static/django_webpack_loader_bundles/main.css'"),
- result.rendered_content)
+ rendered_content)
self.assertIn(
"var contentJS = '/static/django_webpack_loader_bundles/main.js'",
- result.rendered_content)
+ rendered_content)
self.compile_bundles('webpack.config.publicPath.js')
request = self.factory.get('/')
@@ -202,23 +205,24 @@ def test_preload(self):
view = TemplateView.as_view(template_name='preload.html')
request = self.factory.get('/')
result = view(request)
+ rendered_content = result.rendered_content
# Preload
self.assertIn((
''), result.rendered_content)
+ 'rel="preload" as="style"/>'), rendered_content)
self.assertIn((
''),
- result.rendered_content)
+ 'django_webpack_loader_bundles/main.js"/>'),
+ rendered_content)
# Resources
self.assertIn((
''), result.rendered_content)
+ 'rel="stylesheet"/>'), rendered_content)
self.assertIn((
- ''), result.rendered_content)
+ ''), rendered_content)
def test_integrity(self):
self.compile_bundles('webpack.config.integrity.js')
@@ -228,22 +232,23 @@ def test_integrity(self):
view = TemplateView.as_view(template_name='single.html')
request = self.factory.get('/')
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
''), result.rendered_content)
+ 'Pp35WlFR7ykkIafUG8cma4vbEfheH1NVbjsON5BHm8U13I4g==">'
+ ''), rendered_content)
self.assertIn((
''),
- result.rendered_content
+ 'KLQPw=="/>'),
+ rendered_content
)
def test_integrity_with_crossorigin_empty(self):
@@ -255,6 +260,7 @@ def test_integrity_with_crossorigin_empty(self):
request = self.factory.get('/')
request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com'
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
''
- ), result.rendered_content)
+ 'crossorigin>'
+ ), rendered_content)
self.assertIn((
''),
- result.rendered_content
+ 'crossorigin/>'),
+ rendered_content
)
def test_integrity_with_crossorigin_anonymous(self):
@@ -284,6 +290,7 @@ def test_integrity_with_crossorigin_anonymous(self):
request = self.factory.get('/')
request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com'
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
''
- ), result.rendered_content)
+ 'crossorigin="anonymous">'
+ ), rendered_content)
self.assertIn((
''),
- result.rendered_content
+ 'crossorigin="anonymous"/>'),
+ rendered_content
)
def test_integrity_with_crossorigin_use_credentials(self):
@@ -313,6 +320,7 @@ def test_integrity_with_crossorigin_use_credentials(self):
request = self.factory.get('/')
request.META['HTTP_HOST'] = 'crossorigin-custom-static-host.com'
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
''
- ), result.rendered_content)
+ 'crossorigin="use-credentials">'
+ ), rendered_content)
self.assertIn((
''),
- result.rendered_content
+ 'crossorigin="use-credentials"/>'),
+ rendered_content
)
def test_integrity_missing_config(self):
@@ -343,14 +351,15 @@ def test_integrity_missing_config(self):
view = TemplateView.as_view(template_name='single.html')
request = self.factory.get('/')
result = view(request)
+ rendered_content = result.rendered_content
self.assertIn((
- ''), result.rendered_content
+ ''), rendered_content
)
self.assertIn((
- ''),
- result.rendered_content
+ ''),
+ rendered_content
)
# return removed key
@@ -372,7 +381,7 @@ def test_append_extensions(self):
result = view(request)
self.assertIn((
- ''), result.rendered_content)
def test_jinja2(self):
@@ -395,12 +404,14 @@ def test_jinja2(self):
with self.settings(**settings):
request = self.factory.get('/')
result = view(request)
+ rendered_content = result.rendered_content
+
self.assertIn((
''), result.rendered_content)
+ '/main.css" rel="stylesheet"/>'), rendered_content)
self.assertIn((
''), result.rendered_content)
+ 'async charset="UTF-8">'), rendered_content)
def test_reporting_errors(self):
self.compile_bundles('webpack.config.error.js')
@@ -420,6 +431,127 @@ def test_missing_bundle(self):
'Cannot resolve bundle {0}'.format(missing_bundle_name),
str(e))
+ def test_get_bundle_uses_same_assets_snapshot_when_iterated(self):
+ class ChangingAssetsLoader(WebpackLoader):
+ def __init__(self, name, config):
+ super().__init__(name, config)
+ self.load_assets_calls = 0
+ self.assets_snapshots = [
+ {
+ 'status': 'done',
+ 'chunks': {'main': ['old.js']},
+ 'assets': {'old.js': {'name': 'old.js'}},
+ },
+ {
+ 'status': 'done',
+ 'chunks': {'main': ['new.js']},
+ 'assets': {'new.js': {'name': 'new.js'}},
+ },
+ ]
+
+ def load_assets(self):
+ snapshot_index = min(
+ self.load_assets_calls, len(self.assets_snapshots) - 1)
+ self.load_assets_calls += 1
+ return self.assets_snapshots[snapshot_index]
+
+ loader = ChangingAssetsLoader(DEFAULT_CONFIG, {
+ 'CACHE': False,
+ 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/',
+ 'TIMEOUT': None,
+ 'POLL_INTERVAL': 0.1,
+ 'ignores': [],
+ 'INTEGRITY': False,
+ })
+
+ bundle = loader.get_bundle('main')
+
+ self.assertEqual(list(bundle), [{
+ 'name': 'old.js',
+ 'url': '/static/django_webpack_loader_bundles/old.js',
+ }])
+ self.assertEqual(loader.load_assets_calls, 1)
+
+ def test_cached_compile_status_is_reloaded_while_debug_polling(self):
+ class CompilingThenDoneLoader(WebpackLoader):
+ def __init__(self, name, config):
+ super().__init__(name, config)
+ self.load_assets_calls = 0
+ self.assets_snapshots = [
+ {'status': 'compile'},
+ {
+ 'status': 'done',
+ 'chunks': {'main': ['main.js']},
+ 'assets': {'main.js': {'name': 'main.js'}},
+ },
+ ]
+
+ def load_assets(self):
+ snapshot_index = min(
+ self.load_assets_calls, len(self.assets_snapshots) - 1)
+ self.load_assets_calls += 1
+ return self.assets_snapshots[snapshot_index]
+
+ loader = CompilingThenDoneLoader('COMPILE_CACHE_DEBUG', {
+ 'CACHE': True,
+ 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/',
+ 'TIMEOUT': 1,
+ 'POLL_INTERVAL': 0,
+ 'ignores': [],
+ 'INTEGRITY': False,
+ })
+
+ with self.settings(DEBUG=True):
+ bundle = loader.get_bundle('main')
+
+ self.assertEqual(list(bundle), [{
+ 'name': 'main.js',
+ 'url': '/static/django_webpack_loader_bundles/main.js',
+ }])
+ self.assertEqual(loader.load_assets_calls, 2)
+ self.assertEqual(loader._assets[loader.name]['status'], 'done')
+
+ def test_cached_compile_status_is_reloaded_after_bad_stats(self):
+ class CompilingThenDoneLoader(WebpackLoader):
+ def __init__(self, name, config):
+ super().__init__(name, config)
+ self.load_assets_calls = 0
+ self.assets_snapshots = [
+ {'status': 'compile'},
+ {
+ 'status': 'done',
+ 'chunks': {'main': ['main.js']},
+ 'assets': {'main.js': {'name': 'main.js'}},
+ },
+ ]
+
+ def load_assets(self):
+ snapshot_index = min(
+ self.load_assets_calls, len(self.assets_snapshots) - 1)
+ self.load_assets_calls += 1
+ return self.assets_snapshots[snapshot_index]
+
+ loader = CompilingThenDoneLoader('COMPILE_CACHE_PRODUCTION', {
+ 'CACHE': True,
+ 'BUNDLE_DIR_NAME': 'django_webpack_loader_bundles/',
+ 'TIMEOUT': None,
+ 'POLL_INTERVAL': 0.1,
+ 'ignores': [],
+ 'INTEGRITY': False,
+ })
+
+ with self.settings(DEBUG=False):
+ with self.assertRaises(WebpackLoaderBadStatsError):
+ loader.get_bundle('main')
+ bundle = loader.get_bundle('main')
+
+ self.assertEqual(list(bundle), [{
+ 'name': 'main.js',
+ 'url': '/static/django_webpack_loader_bundles/main.js',
+ }])
+ self.assertEqual(loader.load_assets_calls, 2)
+ self.assertEqual(loader._assets[loader.name]['status'], 'done')
+
def test_missing_stats_file(self):
stats_file = settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE']
if os.path.exists(stats_file):
@@ -509,13 +641,13 @@ def test_emits_warning_on_no_request_in_djangoengine(self, warn_mock):
"""
self.compile_bundles('webpack.config.skipCommon.js')
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
# Shouldn't call any `warn()` here
@@ -578,13 +710,13 @@ def test_emits_warning_on_no_request_in_jinja2engine(self, warn_mock):
]
}
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
warning_call = MockCall(
message=_WARNING_MESSAGE, category=RuntimeWarning)
@@ -645,7 +777,7 @@ def test_get_files_emits_warning_on_no_request_in_djangoengine(self, warn_mock):
'{% render_bundle "app1" %}'
'{% get_files "app2" skip_common_chunks=True as app2_files %}'
'{% for f in app2_files %}'
- ' '
+ ' '
'{% endfor %}'),
) # type: Template
output = template.render(context=Context())
@@ -700,13 +832,13 @@ def _assert_common_chunks_duplicated_djangoengine(self, template):
"""
request = self.factory.get(path='/')
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
rendered_template = template.render(
context=None, request=request)
@@ -727,13 +859,13 @@ def _assert_common_chunks_not_duplicated_djangoengine(self, template):
"""
request = self.factory.get(path='/')
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
rendered_template = template.render(
context=None, request=request)
@@ -765,13 +897,13 @@ def _assert_common_chunks_duplicated_jinja2engine(self, view):
]
}
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
with self.settings(**settings):
@@ -808,13 +940,13 @@ def _assert_common_chunks_not_duplicated_jinja2engine(self, view):
]
}
asset_vendor = (
- '')
asset_app1 = (
- '')
asset_app2 = (
- '')
with self.settings(**settings):
@@ -933,16 +1065,68 @@ def test_skip_common_chunks_missing_config(self):
# return removed key
loader.config['SKIP_COMMON_CHUNKS'] = skip_common_chunks
+ def test_skip_common_chunks_get_files_then_get_files(self):
+ self.compile_bundles('webpack.config.skipCommon.js')
+ asset_vendor = '/static/django_webpack_loader_bundles/vendors.js'
+ asset_app2 = '/static/django_webpack_loader_bundles/app2.js'
+
+ template = Template(template_string=(
+ '{% load render_bundle get_files from webpack_loader %}'
+ '{% get_files "app1" skip_common_chunks=True as app1_files %}'
+ '{% for f in app1_files %}{% endfor %}'
+ '{% get_files "app2" skip_common_chunks=True as app2_files %}'
+ '{% for f in app2_files %}{% endfor %}'
+ ))
+ request = self.factory.get(path='/')
+ output = template.render(context=Context({'request': request}))
+
+ self.assertEqual(output.count(asset_vendor), 1)
+ self.assertIn(asset_app2, output)
+
+ def test_skip_common_chunks_get_files_then_render_bundle(self):
+ self.compile_bundles('webpack.config.skipCommon.js')
+ asset_vendor = '/static/django_webpack_loader_bundles/vendors.js'
+ asset_app2 = '/static/django_webpack_loader_bundles/app2.js'
+
+ template = Template(template_string=(
+ '{% load render_bundle get_files from webpack_loader %}'
+ '{% get_files "app1" skip_common_chunks=True as app1_files %}'
+ '{% for f in app1_files %}{% endfor %}'
+ '{% render_bundle "app2" skip_common_chunks=True %}'
+ ))
+ request = self.factory.get(path='/')
+ output = template.render(context=Context({'request': request}))
+
+ self.assertEqual(output.count(asset_vendor), 1)
+ self.assertIn(asset_app2, output)
+
+ def test_skip_common_chunks_render_bundle_then_get_files(self):
+ self.compile_bundles('webpack.config.skipCommon.js')
+ asset_vendor = '/static/django_webpack_loader_bundles/vendors.js'
+ asset_app2 = '/static/django_webpack_loader_bundles/app2.js'
+
+ template = Template(template_string=(
+ '{% load render_bundle get_files from webpack_loader %}'
+ '{% render_bundle "app1" skip_common_chunks=True %}'
+ '{% get_files "app2" skip_common_chunks=True as app2_files %}'
+ '{% for f in app2_files %}{% endfor %}'
+ ))
+ request = self.factory.get(path='/')
+ output = template.render(context=Context({'request': request}))
+
+ self.assertEqual(output.count(asset_vendor), 1)
+ self.assertIn(asset_app2, output)
+
def test_get_as_tags_direct_usage(self):
self.compile_bundles('webpack.config.skipCommon.js')
asset_vendor = (
- '')
asset_app1 = (
- '')
+ '')
asset_app2 = (
- '')
tags = get_as_tags('app1')
@@ -1002,6 +1186,22 @@ def test_get_url_to_tag_dict_with_nonce_disabled(self):
tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request_with_nonce)
self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/main.js'])
+ def test_get_url_to_tag_dict_js_preload_includes_integrity_and_nonce(self):
+ self.compile_bundles('webpack.config.integrity.js')
+
+ loader = get_loader(DEFAULT_CONFIG)
+ with patch.dict(loader.config, {'INTEGRITY': True, 'CSP_NONCE': True, 'CACHE': False}):
+ request = self.factory.get('/')
+ request.csp_nonce = 'test-nonce'
+
+ tag_dict = get_as_url_to_tag_dict('main', extension='js', attrs='', request=request, is_preload=True)
+ tag = tag_dict['http://custom-static-host.com/main.js']
+
+ self.assertIn('rel="preload"', tag)
+ self.assertIn('as="script"', tag)
+ self.assertIn('integrity=', tag)
+ self.assertIn('nonce="test-nonce"', tag)
+
def test_get_url_to_tag_dict_with_different_extensions(self):
"""Test the get_as_url_to_tag_dict function with different file extensions."""
diff --git a/tests_webpack5/app/tests/test_webpack.py b/tests_webpack5/app/tests/test_webpack.py
index 04bfb1f3..b7645ccf 100644
--- a/tests_webpack5/app/tests/test_webpack.py
+++ b/tests_webpack5/app/tests/test_webpack.py
@@ -46,7 +46,7 @@ def test_integrity(self):
'integrity="sha256-tq+bx/AOKBO9HvojLMT+nwLvdzX5q9s5hGI8sJr'
'V+6Q= sha384-MQ3aER73Wrl5JjMLWVotKhBZk4e9/67+xrO8/qqACm7a'
'695zI9sgQKa6bC54TMvb sha512-dKT17sF4HfpJC+UMIjQch07waKpAt'
- 'Tvv9GM2s/eGomGDbCKpKHGp29+6SRrQSrT2+6IF3YGu3BaoQAoAS4opOQ==" '
+ 'Tvv9GM2s/eGomGDbCKpKHGp29+6SRrQSrT2+6IF3YGu3BaoQAoAS4opOQ=="'
'>'), result.rendered_content)
def test_integrity_with_crossorigin_empty(self):
@@ -65,7 +65,7 @@ def test_integrity_with_crossorigin_empty(self):
'V+6Q= sha384-MQ3aER73Wrl5JjMLWVotKhBZk4e9/67+xrO8/qqACm7a'
'695zI9sgQKa6bC54TMvb sha512-dKT17sF4HfpJC+UMIjQch07waKpAt'
'Tvv9GM2s/eGomGDbCKpKHGp29+6SRrQSrT2+6IF3YGu3BaoQAoAS4opOQ==" '
- 'crossorigin >'
+ 'crossorigin>'
), result.rendered_content)
def test_integrity_with_crossorigin_anonymous(self):
@@ -84,7 +84,7 @@ def test_integrity_with_crossorigin_anonymous(self):
'V+6Q= sha384-MQ3aER73Wrl5JjMLWVotKhBZk4e9/67+xrO8/qqACm7a'
'695zI9sgQKa6bC54TMvb sha512-dKT17sF4HfpJC+UMIjQch07waKpAt'
'Tvv9GM2s/eGomGDbCKpKHGp29+6SRrQSrT2+6IF3YGu3BaoQAoAS4opOQ==" '
- 'crossorigin="anonymous" >'
+ 'crossorigin="anonymous">'
), result.rendered_content)
def test_integrity_with_crossorigin_use_credentials(self):
@@ -103,7 +103,7 @@ def test_integrity_with_crossorigin_use_credentials(self):
'V+6Q= sha384-MQ3aER73Wrl5JjMLWVotKhBZk4e9/67+xrO8/qqACm7a'
'695zI9sgQKa6bC54TMvb sha512-dKT17sF4HfpJC+UMIjQch07waKpAt'
'Tvv9GM2s/eGomGDbCKpKHGp29+6SRrQSrT2+6IF3YGu3BaoQAoAS4opOQ==" '
- 'crossorigin="use-credentials" >'
+ 'crossorigin="use-credentials">'
), result.rendered_content)
def test_get_url_to_tag_dict_with_nonce(self):
@@ -155,3 +155,19 @@ def test_get_url_to_tag_dict_with_nonce_disabled(self):
# Test with CSP_NONCE disabled - should not have nonce
tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request_with_nonce)
self.assertNotIn('nonce=', tag_dict['/static/django_webpack_loader_bundles/resources.js'])
+
+ def test_get_url_to_tag_dict_js_preload_includes_integrity_and_nonce(self):
+ self.compile_bundles('webpack.config.integrity.js')
+
+ loader = get_loader(DEFAULT_CONFIG)
+ with patch.dict(loader.config, {'INTEGRITY': True, 'CSP_NONCE': True, 'CACHE': False}):
+ request = self.factory.get('/')
+ request.csp_nonce = 'test-nonce'
+
+ tag_dict = get_as_url_to_tag_dict('resources', extension='js', attrs='', request=request, is_preload=True)
+ tag = tag_dict['http://custom-static-host.com/resources.js']
+
+ self.assertIn('rel="preload"', tag)
+ self.assertIn('as="script"', tag)
+ self.assertIn('integrity=', tag)
+ self.assertIn('nonce="test-nonce"', tag)
diff --git a/webpack_loader/loaders.py b/webpack_loader/loaders.py
index 268d52d0..27a74c88 100644
--- a/webpack_loader/loaders.py
+++ b/webpack_loader/loaders.py
@@ -66,9 +66,12 @@ def load_assets(self):
def get_assets(self):
if self.config["CACHE"]:
- if self.name not in self._assets:
- self._assets[self.name] = self.load_assets()
- return self._assets[self.name]
+ assets = self._assets.get(self.name)
+ if not assets or assets.get("status") == "compile":
+ assets = self.load_assets()
+ if assets.get("status") != "compile":
+ self._assets[self.name] = assets
+ return assets
return self.load_assets()
def get_asset_by_source_filename(self, name):
@@ -79,7 +82,7 @@ def _add_crossorigin(
self, request: Optional[HttpRequest], chunk: Dict[str, str],
integrity: str, attrs_l: str) -> str:
'Return an added `crossorigin` attribute if necessary.'
- def_value = f' integrity="{integrity}" '
+ def_value = f'integrity="{integrity}"'
if not request:
message = _CROSSORIGIN_NO_REQUEST.format(chunk_name=chunk['name'])
warn(message=message, category=RuntimeWarning)
@@ -97,15 +100,15 @@ def _add_crossorigin(
return def_value
cfgval: str = self.config.get('CROSSORIGIN')
if cfgval == '':
- return f'{def_value}crossorigin '
- return f'{def_value}crossorigin="{cfgval}" '
+ return f'{def_value} crossorigin'
+ return f'{def_value} crossorigin="{cfgval}"'
def get_integrity_attr(
self, chunk: Dict[str, str], request: Optional[HttpRequest],
- attrs_l: str) -> str:
+ attrs_l: str) -> Optional[str]:
if not self.config.get('INTEGRITY'):
# Crossorigin only necessary when integrity is used
- return ' '
+ return None
integrity = chunk.get('integrity')
if not integrity:
@@ -122,10 +125,12 @@ def get_integrity_attr(
attrs_l=attrs_l,
)
- def get_nonce_attr(self, chunk: Dict[str, str], request: Optional[HttpRequest], attrs: str) -> str:
+ def get_nonce_attr(
+ self, chunk: Dict[str, str], request: Optional[HttpRequest],
+ attrs: str) -> Optional[str]:
'Return an added nonce for CSP when available.'
if not self.config.get('CSP_NONCE'):
- return ''
+ return None
if request is None:
message = _NONCE_NO_REQUEST.format(chunk_name=chunk['name'])
warn(message=message, category=RuntimeWarning)
@@ -137,7 +142,7 @@ def get_nonce_attr(self, chunk: Dict[str, str], request: Optional[HttpRequest],
return ''
if 'nonce=' in attrs.lower():
return ''
- return f'nonce="{nonce}" '
+ return f'nonce="{nonce}"'
def filter_chunks(self, chunks):
filtered_chunks = []
@@ -149,20 +154,21 @@ def filter_chunks(self, chunks):
return filtered_chunks
- def map_chunk_files_to_url(self, chunks):
- assets = self.get_assets()
+ def map_chunk_files_to_url(self, chunks, assets=None):
+ assets = assets or self.get_assets()
files = assets["assets"]
add_integrity = self.config.get("INTEGRITY")
for chunk in chunks:
- url = self.get_chunk_url(files[chunk])
+ asset = files[chunk]
+ url = self.get_chunk_url(asset)
if add_integrity:
yield {
"name": chunk,
"url": url,
- "integrity": files[chunk].get("integrity"),
+ "integrity": asset.get("integrity"),
}
else:
yield {"name": chunk, "url": url}
@@ -219,7 +225,7 @@ def get_bundle(self, bundle_name):
"Cannot resolve asset {0}.".format(chunk)
)
- return self.map_chunk_files_to_url(filtered_chunks)
+ return self.map_chunk_files_to_url(filtered_chunks, assets=assets)
elif assets.get("status") == "error":
if "file" not in assets:
diff --git a/webpack_loader/templatetags/webpack_loader.py b/webpack_loader/templatetags/webpack_loader.py
index 5b838a7a..29dd1ffa 100644
--- a/webpack_loader/templatetags/webpack_loader.py
+++ b/webpack_loader/templatetags/webpack_loader.py
@@ -82,8 +82,10 @@ def get_files(
return result
used_urls = getattr(request, '_webpack_loader_used_urls', None)
- if not used_urls:
+ if used_urls is None:
used_urls = set()
+ setattr(request, '_webpack_loader_used_urls', used_urls)
if skip_common_chunks:
result = [chunk for chunk in result if chunk['url'] not in used_urls]
+ used_urls.update(chunk['url'] for chunk in result)
return result
diff --git a/webpack_loader/utils.py b/webpack_loader/utils.py
index e789f70b..9f833834 100644
--- a/webpack_loader/utils.py
+++ b/webpack_loader/utils.py
@@ -79,29 +79,23 @@ def get_as_url_to_tag_dict(
attrs_l = attrs.lower()
for chunk in bundle:
+ url = ''.join([chunk['url'], suffix])
+ integrity = loader.get_integrity_attr(chunk, request, attrs_l)
+ nonce = loader.get_nonce_attr(chunk, request, attrs_l)
+ extra = ' '.join(filter(bool, [integrity, nonce, attrs.strip()]))
+ attrs_str = f' {extra}' if extra else ''
+
if chunk['name'].endswith(('.js', '.js.gz')):
if is_preload:
result[chunk['url']] = (
- ''
- ).format(''.join([chunk['url'], suffix]), attrs)
- else:
- result[chunk['url']] = (
- ''
- ).format(
- ''.join([chunk['url'], suffix]),
- attrs,
- loader.get_integrity_attr(chunk, request, attrs_l),
- loader.get_nonce_attr(chunk, request, attrs_l),
+ f''
)
+ else:
+ result[chunk['url']] = f''
elif chunk['name'].endswith(('.css', '.css.gz')):
+ rel = '"stylesheet"' if not is_preload else '"preload" as="style"'
result[chunk['url']] = (
- ''
- ).format(
- ''.join([chunk['url'], suffix]),
- attrs,
- '"stylesheet"' if not is_preload else '"preload" as="style"',
- loader.get_integrity_attr(chunk, request, attrs_l),
- loader.get_nonce_attr(chunk, request, attrs_l),
+ f''
)
return result