Skip to content

Commit

Permalink
Merge pull request #31 from shonin/dev
Browse files Browse the repository at this point in the history
prep for publication
  • Loading branch information
Elliot Charney authored Dec 28, 2020
2 parents 1f6db51 + 4591ee2 commit 3ec8adb
Show file tree
Hide file tree
Showing 17 changed files with 803 additions and 196 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ node_modules
build
!tests/dist
.coverage
htmlcov
htmlcov
_build
203 changes: 22 additions & 181 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,42 @@
# Django Manifest Loader
# Django Manifest Loader

[![Build Status](https://img.shields.io/travis/shonin/django-manifest-loader/main?label=latest%20published%20branch&style=flat-square
[![Build Status](https://img.shields.io/travis/shonin/django-manifest-loader/main?label=stable%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._

Reads a manifest file to import your assets into a Django template. Find
the URL for a single asset or the URLs for multiple assets by using
pattern matching against the file names. Path resolution handled using
Django's built-in `staticfiles` app. Minimal configuraton,
cache-busting, split chunks.

## [Documentation](https://django-manifest-loader.readthedocs.io/en/latest/index.html)

## About

**Turns this**

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

**Into this**

```djangotemplate
<script src="/static/main.8f7705adfa281590b8dd.js" />
<script src="/static/main.8f7705adfa281590b8dd.js"></script>
```

* [Official documentation](https://django-manifest-loader.readthedocs.io/en/latest/index.html)
* 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 django-manifest-loader
```

## Django Setup

```python
# settings.py

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

STATICFILES_DIRS = [
BASE_DIR / 'dist' # the directory webpack outputs to
]
```

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.
* [Quick start blog post](https://medium.com/@shonin/django-and-webpack-in-4-short-steps-b39bd3380c71)

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

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.
}
```

## Webpack configuration

You must install the `WebpackManifestPlugin`. Optionally, but recommended, is to install the `CleanWebpackPlugin`.

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

```javascript
// webpack.config.js

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

module.exports = {
...
plugins: [
new CleanWebpackPlugin(), // removes outdated assets from the output dir
new ManifestPlugin(), // generates the required manifest.json file
],
...
};
```

# 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.

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

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

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)
### Manifest match tag

```djangotemplate
{% load manifest %}
{% manifest_match '*.js' '<script src="{match}"/>' %}
{% 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.
## URLs in Manifest File
If your manifest file points to full urls, instead of file names, the full url will be output instead of pointing
to the static file directory in Django.
Example:
```json
{
"main.js": "http://localhost:8080/main.js"
}
```
```djangotemplate
{% load manifest %}
<script src="{% manifest 'main.js' %}" />
```
Will output as:
```html
<script src="http://localhost:8080/main.js" />
```
# 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. 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
```
BASE_DIR
├── dist
│   ├── main.f82c02a005f7f383003c.js
│   └── manifest.json
├── frontend
│   ├── apps.py
│ ├── src
│ │   └── index.js
│ ├── templates
│ │   └── frontend
│ │   └── index.html
│ └── views.py
├── manage.py
├── package.json
├── project
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── webpack.config.js
```
### 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
# check code coverage
pip install coverage
coverage run --source=manifest_loader/ runtests.py
coverage report
<script src="/static/vendors~main.3ad032adfa281590f2a21.js"></script>
<script src="/static/main.8f7705adfa281590b8dd.js"></script>
```

### Contributing
Do it. Please feel free to file an issue or open a pull request. The code of conduct is basic human kindness.
### License

Django Manifest Loader is distributed under the [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause).
Expand Down
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
61 changes: 61 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('../manifest_loader/templatetags'))


# -- Project information -----------------------------------------------------

project = 'django-manifest-loader'
copyright = '3-Clause BSD License'
author = 'django-manifest-loader'

# The full version, including alpha/beta/rc tags
release = '1.01'


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc','recommonmark', 'sphinx_rtd_theme'
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []

source_suffix = {
'.rst': 'restructuredtext',
'.txt': 'markdown',
'.md': 'markdown',
}
Loading

0 comments on commit 3ec8adb

Please sign in to comment.