-
Notifications
You must be signed in to change notification settings - Fork 341
Description
Summary
- Context: Configuration for
django-webpack-loaderis initialized at module load time inwebpack_loader/config.py. - Bug: The configuration settings (like
CACHE) are statically computed at import time and do not respond to dynamic changes in Django's settings (e.g., via@override_settings), leading to stale configurations and potential infinite loops. - Actual vs. expected: Changes to
settings.DEBUGorsettings.WEBPACK_LOADERshould be reflected in the loader's behavior, but they are currently ignored after the module is first imported. - Impact: When
DEBUGisTruebutCACHEis alsoTrue(due to stale configuration during a test override),get_bundleenters an infinite loop if it encounters a "compile" status in the stats file, hanging the thread or process indefinitely.
Code with bug
# webpack_loader/config.py
8: DEFAULT_CONFIG = {
9: 'DEFAULT': {
10: 'CACHE': not settings.DEBUG, # <-- BUG 🔴 [Statically computed at import time]
...
31: user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG)
32:
33: user_config = dict(
34: (name, dict(DEFAULT_CONFIG['DEFAULT'], **cfg)) # <-- BUG 🔴 [Static merge at import time]
35: for name, cfg in user_config.items()
36: )Evidence
1. Reproduction of Stale Configuration
The following script demonstrates that load_config does not reflect changes made via override_settings.
import os
import django
from django.conf import settings
from django.test import override_settings
if not settings.configured:
settings.configure(
DEBUG=True,
WEBPACK_LOADER={
"DEFAULT": {"BUNDLE_DIR_NAME": "bundles/"}
},
STATIC_URL="/static/",
)
django.setup()
from webpack_loader.config import load_config
def test_config_override():
print(f"Initial BUNDLE_DIR_NAME: {load_config('DEFAULT')['BUNDLE_DIR_NAME']}")
with override_settings(
WEBPACK_LOADER={"DEFAULT": {"BUNDLE_DIR_NAME": "changed/"}}
):
# The configuration is NOT updated
print(f"Overridden BUNDLE_DIR_NAME: {load_config('DEFAULT')['BUNDLE_DIR_NAME']}")
if __name__ == "__main__":
test_config_override()Output:
Initial BUNDLE_DIR_NAME: bundles/
Overridden BUNDLE_DIR_NAME: bundles/
2. Impact: Infinite Loop
The most severe impact occurs in loaders.py because it dynamically checks settings.DEBUG but uses the stale self.config["CACHE"]. If DEBUG is overridden to True but CACHE remains True (stale from import time), get_assets() will always return the cached assets from the first call. If those assets have a status of "compile", the polling loop in get_bundle will never exit.
# webpack_loader/loaders.py
68: if self.config["CACHE"]:
...
186: if settings.DEBUG:
...
190: while assets["status"] == "compile" and not timed_out:
...
198: assets = self.get_assets() # Returns cached assets if CACHE is TrueWhy has this bug gone undetected?
Most production environments use static settings. This bug primarily affects developers during testing or when using dynamic settings overrides. The project's own test suite avoids this by directly patching the internal loader.config dictionary instead of using override_settings.
Recommended fix
The configuration logic in webpack_loader/config.py should be moved into the load_config function or a similar lazy-loading mechanism that retrieves values from django.conf.settings when called.
# Recommended dynamic approach in webpack_loader/config.py
def load_config(name):
# Fetch settings dynamically // <-- FIX 🟢
user_config = getattr(settings, 'WEBPACK_LOADER', DEFAULT_CONFIG)
# ... perform merge logic ...
return merged_configHistory
This bug was introduced in commit 64971f4 (@hugobessa, 2025-05-12, PR #413). Although the static configuration loading pattern existed since the project's early days, it was briefly fixed and made dynamic in commit 6ac6d58, only to be immediately reverted to the buggy static implementation in 64971f4 as part of a series of changes for Webpack 5 support.