Skip to content

Commit 152414c

Browse files
rhyneavowais
authored andcommitted
Add custom LOADER_CLASS support (django-webpack#210)
`LOADER_CLASS` on the `WEBPACK_CONFIG` setting is now where the loader class is defined. To retain backward compatibility and to keep getting started simple, this defaults to the existing WebpackLoader class. `WebpackLoader._load_assets` has been renamed to `WebpackLoader.load_assets`. This keeps the API extendable when creating custom webpack loaders. Documentation has been updated to include how to extend the WebpackLoader using the `LOADER_CLASS`.
1 parent 8c3c370 commit 152414c

File tree

6 files changed

+127
-12
lines changed

6 files changed

+127
-12
lines changed

README.md

+36-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ WEBPACK_LOADER = {
9090
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
9191
'POLL_INTERVAL': 0.1,
9292
'TIMEOUT': None,
93-
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
93+
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
94+
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
9495
}
9596
}
9697
```
@@ -168,6 +169,40 @@ and your webpack config is located at `/home/src/webpack.config.js`, then the va
168169
169170
<br>
170171
172+
#### LOADER_CLASS
173+
174+
`LOADER_CLASS` is the fully qualified name of a python class as a string that holds the custom webpack loader.
175+
This is where behavior can be customized as to how the stats file is loaded. Examples include loading the stats file
176+
from a database, cache, external url, etc. For convenience, `webpack_loader.loader.WebpackLoader` can be extended;
177+
The `load_assets` method is likely where custom behavior will be added. This should return the stats file as an object.
178+
179+
Here's a simple example of loading from an external url:
180+
181+
```py
182+
# in app.module
183+
import requests
184+
from webpack_loader.loader import WebpackLoader
185+
186+
class ExternalWebpackLoader(WebpackLoader):
187+
188+
def load_assets(self):
189+
url = self.config['STATS_URL']
190+
return requests.get(url).json()
191+
192+
193+
# in app.settings
194+
WEBPACK_LOADER = {
195+
'DEFAULT': {
196+
'CACHE': False,
197+
'BUNDLE_DIR_NAME': 'bundles/',
198+
'LOADER_CLASS': 'app.module.ExternalWebpackLoader',
199+
# Custom config setting made available in WebpackLoader's self.config
200+
'STATS_URL': 'https://www.test.com/path/to/stats/',
201+
}
202+
}
203+
```
204+
205+
<br>
171206
172207
## Usage
173208
<br>
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from imp import reload
2+
from django.test import TestCase
3+
from webpack_loader import utils, config, loader
4+
5+
6+
DEFAULT_CONFIG = 'DEFAULT'
7+
LOADER_PAYLOAD = {'status': 'done', 'chunks': []}
8+
9+
10+
class ValidCustomLoader(loader.WebpackLoader):
11+
12+
def load_assets(self):
13+
return LOADER_PAYLOAD
14+
15+
16+
class CustomLoadersTestCase(TestCase):
17+
def tearDown(self):
18+
self.reload_webpack()
19+
20+
def reload_webpack(self):
21+
'''
22+
Reloads webpack loader modules that have cached values to avoid polluting certain tests
23+
'''
24+
reload(utils)
25+
reload(config)
26+
27+
def test_bad_custom_loader(self):
28+
'''
29+
Tests that a bad custom loader path will raise an error
30+
'''
31+
loader_class = 'app.tests.bad_loader_path.BadCustomLoader'
32+
with self.settings(WEBPACK_LOADER={
33+
'DEFAULT': {
34+
'CACHE': False,
35+
'BUNDLE_DIR_NAME': 'bundles/',
36+
'LOADER_CLASS': loader_class
37+
}
38+
}):
39+
self.reload_webpack()
40+
try:
41+
loader = utils.get_loader(DEFAULT_CONFIG)
42+
self.fail('The loader should fail to load with a bad LOADER_CLASS')
43+
except ImportError as e:
44+
self.assertIn(
45+
'{} doesn\'t look like a valid module path'.format(loader_class),
46+
str(e)
47+
)
48+
49+
def test_good_custom_loader(self):
50+
'''
51+
Tests that a good custom loader will return the correct assets
52+
'''
53+
loader_class = 'app.tests.test_custom_loaders.ValidCustomLoader'
54+
with self.settings(WEBPACK_LOADER={
55+
'DEFAULT': {
56+
'CACHE': False,
57+
'BUNDLE_DIR_NAME': 'bundles/',
58+
'LOADER_CLASS': loader_class,
59+
}
60+
}):
61+
self.reload_webpack()
62+
assets = utils.get_loader(DEFAULT_CONFIG).load_assets()
63+
self.assertEqual(assets, LOADER_PAYLOAD)

webpack_loader/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
__author__ = 'Owais Lone'
2-
__version__ = '0.6.0'
2+
__version__ = '0.7.0'
33

44
default_app_config = 'webpack_loader.apps.WebpackLoaderConfig'

webpack_loader/config.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
# FIXME: Explore usage of fsnotify
1515
'POLL_INTERVAL': 0.1,
1616
'TIMEOUT': None,
17-
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
17+
'IGNORE': ['.+\.hot-update.js', '.+\.map'],
18+
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
1819
}
1920
}
2021

webpack_loader/loader.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,16 @@
1111
WebpackLoaderTimeoutError,
1212
WebpackBundleLookupError
1313
)
14-
from .config import load_config
1514

1615

1716
class WebpackLoader(object):
1817
_assets = {}
1918

20-
def __init__(self, name='DEFAULT'):
19+
def __init__(self, name, config):
2120
self.name = name
22-
self.config = load_config(self.name)
21+
self.config = config
2322

24-
def _load_assets(self):
23+
def load_assets(self):
2524
try:
2625
with open(self.config['STATS_FILE'], encoding="utf-8") as f:
2726
return json.load(f)
@@ -34,9 +33,9 @@ def _load_assets(self):
3433
def get_assets(self):
3534
if self.config['CACHE']:
3635
if self.name not in self._assets:
37-
self._assets[self.name] = self._load_assets()
36+
self._assets[self.name] = self.load_assets()
3837
return self._assets[self.name]
39-
return self._load_assets()
38+
return self.load_assets()
4039

4140
def filter_chunks(self, chunks):
4241
for chunk in chunks:

webpack_loader/utils.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1+
from importlib import import_module
12
from django.conf import settings
2-
3-
from .loader import WebpackLoader
3+
from .config import load_config
44

55

66
_loaders = {}
77

88

9+
def import_string(dotted_path):
10+
'''
11+
This is a rough copy of django's import_string, which wasn't introduced until Django 1.7
12+
13+
Once this package's support for Django 1.6 has been removed, this can be safely replaced with
14+
`from django.utils.module_loading import import_string`
15+
'''
16+
try:
17+
module_path, class_name = dotted_path.rsplit('.', 1)
18+
module = import_module(module_path)
19+
return getattr(module, class_name)
20+
except (ValueError, AttributeError, ImportError):
21+
raise ImportError('%s doesn\'t look like a valid module path' % dotted_path)
22+
23+
924
def get_loader(config_name):
1025
if config_name not in _loaders:
11-
_loaders[config_name] = WebpackLoader(config_name)
26+
config = load_config(config_name)
27+
loader_class = import_string(config['LOADER_CLASS'])
28+
_loaders[config_name] = loader_class(config_name, config)
1229
return _loaders[config_name]
1330

1431

0 commit comments

Comments
 (0)