django-webpack-loader
Use webpack to generate your static bundles without django's staticfiles or opaque wrappers.
Django webpack loader consumes the output generated by webpack-bundle-tracker and lets you use the generated bundles in django.
A changelog is also available.
Compatibility
Test cases cover Django>=2.0 on Python>=3.5. 100% code coverage is the target so we can be sure everything works anytime. It should probably work on older version of django as well but the package does not ship any test cases for them.
Install
npm install --save-dev webpack-bundle-tracker
pip install django-webpack-loader
Configuration
webpack-bundle-tracker
Configuring Before configuring django-webpack-loader
, let's first configure what's necessary on webpack-bundle-tracker
side. Update your Webpack configuration file (it's usually on webpack.config.js
in the project root). Make sure your file looks like this (adapt to your needs):
const path = require('path');
const webpack = require('webpack');
const BundleTracker = require('webpack-bundle-tracker');
module.exports = {
context: __dirname,
entry: './assets/js/index',
output: {
path: path.resolve('./assets/webpack_bundles/'),
filename: "[name]-[hash].js"
},
plugins: [
new BundleTracker({filename: './webpack-stats.json'})
],
}
The configuration above expects the index.js
(the app entrypoint file) to live inside the /assets/js/
directory (this guide going forward will assume that all front-end related files are placed inside the /assets/
directory, with the different kinds of files arranged within its subdirectories).
The generated compiled files will be placed inside the /assets/webpack_bundles/
directory and the file with the information regarding the bundles and assets (webpack-stats.json
) will be stored in the project root.
Compiling the front-end assets
You must generate the front-end bundle using webpack-bundle-tracker
before using django-webpack-loader
. You can compile the assets and generate the bundles by running:
npx webpack --config webpack.config.js --watch
This will also generate the stats file. You can also refer to how django-react-boilerplate
configure the package.json scripts for different situations.
⚠️ Hot reload is available through a specific config. Check this section.
⚠️ This is the recommended usage for the development environment. For usage in production, please refer to this section
Configuring the settings file
First of all, add webpack_loader
to INSTALLED_APPS
.
INSTALLED_APPS = (
...
'webpack_loader',
...
)
Below is the recommended setup for the Django settings file when using django-webpack-loader
.
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG,
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
}
}
For that setup, we're using the DEBUG
variable provided by Django. Since in a production environment (DEBUG = False
) the assets files won't constantly change, we can safely cache the results (CACHE=True
) and optimize our flow, as django-webpack-loader
will read the stats file only once and store the assets files paths in memory. From that point onwards, it will use these stored paths as the source of truth. If CACHE=False
, we'll always read the stats file to get the assets paths.
⚠️ IfCACHE=True
, any changes made in the assets files will only be read when the web workers are restarted.
During development, when the stats file changes a lot, we want to always poll for its updated version (in our case, we'll fetch it every 0.1s, as defined on POLL_INTERVAL
).
⚠️ In production (DEBUG=False
), we'll only fetch the stats file once, soPOLL_INTERVAL
is ignored.
While CACHE
isn't directly related to POLL_INTERVAL
, it's interesting to keep CACHE
binded to the DEBUG
logic value (in this case, the negation of the logic value) in order to only cache the assets in production, as we'd not continuously poll the stats file in that environment.
The STATS_FILE
parameter represents the output file produced by webpack-bundle-tracker
. Since in the Webpack configuration file we've named it webpack-stats.json
and stored it on the project root, we must replicate that setting on the back-end side.
IGNORE
is a list of regular expressions. If a file generated by Webpack matches one of the expressions, the file will not be included in the template.
Extra settings
-
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 -
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. Theload_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:
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()
Rendering
In order to render the front-end code into the Django templates, we use the render_bundle
template tag.
Its behavior is to accept a string with the name of an entrypoint from the stats file (in our case, we're using main
, which is the default) and it'll proceed to include all files under that entrypoint. You can read more about the entrypoints concept here.
⚠️ You can also check an example on how to use multipleentry
values here.
Below is the basic usage for render_bundle
within a template:
{% load render_bundle from webpack_loader %}
{% render_bundle 'main' %}
That will render the proper and
tags needed in your template.
Running in development
For django-webpack-loader
to work, you must run the webpack pipeline. Please refer to this section.
In summary, you should do the following:
# in one shell
npx webpack --config webpack.config.js --watch
# in another shell
python manage.py runserver
⚠️ You can also check this example on how to run a project withdjango-webpack-loader
andwebpack-bundle-track
.
Usage in production
We recommend that you keep your local bundles and the stats file outside the version control, having a production pipeline that will compile and collect the assets during the deployment phase.
You must add STATICFILES_DIRS
to your settings file, pointing to the directory where the static files are located. This will let collectstatic
know where it should look at:
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'assets'),
)
Below are the commands that should be run to compile and collect the static files (please note this may change from platform to platform):
npm run build
python manage.py collectstatic --noinput
First we build the assets and, since we have webpack-bundle-tracker
in our front-end building pipeline, the stats file will be populated. Then, we manually run collecstatic to collect the compiled assets.
⚠️ Heroku is one platform that automatically runs collectstatic for you, so you need to setDISABLE_COLLECTSTATIC=1
environment var. Instead, you must manually run collectstatic after running webpack. In Heroku, this is achieved with apost_compile
hook. You can see an example on how to implement this flow on django-react-boilerplate.
However, production usage for this package is fairly flexible. Other approaches may include keeping the production bundles in the version control and take that responsibility from the automatic pipeline. However, you must remember to always build the frontend and generate the bundle before pushing to remote.
Usage in tests
There are 2 approaches for when render_bundle
shows up in tests, since we don't have webpack-bundle-tracker
at that point to generate the stats file.
-
The first approach is to have specific settings for them (which is how we approach on our tests), such as done here. Please note that it's necessary to have a pre-made stats file for the tests (which in general can be empty, such as here).
-
The second approach is to leverage
LOADER_CLASS
overriding for the test settings and customize theget_bundle
method to return the url of a stats file. Note that, using this approach, the stats file doesn't have to exist.
Advanced Usage
Rendering by file extension
render_bundle
also takes a second argument which can be a file extension to match. This is useful when you want to render different types for files in separately. For example, to render CSS in head and JS at bottom we can do something like this,
{% load render_bundle from webpack_loader %}
<html>
<head>
{% render_bundle 'main' 'css' %}
head>
<body>
....
{% render_bundle 'main' 'js' %}
body>
head>
Using preload
The is_preload=True
option in the render_bundle
template tag can be used to add rel="preload"
link tags.
{% load render_bundle from webpack_loader %}
<html>
<head>
{% render_bundle 'main' 'css' is_preload=True %}
{% render_bundle 'main' 'js' is_preload=True %}
{% render_bundle 'main' 'css' %}
head>
<body>
{% render_bundle 'main' 'js' %}
body>
html>
Accessing other webpack assets
webpack_static
template tag provides facilities to load static assets managed by webpack in Django templates. It is like Django's built in static
tag but for webpack assets instead.
In the below example, logo.png
can be any static asset shipped with any npm package.
{% load webpack_static from webpack_loader %} <img src="{% webpack_static 'logo.png' %}"/>