diff --git a/.gitignore b/.gitignore index 71b0596f..8cd66792 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +venv/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e29f651d --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: clean build deploy install publish + +# Project settings +PROJECT = webpack-loader + +# Virtual environment settings +ENV ?= venv + +requirements = -r requirements-dev.txt + +# List directories +dist_dir = dist +clean_dirs = $(PROJECT) $(ENV) $(tests_dir) $(shell [ -d $(tox_dir) ] && echo $(tox_dir) || :) + +all: install build + +clean: + find webpack_loader/ -name '*.pyc' -delete + rm -rf ./build ./*egg* ./.coverage + +build: clean + python setup.py sdist bdist_wheel --universal + +install: + [ ! -d $(ENV)/ ] && virtualenv $(ENV)/ || : + $(ENV)/bin/pip install $(requirements) + +publish: build + $(ENV)/bin/twine upload dist/* diff --git a/README.md b/README.md index 6fe029c2..4f2c1cff 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ WEBPACK_LOADER = { 'BUNDLE_DIR_NAME': 'bundles/', # must end with slash 'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'), 'POLL_INTERVAL': 0.1, + 'TIMEOUT': None, 'IGNORE': ['.+\.hot-update.js', '.+\.map'] } } @@ -147,12 +148,18 @@ and your webpack config is located at `/home/src/webpack.config.js`, then the va #### POLL_INTERVAL -`POLL_INTERVAL` is the number of seconds webpack_loader should wait between polling the stats file. The stats file is polled every 200 miliseconds by default and any requests to are blocked while webpack compiles the bundles. You can reduce this if your bundles take shorter to compile. +`POLL_INTERVAL` is the number of seconds webpack_loader should wait between polling the stats file. The stats file is polled every 100 miliseconds by default and any requests to are blocked while webpack compiles the bundles. You can reduce this if your bundles take shorter to compile. **NOTE:** Stats file is not polled when in production (DEBUG=False).
+#### TIMEOUT + +`TIMEOUT` is the number of seconds webpack_loader should wait for webpack to finish compiling before raising an exception. `0`, `None` or leaving the value out of settings disables timeouts. + +
+ ## Usage
diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..b314c8c1 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +twine==1.7.4 diff --git a/tests/app/tests/test_webpack.py b/tests/app/tests/test_webpack.py index 0b6a85f6..337de78f 100644 --- a/tests/app/tests/test_webpack.py +++ b/tests/app/tests/test_webpack.py @@ -12,7 +12,8 @@ from unittest2 import skipIf from webpack_loader.exceptions import ( WebpackError, - WebpackLoaderBadStatsError + WebpackLoaderBadStatsError, + WebpackLoaderTimeoutError ) from webpack_loader.utils import get_loader @@ -146,7 +147,6 @@ def test_jinja2(self): self.assertIn('', result.rendered_content) def test_reporting_errors(self): - #TODO: self.compile_bundles('webpack.config.error.js') try: get_loader(DEFAULT_CONFIG).get_bundle('main') @@ -166,6 +166,17 @@ def test_missing_stats_file(self): ).format(stats_file) self.assertIn(expected, str(e)) + def test_timeouts(self): + with self.settings(DEBUG=True): + with open( + settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE'], 'w' + ) as stats_file: + stats_file.write(json.dumps({'status': 'compiling'})) + loader = get_loader(DEFAULT_CONFIG) + loader.config['TIMEOUT'] = 0.1 + with self.assertRaises(WebpackLoaderTimeoutError): + loader.get_bundle('main') + def test_bad_status_in_production(self): with open( settings.WEBPACK_LOADER[DEFAULT_CONFIG]['STATS_FILE'], 'w' diff --git a/tests/db.sqlite3 b/tests/db.sqlite3 index 755e4629..61d68fda 100644 Binary files a/tests/db.sqlite3 and b/tests/db.sqlite3 differ diff --git a/webpack_loader/__init__.py b/webpack_loader/__init__.py index 7ff2855d..12b6a3c7 100644 --- a/webpack_loader/__init__.py +++ b/webpack_loader/__init__.py @@ -1,4 +1,4 @@ __author__ = 'Owais Lone' -__version__ = '0.3.2' +__version__ = '0.3.3' default_app_config = 'webpack_loader.apps.WebpackLoaderConfig' diff --git a/webpack_loader/config.py b/webpack_loader/config.py index 3bb26cae..39ff2b1a 100644 --- a/webpack_loader/config.py +++ b/webpack_loader/config.py @@ -13,6 +13,7 @@ 'STATS_FILE': 'webpack-stats.json', # FIXME: Explore usage of fsnotify 'POLL_INTERVAL': 0.1, + 'TIMEOUT': None, 'IGNORE': ['.+\.hot-update.js', '.+\.map'] } } diff --git a/webpack_loader/exceptions.py b/webpack_loader/exceptions.py index c7a5165a..307b0887 100644 --- a/webpack_loader/exceptions.py +++ b/webpack_loader/exceptions.py @@ -7,3 +7,7 @@ class WebpackError(Exception): class WebpackLoaderBadStatsError(Exception): pass + + +class WebpackLoaderTimeoutError(Exception): + pass diff --git a/webpack_loader/loader.py b/webpack_loader/loader.py index 869613e2..4fd106f4 100644 --- a/webpack_loader/loader.py +++ b/webpack_loader/loader.py @@ -4,7 +4,11 @@ from django.conf import settings from django.contrib.staticfiles.storage import staticfiles_storage -from .exceptions import WebpackError, WebpackLoaderBadStatsError +from .exceptions import ( + WebpackError, + WebpackLoaderBadStatsError, + WebpackLoaderTimeoutError +) from .config import load_config @@ -53,13 +57,24 @@ def get_chunk_url(self, chunk): def get_bundle(self, bundle_name): assets = self.get_assets() + # poll when debugging and block request until bundle is compiled + # or the build times out if settings.DEBUG: - # poll when debugging and block request until bundle is compiled - # TODO: support timeouts - while assets['status'] == 'compiling': + timeout = self.config['TIMEOUT'] or 0 + timed_out = False + start = time.time() + while assets['status'] == 'compiling' and not timed_out: time.sleep(self.config['POLL_INTERVAL']) + if timeout and (time.time() - timeout > start): + timed_out = True assets = self.get_assets() + if timed_out: + raise WebpackLoaderTimeoutError( + "Timed Out. Bundle `{0}` took more than {1} seconds " + "to compile.".format(bundle_name, timeout) + ) + if assets.get('status') == 'done': chunks = assets['chunks'][bundle_name] return self.filter_chunks(chunks)