Saltar al contenido

Integrando webpack-dev-server + hot module replacement con Django

Cómo conectar webpack-dev-server a un proyecto Django para tener hot module replacement, incluyendo un template tag para alternar entre desarrollo y producción.

4 min de lectura
  • webpack
  • django
  • tooling

Publicado originalmente en Medium

TL;DR: En este post no profundizaré mucho en los conceptos usados, pues asumo que el lector tiene cierta experiencia con webpack y Django; aun así, al final del post dejaré algunos links que puedes usar para profundizar en estos conceptos.

Debo suponer que si llegaste a este post es porque buscas una forma rápida y sencilla de mejorar la experiencia de desarrollo en el lado front-end… y que obviamente ya usas webpack y Django ლ(╹◡╹ლ)

Para dejarlo simple, webpack-dev-server es una herramienta que automáticamente actualiza el navegador cuando haces algún cambio en tu código JavaScript. Para comunicar estos cambios al navegador, webpack-dev-server usa un server de express.js configurado con socket.io.

Para tener en cuenta:

Live reload: comportamiento por default de webpack-dev-server; al hacer un cambio en el código, el navegador automáticamente hace un full reload.

Hot Module Replacement (HMR): usando algunos plugins y una configuración extra podemos hacer que nuestros cambios se vean reflejados en el navegador sin necesidad de hacer un full reload.

Vamos directamente a ello…

Configurando webpack

Primero instala el paquete:

npm install --save-dev webpack-dev-server

Luego cambiemos la configuración de nuestro webpack:

module.exports = {
  // ... el resto de tu configuración
  entry: {
    app: './static/js/app.js',
  },
  output: {
    // el output.publicPath debe coincidir con el publicPath del devServer
    publicPath: 'http://localhost:9000/static/js/',
    filename: '[name].min.js',
  },
  // devServer es lo que necesitamos configurar para obtener hmr con webpack-dev-server
  devServer: {
    // url publica por donde podremos acceder a nuestros archivos
    publicPath: 'http://localhost:9000/static/js/',
    // puerto donde correrá webpack-dev-server (el default es 8080)
    port: 9000,
    // ACTIVAMOS EL HMR
    hot: true,
    /*
      la configuración en headers es necesaria para decirle a webpack que acepte
      peticiones de cualquier puerto (en este caso las peticiones que hará nuestro server de django para obtener los archivos js)
      de lo contrario al hacer algún cambio en nuestro código la consola del navegador
      nos mostrará el siguiente mensaje:
      "No 'Access-Control-Allow-Origin' header is present on the requested resource"
    */
    headers: {
      /*
        también podemos colocar directamente la url en donde corre nuestro server de Django
        'Access-Control-Allow-Origin': 'http://localhost:8000'
        si lo hacemos urls como "http://mi-alias-de-localhost:8000" o "http://mi-ip-publica:8000"
        no podrán acceder a nuestros archivos js, ya que no contienen el dominio(localhost) especificado.
      */
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET,OPTIONS,HEAD,PUT,POST,DELETE,PATCH',
      'Access-Control-Allow-Headers':
        'Origin, Content-Type, Accept, Authorization, X-Request-With',
      'Access-Control-Allow-Credentials': 'true',
    },
    // para ver el resto de opciones puedes revisar:
    // https://webpack.js.org/configuration/dev-server/#devserver
  },
  plugins: [
    // ...tus otros plugins
    // plugins necesarios para activar el hmr
    // new webpack.NoEmitOnErrorsPlugin(), // en webpackv4 este plugin ya viene por default
    new webpack.HotModuleReplacementPlugin(),
  ],
};

Como viste, no es nada complicado: solo configuramos el devServer y agregamos un par de plugins para obtener el HMR. Ahora solo necesitamos agregar un nuevo script en nuestro package.json para correr el webpack-dev-server:

"client:hmr": "webpack-dev-server --colors --progress"

Ejecutamos el comando en una consola y listo, ya tenemos el 70% del trabajo hecho:

npm run client:hmr

Configurando Django

Lo que normalmente tenemos en nuestros templates de Django es algo como esto (suponiendo que tus archivos estáticos pueden ser accedidos vía http://localhost:8000/static/**/*):

<script src="{% static 'js/app.min.js' %}"></script>

Pero ahora que ya tenemos webpack-dev-server corriendo en el puerto 9000, todos nuestros scripts deben apuntar a ese puerto, así que cambiemos el script por:

<script src="http://localhost:9000/static/js/app.min.js"></script>

Sí, http://localhost:9000/static/js es el valor que colocamos en output.publicPath y devServer.publicPath. Y sí, puedes ponerle cualquier ruta que quieras (╯°□°)╯, pero por seguir el estándar con el que nuestra app de Django sirve los estáticos opté por configurarlo de esa forma.

Hasta este punto, si entras a la url del template configurado, podrás ver en la consola del navegador los siguientes logs:

Logs de HMR en la consola del navegador

Lo hemos logrado…

Éxito

…pero está imperfecto ( ゚,ゝ゚). No tratamos de ser perfectos pero sí de hacerlo lo mejor posible, ¿verdad? ┌( ಠಠ )┘

El principal problema es que para llevarlo a production tendremos que cambiar 'manualmente' la url de los scripts, y luego, para regresar a modo development, tendremos que volverla a cambiar.

La solución: crear un template tag que nos permita hacer el cambio entre modo production/development.

Para ello recomiendo crear variables en nuestro settings.py para poder reusarlas en los lugares que necesitemos:

WEBPACK_DEV_SERVER_URL = 'http://localhost:9000'
WEBPACK_DEV_SERVER = True

Las usamos en la creación de nuestro webpack template tag:

# ../templatetags/webpack_tags.py
# No soy experto en django ya que soy más del lado front-end que
# back-end, pero así es como yo lo haría (ノ ಥ ウಥ )ノ
from django import template
from django.conf import settings
from django.contrib.staticfiles.templatetags.staticfiles import static
 
register = template.Library()
 
 
@register.simple_tag
def webpack(javascript_file, **args):
    if settings.DEBUG and settings.WEBPACK_DEV_SERVER:
        return '%s/%s' % (settings.WEBPACK_DEV_SERVER_URL, javascript_file)
    return static(javascript_file)

Ahora vamos a nuestro template y cambiamos el script:

{% load webpack_tags %}
 
<script src="{% webpack 'app.min.js' %}"></script>

Actualiza tu navegador y deberías ver los logs del HMR:

Logs del HMR funcionando

Lo logramos (屮◉◞益◟◉)屮

Bien hecho soldado

Obviamente aún falta activar el HMR dentro de tus archivos JavaScript, pero eso es relativamente fácil, así que no deberías tener problemas haciéndolo por ti mismo —¿también querías que te muestre cómo? Lo siento chico, la vida no es tan fácil ლ(ಠ益ಠლ)—. Aquí te dejo la guía oficial.

¡Y eso fue todo! ( ̯͡◕ ▽ ̯͡◕ ) Muchas gracias por leer el post. Si lo encontraste útil, dale un buen 👏 y compártelo con tus amigos; estaré muy agradecido si lo haces (•̪◡•̪)

¡¡Cualquier feedback, corrección o mejora es bienvenido!!

Links de ayuda: