Instant reload with Django, NPM, and Browsersync

Here at Metal Toad, we are starting to use NPM as a task runner to automate our development processes. This tutorial shows how to automate CSS compilation and instantly reload the browser when files change.

Objectives

In this post, we will be setting up an automated local build environment with the following goals in mind:

  • A single command to start the dev server and start watching for file changes
  • Automatic compilation of Sass stylesheets into CSS
  • Automatic reload of the CSS without reloading the full page in the browser
  • Automatic reload of the browser tab when a Python, JavaScript, or HTML file changes
  • The ability to run all tasks using NPM, without needing another task runner like Grunt or Gulp

Setup

Before we can set up the build scripts, we will need the following:

  • Python and Pip installed
  • A Django site. The Polls site from the Django Tutorial will work fine.
  • Node and NPM installed

Configuring the NPM task runner

Creating your package.json

Change directory into the root of your Django project. This should be the same folder where manage.py is located. Then, initialize your NPM project and install some packages:

npm init
npm install node-sass concurrently browser-sync --save-dev

What these packages do

node-sass is the Sass compiler for NPM.

concurrently improves NPM's handling of parallel task execution.

browser-sync is a lightweight development server which provides live-reload capability. We will use it in tandem with the Django development server.

Configuring the NPM scripts

Now that we have our packages installed, there will be a package.json file in our project folder. Edit it in your favorite text editor and add the following:

{
  ...
  "scripts": {
    "css-compile": "node-sass myapp/static/scss -o myapp/static/css",
    "css-watch": "node-sass myapp/static/scss -o myapp/static/css --watch",
    "browser-sync": "browser-sync start --files \"myapp/static/css/*.css, myapp/static/js/*.js, myapp/**/*.py, myapp/templates/*.html\" --proxy 127.0.0.1:8000 --reload-delay=300 --reload-debounce=500",
    "start": "concurrently --kill-others \"npm run css-watch\" \"python manage.py runserver\" \"npm run browser-sync\" "
  },
  ...
}

You will need to adjust the file paths in the 'css-compile', 'css-watch', and 'browser-sync' lines to match your project.

Running your new environment

This package.json file includes several separate NPM tasks like 'css-watch' and 'browser-sync'. The concurrently package lets us wrap all these commands, along with the Django development server, into one command to rule them all, `npm start`.

If you are using Python's virtualenv, you will need to activate your virtual environment before running `npm start`.

Type `npm start` into your command prompt. This will start watching your project files for changes, and a browser window will appear with the front page of your site. Whenever you edit a Python, JavaScript, or HTML file, the page will reload automatically. When you edit an SCSS file, your stylesheets will be compiled into CSS, and the CSS in the browser will reload, without having to reload the entire page.

Browsersync and the Django development server

Browsersync injects a JavaScript snippet into the body tag of the pages it serves, which sets up the live reload. It can't serve a Python application, however. The good news is that it has a built-in proxy, which we're using to relay requests to the Django development server.

Here, we have given Browsersync a few command-line options:

  • --files \"myapp/static/css/*.css, myapp/**/*.py, myapp/templates/*.html\": This option tells Browsersync to watch all CSS, Python, and HTML files in the specific locations listed. You will probably need to update these paths, or add new ones, to match your project.
  • --proxy 127.0.0.1:8000: This tells Browsersync to proxy its requests to the Django development server
  • --reload-delay=300: This makes Browsersync wait for 300 milliseconds after it detects a file change before reloading the page. This prevents race conditions between Browsersync and Python. Without this, you may see pages fail to load when you edit a Python file. If your browser starts to reload and the loading spinner just spins forever, increase this value, then stop and start the npm task.
  • --reload-debounce=500: This prevents the page from reloading more than twice a second, in case you have fast fingers.

Troubleshooting

The server runs, but nothing happens when I edit a file

Check the paths to the files in your package.json and make sure they match the locations of the files in your projects. You may need to add or remove paths.

"Unable to determine the domain name" error with Browsersync

