Manejo de static en Django con Gulp

PyCon Spain. Almería 2016

Tomás Garzón

Sobre mí

  • Desarollador/fundador startups.
  • Full Stack Web Developer
  • Desde Granada.

Mis proyectos

  • IActive
  • Quinexas
  • Cognocare
  • LeanMonitor
  • Cruzandolameta.es
  • ExO Lever

Django y sus static

¿Qué son statics en Django?

     - css / js / img ... --> se sirven directamente por NGINX

¿Por qué necesito gestionarlos?

 

Bootstrap

Tema

Estilos

Propios

Librerías Propias

Librerías JS

Templates

1ª Aproximación

  • Busco la librería que quiero
  • Descargo la librería.
  • Incluyo las referencias en el base.html de Django
  • Añado la librería a mi proyecto en Git

2ª Aproximación

  • Utilizamos Bower para gestionar las librerías de terceros
  • Configuramos bower.json y .bowerrc
  • Tenemos que añadir manualmente las librerías a base.html
{
  "name": "example",
  "version": "0.0.1",
  "description": "Example Description",
  "dependencies": {
    "jquery": "~2.1.4",
    "bootstrap": "~3.3.6",
    "moment": "~2.10.6",
  }
}

Problemas

  • El equipo de frontend modifica a menudo el base.html.
  • Realizamos muchas peticiones al servidor de static.
  • Necesitamos compilar a mano/on-the-fly Less/Sass
  • Problemas de cacheo de js/css

Necesitamos un script que haga la magia

Ejecutando scripts con Gulp

  • Las cosas de frontend, mejor con herramientas del frontend
  • GULP: ejecutor de tareas basado en Node.

base.tpl

bower.json

Less/sass

Javascript

Templates underscore

Gulp

Librerías JS

Librerías CSS

Código JS proyecto

Código CSS proyecto

Código JS de templates

 

base.html

base.tpl

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>{% block title %}{% endblock %}</title>
        <!-- build:css(src) styles/vendor-base.css -->
        <!-- bower:css -->
        <!-- run `gulp inject` to automatically populate bower styles dependencies -->
        <!-- endbower -->
        <!-- endbuild -->

        <!-- build:css({.tmp/serve,src}) styles/app-base.css -->
        <!-- inject:css -->
        <!-- css files will be automatically insert here -->
        <!-- endinject -->
        <!-- endbuild -->
        {% block styles %}{% endblock %}
    </head>
    <body class="md-skin fixed-sidebar fixed-nav" id="page-top">
        {% block content %}{% endblock %}

        <!-- build:js(src) scripts/vendor-base.js -->
        <!-- bower:js -->
        <!-- run `gulp inject` to automatically populate bower script dependencies -->
        <!-- endbower -->
        <!-- endbuild -->

        <!-- build:js(.tmp/serve) scripts/app-templates.js -->
        <script type="text/javascript" src="templates/templates.js"></script>
        <!-- endbuild -->

        <!-- build:js(src) scripts/app-base.js -->
        <!-- inject:js -->
        <!-- js files will be automatically insert here -->
        <!-- endinject -->
        <!-- endbuild -->
        {% block js %}{% endblock %}
</body>
</html>

Ficheros css de librerías

Ficheros css propios+tema

Ficheros js templates underscore

Ficheros js de librerías

Ficheros js propios

