Add custom LOADER_CLASS support (#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`.
This commit is contained in:
parent
8c3c370838
commit
152414cc26
6 changed files with 127 additions and 12 deletions
37
README.md
37
README.md
|
@ -90,7 +90,8 @@ WEBPACK_LOADER = {
|
|||
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
|
||||
'POLL_INTERVAL': 0.1,
|
||||
'TIMEOUT': None,
|
||||
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
|
||||
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
|
||||
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -168,6 +169,40 @@ and your webpack config is located at `/home/src/webpack.config.js`, then the va
|
|||
|
||||
<br>
|
||||
|
||||
#### LOADER_CLASS
|
||||
|
||||
`LOADER_CLASS` is the fully qualified name of a python class as a string that holds the custom webpack loader.
|
||||
This is where behavior can be customized as to how the stats file is loaded. Examples include loading the stats file
|
||||
from a database, cache, external url, etc. For convenience, `webpack_loader.loader.WebpackLoader` can be extended;
|
||||
The `load_assets` method is likely where custom behavior will be added. This should return the stats file as an object.
|
||||
|
||||
Here's a simple example of loading from an external url:
|
||||
|
||||
```py
|
||||
# in app.module
|
||||
import requests
|
||||
from webpack_loader.loader import WebpackLoader
|
||||
|
||||
class ExternalWebpackLoader(WebpackLoader):
|
||||
|
||||
def load_assets(self):
|
||||
url = self.config['STATS_URL']
|
||||
return requests.get(url).json()
|
||||
|
||||
|
||||
# in app.settings
|
||||
WEBPACK_LOADER = {
|
||||
'DEFAULT': {
|
||||
'CACHE': False,
|
||||
'BUNDLE_DIR_NAME': 'bundles/',
|
||||
'LOADER_CLASS': 'app.module.ExternalWebpackLoader',
|
||||
# Custom config setting made available in WebpackLoader's self.config
|
||||
'STATS_URL': 'https://www.test.com/path/to/stats/',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Usage
|
||||
<br>
|
||||
|
|
63
tests/app/tests/test_custom_loaders.py
Normal file
63
tests/app/tests/test_custom_loaders.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from imp import reload
|
||||
from django.test import TestCase
|
||||
from webpack_loader import utils, config, loader
|
||||
|
||||
|
||||
DEFAULT_CONFIG = 'DEFAULT'
|
||||
LOADER_PAYLOAD = {'status': 'done', 'chunks': []}
|
||||
|
||||
|
||||
class ValidCustomLoader(loader.WebpackLoader):
|
||||
|
||||
def load_assets(self):
|
||||
return LOADER_PAYLOAD
|
||||
|
||||
|
||||
class CustomLoadersTestCase(TestCase):
|
||||
def tearDown(self):
|
||||
self.reload_webpack()
|
||||
|
||||
def reload_webpack(self):
|
||||
'''
|
||||
Reloads webpack loader modules that have cached values to avoid polluting certain tests
|
||||
'''
|
||||
reload(utils)
|
||||
reload(config)
|
||||
|
||||
def test_bad_custom_loader(self):
|
||||
'''
|
||||
Tests that a bad custom loader path will raise an error
|
||||
'''
|
||||
loader_class = 'app.tests.bad_loader_path.BadCustomLoader'
|
||||
with self.settings(WEBPACK_LOADER={
|
||||
'DEFAULT': {
|
||||
'CACHE': False,
|
||||
'BUNDLE_DIR_NAME': 'bundles/',
|
||||
'LOADER_CLASS': loader_class
|
||||
}
|
||||
}):
|
||||
self.reload_webpack()
|
||||
try:
|
||||
loader = utils.get_loader(DEFAULT_CONFIG)
|
||||
self.fail('The loader should fail to load with a bad LOADER_CLASS')
|
||||
except ImportError as e:
|
||||
self.assertIn(
|
||||
'{} doesn\'t look like a valid module path'.format(loader_class),
|
||||
str(e)
|
||||
)
|
||||
|
||||
def test_good_custom_loader(self):
|
||||
'''
|
||||
Tests that a good custom loader will return the correct assets
|
||||
'''
|
||||
loader_class = 'app.tests.test_custom_loaders.ValidCustomLoader'
|
||||
with self.settings(WEBPACK_LOADER={
|
||||
'DEFAULT': {
|
||||
'CACHE': False,
|
||||
'BUNDLE_DIR_NAME': 'bundles/',
|
||||
'LOADER_CLASS': loader_class,
|
||||
}
|
||||
}):
|
||||
self.reload_webpack()
|
||||
assets = utils.get_loader(DEFAULT_CONFIG).load_assets()
|
||||
self.assertEqual(assets, LOADER_PAYLOAD)
|
|
@ -1,4 +1,4 @@
|
|||
__author__ = 'Owais Lone'
|
||||
__version__ = '0.6.0'
|
||||
__version__ = '0.7.0'
|
||||
|
||||
default_app_config = 'webpack_loader.apps.WebpackLoaderConfig'
|
||||
|
|
|
@ -14,7 +14,8 @@ DEFAULT_CONFIG = {
|
|||
# FIXME: Explore usage of fsnotify
|
||||
'POLL_INTERVAL': 0.1,
|
||||
'TIMEOUT': None,
|
||||
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']
|
||||
'IGNORE': ['.+\.hot-update.js', '.+\.map'],
|
||||
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,17 +11,16 @@ from .exceptions import (
|
|||
WebpackLoaderTimeoutError,
|
||||
WebpackBundleLookupError
|
||||
)
|
||||
from .config import load_config
|
||||
|
||||
|
||||
class WebpackLoader(object):
|
||||
_assets = {}
|
||||
|
||||
def __init__(self, name='DEFAULT'):
|
||||
def __init__(self, name, config):
|
||||
self.name = name
|
||||
self.config = load_config(self.name)
|
||||
self.config = config
|
||||
|
||||
def _load_assets(self):
|
||||
def load_assets(self):
|
||||
try:
|
||||
with open(self.config['STATS_FILE'], encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
@ -34,9 +33,9 @@ class WebpackLoader(object):
|
|||
def get_assets(self):
|
||||
if self.config['CACHE']:
|
||||
if self.name not in self._assets:
|
||||
self._assets[self.name] = self._load_assets()
|
||||
self._assets[self.name] = self.load_assets()
|
||||
return self._assets[self.name]
|
||||
return self._load_assets()
|
||||
return self.load_assets()
|
||||
|
||||
def filter_chunks(self, chunks):
|
||||
for chunk in chunks:
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
from importlib import import_module
|
||||
from django.conf import settings
|
||||
|
||||
from .loader import WebpackLoader
|
||||
from .config import load_config
|
||||
|
||||
|
||||
_loaders = {}
|
||||
|
||||
|
||||
def import_string(dotted_path):
|
||||
'''
|
||||
This is a rough copy of django's import_string, which wasn't introduced until Django 1.7
|
||||
|
||||
Once this package's support for Django 1.6 has been removed, this can be safely replaced with
|
||||
`from django.utils.module_loading import import_string`
|
||||
'''
|
||||
try:
|
||||
module_path, class_name = dotted_path.rsplit('.', 1)
|
||||
module = import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
except (ValueError, AttributeError, ImportError):
|
||||
raise ImportError('%s doesn\'t look like a valid module path' % dotted_path)
|
||||
|
||||
|
||||
def get_loader(config_name):
|
||||
if config_name not in _loaders:
|
||||
_loaders[config_name] = WebpackLoader(config_name)
|
||||
config = load_config(config_name)
|
||||
loader_class = import_string(config['LOADER_CLASS'])
|
||||
_loaders[config_name] = loader_class(config_name, config)
|
||||
return _loaders[config_name]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue