Skip to content
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ module.exports = {
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG,
'AUTO_RELOAD': False,
'BUNDLE_DIR_NAME': 'webpack_bundles/', # must end with slash
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
Expand All @@ -101,11 +102,15 @@ WEBPACK_LOADER = {
```python
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG
'CACHE': not DEBUG,
'AUTO_RELOAD': True
}
}
```
When `CACHE` is set to True, webpack-loader will read the stats file only once and cache the result. This means web workers need to be restarted in order to pick up any changes made to the stats files.
When `CACHE` and `AUTO_RELOAD` are set to True, webpack-loader will read the stats file only once and cache the result. Wepback will watch the stats file for any changes (based on the modification time of the file). If the file is changed webpack-loader will automatically pick up any changes made to the stats files but otherwise use the cache.

When `AUTO_RELOAD` is set to False, webpack-loader will read the stats file only once and cache the result. This means web workers need to be restarted in order to pick up any changes made to the stats files.


<br>

Expand Down
97 changes: 97 additions & 0 deletions tests/app/tests/test_webpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@
WebpackLoaderTimeoutError,
WebpackBundleLookupError
)
from webpack_loader.loader import WebpackLoader
from webpack_loader.utils import get_loader

try:
import unittest.mock as mock
except ImportError:
import mock

BUNDLE_PATH = os.path.join(settings.BASE_DIR, 'assets/bundles/')
DEFAULT_CONFIG = 'DEFAULT'
Expand Down Expand Up @@ -246,3 +251,95 @@ def test_request_blocking(self):
result.rendered_content
elapsed = time.time() - then
self.assertTrue(elapsed < wait_for)

def test_no_shared_state_between_loader_instances(self):
self.compile_bundles('webpack.config.simple.js')

loader = WebpackLoader()
loader.config['CACHE'] = True

try:
assets = loader.get_assets()

self.assertIn('chunks', assets)

# A new instance of the WebpackLoader should now start with an empty
# cache
with mock.patch(
'webpack_loader.loader.open', mock.mock_open(read_data='{}')):
loader = WebpackLoader()
loader.config['CACHE'] = True
assets = loader.get_assets()
self.assertNotIn('chunks', assets)
finally:
loader.config['CACHE'] = False

def test_caching_disabled(self):
self.compile_bundles('webpack.config.simple.js')

# Don't use get_loader to prevent sharing state with other tests
loader = WebpackLoader()
assets = loader.get_assets()

self.assertIn('chunks', assets)

# Caching is disabled so another call to get_assets should trigger
# a reload of the bundle, mock open to change the contents
with mock.patch(
'webpack_loader.loader.open', mock.mock_open(read_data='{}')):
assets = loader.get_assets()
self.assertEqual(assets, {})

def test_caching_enabled(self):
self.compile_bundles('webpack.config.simple.js')

# Don't use get_loader to prevent sharing state with other tests
loader = WebpackLoader()
loader.config['CACHE'] = True
try:
assets = loader.get_assets()

self.assertIn('chunks', assets)

# Caching is enabled so another call to get_assets should not trigger
# a reload of the bundle
with mock.patch(
'webpack_loader.loader.open', mock.mock_open(read_data='{}')):
assets = loader.get_assets()
self.assertIn('chunks', assets)
finally:
loader.config['CACHE'] = False

def test_caching_enabled_with_auto_reload(self):
self.compile_bundles('webpack.config.simple.js')

# Don't use get_loader to prevent sharing state with other tests
loader = WebpackLoader()
loader.config['CACHE'] = True
loader.config['AUTO_RELOAD'] = True

try:
assets = loader.get_assets()

self.assertIn('chunks', assets)

# If file has same mtime, hit cache
with mock.patch(
'webpack_loader.loader.open', mock.mock_open(read_data='{}')):
assets = loader.get_assets()
self.assertIn('chunks', assets)

assets = loader.get_assets()

self.assertIn('chunks', assets)

# When mtime changes, auto reload
os.utime(loader.config['STATS_FILE'], None)

with mock.patch(
'webpack_loader.loader.open', mock.mock_open(read_data='{}')):
assets = loader.get_assets()
self.assertEqual(assets, {})
finally:
loader.config['CACHE'] = False
loader.config['AUTO_RELOAD'] = False
1 change: 1 addition & 0 deletions tests/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ deps =
django19: django>=1.9.0,<1.10.0
django110: django>=1.10.0,<1.11.0
django111: django>=1.11.0,<2.0
{py26,py27}: mock
commands =
coverage run --source=webpack_loader manage.py test {posargs}
1 change: 1 addition & 0 deletions webpack_loader/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DEFAULT_CONFIG = {
'DEFAULT': {
'CACHE': not settings.DEBUG,
'AUTO_RELOAD': False,
'BUNDLE_DIR_NAME': 'webpack_bundles/',
'STATS_FILE': 'webpack-stats.json',
# FIXME: Explore usage of fsnotify
Expand Down
21 changes: 20 additions & 1 deletion webpack_loader/loader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import time
from io import open

Expand All @@ -15,9 +16,10 @@


class WebpackLoader(object):
_assets = {}

def __init__(self, name='DEFAULT'):
self._assets = {}
self._mtimes = {}
self.name = name
self.config = load_config(self.name)

Expand All @@ -31,10 +33,27 @@ def _load_assets(self):
'the file and the path is correct?'.format(
self.config['STATS_FILE']))

def _reload_assets_on_change(self):
# If no entry yet in _mtimes then this is the initial load
# set mtime and leave cache untouched
if self.name not in self._mtimes:
self._mtimes[self.name] = os.path.getmtime(
self.config['STATS_FILE'])
return

mtime = os.path.getmtime(self.config['STATS_FILE'])
if self._mtimes[self.name] < mtime:
self._assets[self.name] = self._load_assets()
self._mtimes[self.name] = mtime

def get_assets(self):
if self.config['CACHE']:
if self.name not in self._assets:
self._assets[self.name] = self._load_assets()

if self.config['AUTO_RELOAD']:
self._reload_assets_on_change()

return self._assets[self.name]
return self._load_assets()

Expand Down