Summary
- Context: The
django-webpack-loader template tags allow users to include assets from Webpack bundles in their Django templates, with an option to skip common chunks to avoid duplication.
- Bug: The
get_files template tag fails to track and skip already rendered common chunks when skip_common_chunks=True is used, because it does not update the _webpack_loader_used_urls attribute on the request object.
- Actual vs. expected:
get_files correctly filters against existing URLs in _webpack_loader_used_urls but fails to add its own returned URLs to this set, leading to subsequent tags (like render_bundle or another get_files) rendering the same assets again.
- Impact: Duplicate assets (especially common vendor chunks) are loaded multiple times in the browser, increasing page size and potentially causing side effects in JavaScript execution.
Code with bug
@register.simple_tag(takes_context=True)
def get_files(
context, bundle_name, extension=None, config='DEFAULT',
skip_common_chunks=None):
# ... (omitted setup)
used_urls = getattr(request, '_webpack_loader_used_urls', None)
if not used_urls:
used_urls = set()
if skip_common_chunks:
result = [chunk for chunk in result if chunk['url'] not in used_urls]
return result # <-- BUG 🔴 [The used_urls set is never updated or attached to the request if it was None]
Evidence
Reproduction Script
A reproduction script shows that calling get_files twice with skip_common_chunks=True returns the same files both times, and a subsequent render_bundle also includes them.
# First call
files1 = get_files(context, 'main', skip_common_chunks=True)
# Result: ['/static/bundles/common.js', '/static/bundles/main.js']
# Second call
files2 = get_files(context, 'main', skip_common_chunks=True)
# Result: ['/static/bundles/common.js', '/static/bundles/main.js']
# EXPECTED: []
Failing Test Case
A new test case in the project's test suite confirms the failure:
def test_get_files_deduplication(self):
request = self.factory.get('/')
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 %}<script src="{{ f.url }}"></script>{% endfor %}'
'{% get_files "app2" skip_common_chunks=True as app2_files %}'
'{% for f in app2_files %}<script src="{{ f.url }}"></script>{% endfor %}'
))
output = template.render(context=Context({'request': request}))
asset_vendor = '/static/django_webpack_loader_bundles/vendors.js'
self.assertEqual(output.count(asset_vendor), 1) # Fails: returns 2
Why has this bug gone undetected?
get_files is less commonly used than render_bundle, and it's often used for prefetching or in scenarios where duplication might not be immediately obvious (e.g., hidden in a list of link tags). Developers using get_files might expect it to only return the files, and may not realize it needs to participate in the global deduplication state managed by render_bundle.
Recommended fix
Update get_files to match the behavior of render_bundle by ensuring used_urls is attached to the request and updated with the returned URLs.
used_urls = getattr(request, '_webpack_loader_used_urls', None)
if used_urls is None: # <-- FIX 🟢
used_urls = set()
setattr(request, '_webpack_loader_used_urls', used_urls) # <-- FIX 🟢
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) # <-- FIX 🟢
return result
History
This bug was introduced in commit 1792e80 (@karolyi, 2023-12-29, PR #386). This commit added the skip_common_chunks option to the get_files template tag but failed to include an update to the shared used_urls set, meaning files returned by this tag were never marked as rendered for subsequent tags.
Summary
django-webpack-loadertemplate tags allow users to include assets from Webpack bundles in their Django templates, with an option to skip common chunks to avoid duplication.get_filestemplate tag fails to track and skip already rendered common chunks whenskip_common_chunks=Trueis used, because it does not update the_webpack_loader_used_urlsattribute on the request object.get_filescorrectly filters against existing URLs in_webpack_loader_used_urlsbut fails to add its own returned URLs to this set, leading to subsequent tags (likerender_bundleor anotherget_files) rendering the same assets again.Code with bug
Evidence
Reproduction Script
A reproduction script shows that calling
get_filestwice withskip_common_chunks=Truereturns the same files both times, and a subsequentrender_bundlealso includes them.Failing Test Case
A new test case in the project's test suite confirms the failure:
Why has this bug gone undetected?
get_filesis less commonly used thanrender_bundle, and it's often used for prefetching or in scenarios where duplication might not be immediately obvious (e.g., hidden in a list oflinktags). Developers usingget_filesmight expect it to only return the files, and may not realize it needs to participate in the global deduplication state managed byrender_bundle.Recommended fix
Update
get_filesto match the behavior ofrender_bundleby ensuringused_urlsis attached to the request and updated with the returned URLs.History
This bug was introduced in commit 1792e80 (@karolyi, 2023-12-29, PR #386). This commit added the
skip_common_chunksoption to theget_filestemplate tag but failed to include an update to the sharedused_urlsset, meaning files returned by this tag were never marked as rendered for subsequent tags.