Skip to content

Instantly share code, notes, and snippets.

@limelights
Last active February 28, 2023 06:23
Show Gist options
  • Select an option

  • Save limelights/0bdc634f388af78aad3064438ad47696 to your computer and use it in GitHub Desktop.

Select an option

Save limelights/0bdc634f388af78aad3064438ad47696 to your computer and use it in GitHub Desktop.
How I setup Workbox with Django
Setting up [WorkboxJS](workboxjs.org) with [Django](djangoproject.com).
Prerequisites:
My particular project is using Docker to power the Django app.
In front of the Django app there is Nginx which serves and caches assets.
Your setup might be a bit different but I have yet to find any "best practices" regarding ServiceWorkers with Workbox, webpack, Nginx and Django.
I wanted to use Workbox because I wanted to use precaching and background analytics for my site. This is quite lengthy.
*I ALSO ASSUME THAT YOU'VE SETUP YOUR MANIFEST.JSON and all other PWA related things*
*I am sure that there must be a cleaner way to accomplish this.*
Let's start by configuring Nginx first;
**Nginx:**
Assuming Nginx 1.9.x, in your nginx conf (mine is named `django.conf`) add a new location block:
```django.conf
location /static/ {
autoindex on;
alias /static/;
}
location = /index.html {
return 301 /;
}
```
This will be neccessary for the ServiceWorker to be able to cache `/index.html` and serve your cached assets.
**Creating the service worker**
In order to be able to use a serviceworker at `/` of our app we need to register it first.
I used some boilerplate code extracted into a file called `register-service-worker.js` which I used in my template
served with standard Django static assets template tag, like this:
``` index.html
<script type="text/javscript" src="{% static 'register-service-worker.js' %}"></script>
```
This file contained the following registration code
```js register-service-worker.js
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/service-worker.js');
registration.onupdatefound = () => {
const isInstalling = registration.installing;
isInstalling.onstatechange = () => {
if (isInstalling.state === 'installed') {
if (navigator.serviceWorker.controller) {
console.log('new content found, please refresh');
} else {
console.log(`content is now cached for offline usage with scope: ${registration.scope}`);
}
}
}
};
} catch (err) {
console.log('ServiceWorker registration failed: ', err);
}
});
}
```
Now that you've take care of this, where does this code live?
I found that creating a Django app called `javascript` was a neat way for me to utilise the tooling available.
`python3 manage.py startapp javascript apps/javascript` *assuming you have your apps in /apps*
The sole purpose of this app is to host my javascript I went ahead and removed `models, admin, views` but kept the
app configuration and `urls.py` (which we need soon).
Now we remember that a service worker only works under one path so in order for us to have it access everything below `/`
we need to register it on `/` which we did in the boilerplate above.
Neither Nginx nor Django will be able to locate this file and thus the registration will fail.
In order to fix this I opted to add a [TemplateView]() that serves the `service-worker.js`.
In your `apps/javascript/urls.py` add the following, changing your import paths to what they might in your app.
```python apps/javascript/urls.py
from django.urls import path
from apps.javascript.views.serviceworker import ServiceWorkerView
app_name = 'javascript'
urlpatterns = [
path('service-worker.js', ServiceWorkerView.as_view(), name="serviceworker"),
]
```
and the following in `apps/javascript/views/serviceworker.py` (I've opted to use packages for my views and single files instead of mushing it all into `views.py`)
```python apps/javascript/views/serviceworker.py
from django.views.generic import TemplateView
class ServiceWorkerView(TemplateView):
template_name = 'serviceworker/serviceworker.js'
content_type = 'text/javascript'
```
Now when we fire our app up and load whatever page we used the `{% static 'register-service-worker.js' %}` on the service worker should be registring and you're good to go!
*Adding Workbox.js*
I'm assuming that you use webpack (since I use it) then there's a webpack plugin that makes working with Workbox easier.
[workbox-webpack-plugin](https://www.npmjs.com/package/workbox-webpack-plugin).
Make sure this plugin goes in last in your plugin list and again, this is the code I got working for my project and my needs.
```js webpack.config.js
new workboxWebpackPlugin({
injectManifest: true,
swSrc: './apps/javascript/src/lib/serviceworker.js', //Inside of the Django app `javascript`
swDest: './apps/javascript/templates/javascript/serviceworker.js', //This is the view we wrote above that serves it.
globDirectory: './static/',
globPatterns: [
'**/*.js'
],
globIgnores: ['admin/**/*', ],
manifestTransforms: [staticPathTransform],
templatedUrls: {
'index.html': [
'bundle-*.js'
]
}
})
```
The template file `service-worker.js` looks like this
```
importScripts("/static/workbox-sw.prod.v2.1.2.js");
const workBoxServiceWorker = new self.WorkboxSW({
cacheId: 'plannerlyweb',
skipWaiting: true,
clientsClaim: true,
});
workBoxServiceWorker.precache([]);
```
The quick rundown is that we use a template file that Workbox injects the manifest (the assets you want cached and precached by the service worker) into.
This file resides in `/apps/javascript/src/lib/serviceworker.js` (in `/apps/javascript/src` is where all javascript will reside in my app)
The plugin then outputs a "compiled" serviceworker into the `swDest` which is the same path the TemplateView from above serves the `service-worker.js` file.
- The `globDirectory` is where the plugin finds the files to put in the
manifest. The `globPatterns` are what files the plugin looks for
(.html, .js, .css, .whatever) The `globIgnores` are what it sounds
like. The `manifestTransforms` is an Array with functions which will
modify each entry in the manifest (all files discovered in
`globDirectory`). We have a transform (outlined below)
in order for us to be able to precache files found in `/static/` which Nginx serves. The `templatedUrls` is there for the
ServiceWorker to say "When finding /index.html also find these
related dependencies". *This is key in our setup*.
We use a glob called `bundle-*.js` in order for the ServiceWorker to pick up the bundle produced by Webpack.
And the last step is to copy the `workbox-sw.prod.v2.1.2.js` file into a `static` folder with your `javascript` Django app so that `collectstatic` can find it!
And there you have it, a working ServiceWorker with Workbox with assets served by Nginx and Django. Built by webpack.
@e-monson
Copy link

Thanks for writing this up. It's been very helpful. Can you also share the staticPathTransform you're using above?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment