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
- Para incluir una nueva librería basta con añadirla a bower.json
- Para cambiar un estilo, basta con tocar el less o el css
- Con gulp.watch podemos tener en segundo plano la tarea
- Podemos depurar js/css por que los ficheros no están minimizados
- Nuestro equipo de frontend es más independiente.
Desplegando nuestro proyecto
- Necesitamos que los ficheros vayan minificados.
- 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
Django Gulp
- 3,083