var gulp = require('gulp'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat');

gulp.task('scripts', function () { 
    return gulp.src('js/*.js')
       .pipe(jshint())
       .pipe(uglify())
       .pipe(concat('app.js'))
       .pipe(gulp.dest('build')); });

¿Cómo funciona gulp?

- Necesitamos tener node instalado.

- Definimos en package.json las dependencias

- npm install

gulp scripts

gulpfile.js

Principales paquetes de Gulp:

- gulp-inject: inyecta las dependencias en el template

- gulp-wiredep: mapea las dependencias del bower.json

- gulp-less: compilar nuestros ficheros less a css

- gulp-html2tpl: compilar HTML de underscore a un fichero js

- gulp-minify-css: minimizar ficheros css

- gulp-uglify: comprimir ficheros js

- gulp-useref: concatena ficheros en uno

- gulp-rev: añade un hash para el cacheo de ficheros

Empezando por los estilos....

// bower:less
// endbower

@import "less/style";

// injector
// endinjector

index.less

gulp.task('styles', function() {
    return gulp.src([
        path.join(conf.paths.src, '/app/index.less')
          ])
        .pipe($.inject(injectFiles, injectOptions))
        .pipe(wiredep(_.extend({}, conf.wiredep)))
        .pipe($.sourcemaps.init())
        .pipe($.less(lessOptions)).on('error', conf.errorHandler('Less'))
        .pipe($.sourcemaps.write())
        .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')));
        };

Los htmls de underscore....

gulp.task('templates', function () {
    return gulp.src(path.join(conf.paths.src, '/templates/**/*.html'))
      .pipe($.html2tpl('templates.js'))
      .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/templates/')));
});

Ejecutamos el inject en el base.tpl

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>{% block title %}{% endblock %}</title>
        <!-- build:css(src) styles/vendor-base.css -->
        <!-- bower:css -->
        <!-- run `gulp inject` to automatically populate bower styles dependencies -->
        <!-- endbower -->
        <!-- endbuild -->

        <!-- build:css({.tmp/serve,src}) styles/app-base.css -->
        <!-- inject:css -->
        <!-- css files will be automatically insert here -->
        <!-- endinject -->
        <!-- endbuild -->
        {% block styles %}{% endblock %}
    </head>
    <body class="md-skin fixed-sidebar fixed-nav" id="page-top">
        {% block content %}{% endblock %}

        <!-- build:js(src) scripts/vendor-base.js -->
        <!-- bower:js -->
        <!-- run `gulp inject` to automatically populate bower script dependencies -->
        <!-- endbower -->
        <!-- endbuild -->

        <!-- build:js(.tmp/serve) scripts/app-templates.js -->
        <script type="text/javascript" src="templates/templates.js"></script>
        <!-- endbuild -->

        <!-- build:js(src) scripts/app-base.js -->
        <!-- inject:js -->
        <!-- js files will be automatically insert here -->
        <!-- endinject -->
        <!-- endbuild -->
        {% block js %}{% endblock %}
</body>
</html>
gulp.task('inject', ['styles', 'templates'], function () {
    return gulp.src(conf.paths.base)
        .pipe($.inject(injectStyles, injectOptions))
        .pipe($.inject(injectScripts, injectOptions))
        .pipe(wiredep(conf.wiredep))
        .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
    });

base.tpl "expandido"

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <!-- Page title set in pageTitle directive -->
        <title>{% block title %}{% endblock %}</title>
        <!-- build:css(src) styles/vendor-base.css -->
        <!-- bower:css -->
        <link rel="stylesheet" href="../../frontend/bower_components/toastr/toastr.css" />
        <link rel="stylesheet" href="../../frontend/bower_components/select2/dist/css/select2.min.css" />
        <!-- endbower -->
        <!-- endbuild -->
        <!-- build:css({.tmp/serve,src}) styles/app-base.css -->
        <!-- inject:css -->
        <link rel="stylesheet" href="app/index.css">
        <link rel="stylesheet" href="app/css/animate.css">
        <!-- endinject -->
        <!-- endbuild -->
        {% block styles %}{% endblock %}
    </head>
    <body class="md-skin fixed-sidebar fixed-nav" >
        {% block content %}{% endblock %}
        <!-- build:js(src) scripts/vendor-base.js -->
        <!-- bower:js -->
        <script src="../../frontend/bower_components/jquery/dist/jquery.js"></script>
        <script src="../../frontend/bower_components/bootstrap/dist/js/bootstrap.js"></script>
        <script src="../../frontend/bower_components/moment/moment.js"></script>
         <!-- endbower -->
        <!-- endbuild -->

        <!-- build:js(.tmp/serve) scripts/app-templates.js -->
        <script type="text/javascript" src="templates/templates.js"></script>
        <!-- endbuild -->

        <!-- build:js(src) scripts/app-base.js -->
        <!-- inject:js -->
        <script src="modules/lib.js"></script>
        <script src="modules/app.js"></script>
        <!-- endinject -->
        <!-- endbuild -->
        {% block js %}{% endblock %}
    </body>
</html>

Pasos finales

gulp.task('html-dev', ['inject'], function () {

  return gulp.src(path.join(conf.paths.tmp, '/serve/base.tpl'))
    .pipe($.rename('base.html'))
    .pipe(assets = $.useref.assets())
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe($.revReplace())
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
    .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
  });
{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <!-- Page title set in pageTitle directive -->
        <title>{% block title %}{% endblock %}</title>
        <link rel="stylesheet" href="{% static "styles/vendor-base.css" %}">

        <link rel="stylesheet" href="{% static "styles/app-base.css" %}">
        {% block styles %}{% endblock %}
    </head>
    <body class="md-skin fixed-sidebar fixed-nav">
        {% block content %}{% endblock %}

        <script src="{% static "scripts/vendor-base.js" %}"></script>

        <script src="{% static "scripts/app-templates.js" %}"></script>

        <script src="{% static "scripts/app-base.js" %}"></script>
        {% block js %}{% endblock %}
</body>
</html>

Ventajas durante el desarrollo

  1. Para incluir una nueva librería basta con añadirla a bower.json
  2. Para cambiar un estilo, basta con tocar el less o el css
  3. Con gulp.watch podemos tener en segundo plano la tarea
  4. Podemos depurar js/css por que los ficheros no están minimizados
  5. Nuestro equipo de frontend es más independiente.

Desplegando nuestro proyecto

  1. Necesitamos que los ficheros vayan minificados.
  2. Necesitamos añadir una marca temporal para que una vez servidos, se puedan cachear
gulp.task('html', ['inject'], function () {
  var htmlFilter = $.filter('*.html', { restore: true });
  var jsFilter = $.filter('**/*.js', { restore: true });
  var cssFilter = $.filter('**/*.css', { restore: true });
  var assets;

  return gulp.src(path.join(conf.paths.tmp, '/serve/base.tpl'))
    .pipe($.rename('base.html'))
    .pipe($.rev())
    .pipe(jsFilter)
    .pipe($.sourcemaps.init())
    .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
    .pipe($.sourcemaps.write('maps'))
    .pipe(jsFilter.restore)
    .pipe(cssFilter)
    .pipe($.sourcemaps.init())
    .pipe($.minifyCss({ processImport: false }))
    .pipe($.sourcemaps.write('maps'))
    .pipe(cssFilter.restore)
    .pipe($.useref())
    .pipe($.revReplace())
    .pipe(htmlFilter)
    .pipe($.minifyHtml({
      empty: true,
      spare: true,
      quotes: true,
      conditionals: true
    }))
    .pipe(htmlFilter.restore)
    .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
    .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true }));
  });

base.html

{% load staticfiles %}
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>{% block title %}{% endblock %}</title>
        <link rel="stylesheet" href="{% static "styles/vendor-base-180e53936d.css" %}">
        <link rel="stylesheet" href="{% static "styles/app-base-8807c93d9c.css" %}">
        {% block styles %}{% endblock %}
    </head>
    <body class="md-skin fixed-sidebar fixed-nav">
        {% block content %}{% endblock %}
        <script src="{% static "scripts/vendor-base-9afbaab148.js" %}"></script>
        <script src="{% static "scripts/app-templates-365152f0f1.js" %}"></script>
        <script src="{% static "scripts/app-base-95bdff3860.js" %}"></script>
        {% block js %}{% endblock %}
    </body>
</html>

Manejo de static con bower y gulp para Django

Tomás Garzón Hervás

PyCon ES 2016 Almería

 

https://github.com/ExOLever/django-gulp

tomasgarzonhervas@gmail.com

Django Gulp

By tomasgarzon