If Browsersync fails with an error "Unable to determine the domain name", you need to inspect the command arguments. Use '127.0.0.1' instead of 'localhost', don't include the protocol, and don't enclose the address in quotes.

  • Bad: --proxy localhost:8000 (this should work but doesn't, possibly due to this bug)
  • Bad: --proxy '127.0.0.1:8000'
  • Bad: --proxy https://127.0.0.1:8000
  • Good: --proxy 127.0.0.1:8000

NPM command errors

This setup is designed to use the project versions of the node packages, rather than the global versions. On some systems, you may see errors that the 'node-sass' or 'browser-sync' commands are not found. If this happens, add the following to your package.json:

{
  ...
  "bin": {
    "node-sass": "/.bin/node-sass"
    "browser-sync": "/.bin/browser-sync"
  },
  ...
}

What about Apache and mod_wsgi?

If you are hosting your local development site using Apache and mod_wsgi, you should be able to use most of the techniques shown in this post. You would need to remove the "python manage.py runserver" command from the 'npm start' line in package.json, and change the proxy address provided to Browsersync. Your package.json file might look something like this, assuming 'myapp.dev' is the address of your local development site:

{
  ...
  "scripts": {
    ...
    "browser-sync": "browser-sync start --files \"myapp/static/css/*.css, myapp/**/*.py, myapp/templates/*.html\" --proxy myapp.dev --reload-delay=300 --reload-debounce=500",
    "start": "concurrently --kill-others \"npm run css-watch\" \"npm run browser-sync\" "
  },
  ...
}

Next steps

We could easily add JavaScript minification, CSS minification, image processing, etc. by adding additional commands to `npm start`.

Compatibility

This setup has been tested with Django 1.9 and NPM 3.x on Windows, Ubuntu/Mint, and OSX.

Conclusion

The ideas shown here should also work with platforms other than Django. You would only need to adjust file paths and replace the Django dev server command with a different command.

Happy coding!

Hi. I'm running my python app on Virtual machine using vagrant. I followed this guide and the server seems to run but I'm curious how can I view changes in the browser. The links in the Access URLs section don't seem to open in the browser on my Mac. Please advise. Here is my output:

(hudsongreens) vagrant@vagrant-ubuntu-trusty-64:~/hg$ npm start

> carfilter@1.0.0 start /home/vagrant/code/Carfilter_Project/carfilter
> concurrently --kill-others "python manage.py runserver" "npm run browser-sync"

[1]
[1] > carfilter@1.0.0 browser-sync /home/vagrant/code/Carfilter_Project/carfilter
[1] > browser-sync start --files "filterpages/templates/filterpages/*.html" --proxy 127.0.0.1:8000 --reload-delay=300 --reload-debounce=500
[1]
[1] [BS] Proxying: http://127.0.0.1:8000
[1] [BS] Access URLs:
[1] ----------------------------------
[1] Local: http://localhost:3000
[1] External: http://10.0.2.15:3000
[1] ----------------------------------
[1] UI: http://localhost:3001
[1] UI External: http://10.0.2.15:3001
[1] ----------------------------------
[1] [BS] Watching files...

I haven't tested the browser-sync setup with Vagrant, and it's not guaranteed to work there. But here's some ideas that might help:

If browser-sync is running inside the Vagrant VM, this will prevent browser-sync from opening a new browser tab automatically. Also, you may need to open some ports in your VM config. You'll need to open your browser tabs manually on the host Mac, and use the "External" URLs shown in the program output. (10.0.2.15:3000 in your case.)

instead of --files "...." need to specify --files="....", i ran into (and searched for a long time why) browser-sync does not take files changes, although all the paths and file masks was right. Simple addition '=' solved the problem.

See example of using files parameter on https://browsersync.io/docs/command-line#reload in reload section.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

About the Author

Keith Dechant, Software Architect

Keith has been working on the web almost since it was new, writing his first HTML in 1996 and his first PHP code in 1999. Along the way, he has written everything from e-commerce websites to mailing list software to large web applications for industrial and non-profit organizations. Recently, he has done a lot of work with ASP.NET, Django, Drupal 7 and 8, and AngularJS.

Keith is a native of Illinois who moved to Portland in 2009. He has been to 49 US states, five continents, three former Soviet republics, and two countries that no longer exist. He likes hiking, mountain climbing, classic video games, and the Oxford comma.

Ready for transformation?