Skip to content

Commit

Permalink
Merge pull request #11 from shonin/dev
Browse files Browse the repository at this point in the history
publish v1
  • Loading branch information
Elliot Charney authored Oct 7, 2020
2 parents d3e69ad + 62a7ed1 commit dcb5063
Show file tree
Hide file tree
Showing 28 changed files with 604 additions and 4,239 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ __pycache__
dist
node_modules
*.egg-info
build
build
!tests/dist
.coverage
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: python
install:
- pip install -e .
script:
./runtests.py
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ are permitted provided that the following conditions are met:
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of Easy Django Webpack nor the names of its contributors may
3. Neither the name of Django Manifest Loader nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.

Expand Down
163 changes: 101 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,109 +1,108 @@
# Easy Django webpack
# Django Manifest Loader

_Always have access to the latest assets, with minimal configuration. Wraps Django's built in
`{% static %}` templatetag to allow you to link to assets according to a webpack manifest file._
[![Build Status](https://img.shields.io/travis/shonin/django-manifest-loader/main?label=latest%20published%20branch&style=flat-square
)](https://travis-ci.org/shonin/django-manifest-loader)
[![Build Status](https://img.shields.io/travis/shonin/django-manifest-loader/dev?label=development%20branch&style=flat-square
)](https://travis-ci.org/shonin/django-manifest-loader)
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](#)

_Always have access to the latest webpack assets, with minimal configuration. Wraps Django's built in
`{% static %}` templatetag to allow you to link to assets according to a webpack manifest file. Handles webpack's
split chunks._

**Turns this**

```djangotemplate
{% load static %}
<script src="{% static 'main.8f7705adfa281590b8dd.js' %}"></script>
{% load manifest %}
<script src="{% manifest 'main.js' %}" />
```

**Into this**

```djangotemplate
{% load manifest %}
<script src="{% manifest 'main.js' %}"></script>
<script src="/static/main.8f7705adfa281590b8dd.js" />
```

* For an in-depth look at this package, check out [this blog post here](https://medium.com/@shonin/django-and-webpack-now-work-together-seamlessly-a90cffdbab8e)
* [Quick start guide](https://medium.com/@shonin/django-and-webpack-in-4-short-steps-b39bd3380c71)


## Installation

```shell script
pip install easy_django_webpack
pip install django-manifest-loader
```

## Setup
## Django Setup

```python
# settings.py

INSTALLED_APPS = [
...
'easy_django_webpack',
'manifest_loader', # add to installed apps
...
]
```

You must add webpack's output directory to the `STATICFILES_DIRS` list.
If your webpack configuration is to output all files into a directory `dist/` that is
in the `BASE_DIR` of your project, then you would set it like.

```python
# settings.py
STATICFILES_DIRS = [
BASE_DIR / 'dist'
BASE_DIR / 'dist' # the directory webpack outputs to
]
```

`BASE_DIR`'s default value is `BASE_DIR = Path(__file__).resolve().parent.parent`, in general
you shouldn't be modifying it. _Hint: the `BASE_DIR` is the directory your `manage.py` file is in._
You must add webpack's output directory to the `STATICFILES_DIRS` list.
The above example assumes that your webpack configuration is setup to output all files into a directory `dist/` that is
in the `BASE_DIR` of your project.

`BASE_DIR`'s default value, as set by `$ djagno-admin startproject` is `BASE_DIR = Path(__file__).resolve().parent.parent`, in general
you shouldn't be modifying it.

**Optional settings,** default values shown.
```python
# settings.py

WEBPACK_SETTINGS = {
'output_dir': BASE_DIR / 'dist', # where webpack outputs to.
MANIFEST_LOADER = {
'output_dir': None, # where webpack outputs to, if not set will search in STATICFILES_DIRS for the manifest.
'manifest_file': 'manifest.json', # name of your manifest file
'cache': False, # recommended True for production, requires a server restart to pickup new values from the manifest.
'ignore_missing_assets': False # recommended True for production. Otherwise raises an exception if a file is not in the manifest.
'ignore_missing_assets': False, # raises an exception if a file is not in the manifest.
'ignore_missing_match_tag': False, # raises an exception if the {match} string is not found in the manifest_match tag
}
```

## Webpack example

Install webpack:
## Webpack configuration

```shell script
npm i --save-dev webpack webpack-cli
```
You must install the `WebpackManifestPlugin`. Optionally, but recommended, is to install the `CleanWebpackPlugin`.

Install recommended plugins
```shell script
npm i --save-dev webpack-manifest-plugin clean-webpack-plugin
```

```javascript
// webpack.config.js

const path = require('path');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');

module.exports = {
entry: './frontend/src/index.js',
...
plugins: [
new CleanWebpackPlugin(), // removes outdated assets from the output dir
new ManifestPlugin(), // generates the required manifest.json file
],
output: {
filename: '[name].[contenthash].js', // renames files from example.js to example.8f77someHash8adfa.js
path: path.resolve(__dirname, 'dist'), // output to BASE_DIR/dist, assumes webpack.json is on the same level as manage.py
},
...
};
```

```javascript
// package.json
...
"scripts": {
"start": "webpack"
},
...
```
# Usage

Django Manifest Loader comes with two template tags that house all logic. The `manifest` tag takes a single string
input, such as `'main.js'`, looks it up against the webpack manifest, and then outputs the url to that compiled file.
It works just like Django's built it `static` tag, except it's finding the correct filename.

The `manifest_match` tag takes two arguments, a sting to pattern match filenames against, and a string to embed matched file
urls into. See the `manifest_match` section for more information.

## Usage
## Single file use (for cache busting) (`manifest` tag)

```djangotemplate
{% load manifest %}
Expand All @@ -117,12 +116,38 @@ turns into
<script src="/static/main.8f7705adfa281590b8dd.js"></script>
```

## About
Where the argument to the tag will be the original filename of a file processed by webpack. If in doubt, check your
`manifest.json` file generated by webpack to see what files are available.

The reason this is worth while is because of the content hash after the original filename, which will invalidate the
browser cache every time the file is updated. This ensures that your users always have the latest assets.

## Split chunks (`manifest_match` tag)

At it's heart `easy_django_webpack` is an extension to Django's built-in `static` templatetag.
When you use the provided `{% manifest %}` templatetag, all `easy_django_webpack` is doing is
```djangotemplate
{% load manifest %}
<script src="{% manifest_match '*.js' '<script src="{match}"/>' %}"></script>
```

turns into

```html
<script src="/static/vendors~main.3ad032adfa281590f2a21.js"/>
<script src="/static/main.8f7705adfa281590b8dd.js"/>
```
This tag takes two arguments, a pattern to match against, according to the rules of the python fnmatch package,
and a string to input the file urls into. The second argument must contain the string `{match}`, as it is what
is replaced with the urls.
# About
At it's heart Django Manifest Loader is an extension to Django's built-in `static` templatetag.
When you use the provided `{% manifest %}` templatetag, all the manifest loader is doing is
taking the input string, looking it up against the manifest file, modifying the value, and then
passing along the result to the `{% static %}` template tag.
passing along the result to the `{% static %}` template tag. The `{% manifest_match %}` tag works
similarly, just with a bit of additional logic to find all the necessary files and to render the output.
### Suggested Project Structure
Expand All @@ -149,21 +174,35 @@ BASE_DIR
└── webpack.config.js
```
### Manifest File and Content Hash (the problem this package solves)
### Cache Busting and Split Chunks (the problem this package solves)
* [What is cache busting?](https://www.keycdn.com/support/what-is-cache-busting)
* [The 100% correct way to split your chunks with Webpack](https://medium.com/hackernoon/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758)
### Tests and Code Coverage
Run unit tests and verify 100% code coverage with:
```
git clone https://github.com/shonin/django-manifest-loader.git
cd django-manifest-loader
pip install -e .
# run tests
python runtests.py
When you put a content hash in the filename of an asset file, it serves as a sort of versioning mechanism
for your assets. Every time the content changes, the hash changes. And when the hash changes, the browser sees that it
doesn't have that asset file, it drops it's
cached version of your old assets and gets the new one. If you only use the name `main.js` for your assets, the browser
will just think, oh hey I have this file in my cache, and it won't check for updates. So then your users
won't see the latest changes unless they do a browser cache refresh, which isn't something you can expect.
# check code coverage
pip install coverage
coverage run --source=manifest_loader/templatetags/ runtests.py
coverage report
```
So you can see why you want the content hash in the filename. The manifest.json file is a way to provide a mapping
from the original file name to the new one. If you didn't have a way to automate that mapping, every time you generate
a new version of your assets, you'd have to go into your HTML and update the content hash yourself. Instead, you
can just tell `easy_django_webpack` that you want the file `main.js` and it'll lookup the content hash for you.
### Contributing
Do it. Please feel free to file an issue or open a pull request. The code of conduct is basic human kindness.
### License
Easy Django Webpack is distributed under the [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause).
Django Manifest Loader is distributed under the [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause).
This is an open source license granting broad permissions to modify and redistribute the software.
5 changes: 0 additions & 5 deletions easy_django_webpack/apps.py

This file was deleted.

3 changes: 0 additions & 3 deletions easy_django_webpack/models.py

This file was deleted.

69 changes: 0 additions & 69 deletions easy_django_webpack/templatetags/manifest.py

This file was deleted.

3 changes: 0 additions & 3 deletions easy_django_webpack/tests.py

This file was deleted.

9 changes: 8 additions & 1 deletion example_project/frontend/templates/frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{% load manifest %}

<h1 id="main"></h1>
<script src="{% manifest 'maddin.js' %}"></script>

{% manifest 'main.js' %}

<!--
{% manifest_match '*.js' '<script src="{match}"></script>' %}
-->
4 changes: 1 addition & 3 deletions example_project/frontend/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from django.shortcuts import HttpResponse, render
from django.conf import settings
from django.shortcuts import render


def home(request):
return render(request, 'frontend/index.html')
# return HttpResponse(settings.WEBPACK_SETTINGS.get('output_dir'))
Loading

0 comments on commit dcb5063

Please sign in to comment.