Si no conoces Grunt puede ser de ayuda que veas my presentación sobre nuevas herramientas front en Slid.us.
Estructura principal del contenido.
module.exports = function(grunt) {
grunt.initConfig({
//La configuración de tusp lugins y sus tareas va aquí
});
grunt.loadNpmTasks("nombre_de_grunt_plugin");
grunt.registerTask("tarea_personalizada", ["nombre_de_grunt_plugin:tarea_especifica"]);
};
Para las siguientes descripciones de plugins voy a copiar y usar esta estructura, y al final de la entrada mezclaré todo el código en un fichero único.
Una ayuda para el desacoplamiento.
Puedes leer meta-información de tu package.json o definir la tuya propia, para desacoplar fácilmente la estructura de tu proyecto de tus tareas de grunt.
Para una fácil comprensión:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
meta: {
cssEndpoint: "site/css",
tuktuk: {
sourcesBasePath: "sources/tuktuk/sources"
}
},
source: {
tuktuk: {
stylus: ["<%= meta.tuktuk.sourcesBasePath %>/stylesheets/tuktuk.*.styl"]
//EQUIVALENTE A
//stylus: ["sources/tuktuk/sources/stylesheets/tuktuk.*.styl"]
}
}
});
};
Para mi, construir un bien desacoplado gruntfile ha sido un proceso continuo e iterativo, mi log de confirmaciones está lleno de "refactorización de gruntfile", y para cada persona esta meta-información puede ser distinta. Al final puedo decir que lo que tengo en mente es:
Usa meta para definir rutas y source para archivos específicos, así pues tus tareas usarán siempre un meta y los menos archivos específicos posibles.
El cotnenido completo de esta parte de meta-información que será utilizadas en las siguientes secciones:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
meta: {
cssEndpoint: "site/css",
stylusSourceBase: "sources/stylus/",
jsSourceBase: "sources/js/",
libsJsSourcesBasePath: "sources/libs",
libsJsEndPoint: "site/js/libs",
tuktuk: {
sourcesBasePath: "sources/tuktuk/sources",
file: "tuktuk",
cssEndpoint: "<%= meta.cssEndpoint %>/libs/tuktuk",
jsEndpoint: "<%= meta.libsJsEndPoint %>"
}
},
source: {
tuktuk: {
coffee: ["<%= meta.tuktuk.sourcesBasePath %>/tuktuk.coffee", "<%= meta.tuktuk.sourcesBasePath %>/tuktuk.*.coffee"],
stylus: ["<%= meta.tuktuk.sourcesBasePath %>/stylesheets/tuktuk.*.styl"],
theme: ["<%= meta.stylusSourceBase %>/theme.bu.styl"],
icons: "<%= meta.tuktuk.sourcesBasePath %>/components/lungo.icon/lungo.icon.css"
},
stylus: {
imports:["<%= meta.stylusSourceBase %>/imports.styl"],
customization: ["<%= meta.stylusSourceBase %>/variables.styl","<%= meta.stylusSourceBase %>/customization.styl"]
},
libsJs: {
jquery: ["<%= meta.libsJsSourcesBasePath %>/jquery.js"],
require: ["<%= meta.libsJsSourcesBasePath %>/require.js"],
zepto: ["<%= meta.libsJsSourcesBasePath %>/zepto.js"]
}
}
});
};
Compilando los fuentes coffeescript y stylus de una librería externa.
El principal objetivo de esta tarea es compilar los fuentes de una librería externa en una tarea separada para ejecutarla solo cuando actualicemos los fuentes desde el repositorio original, o cuando modifiquemos la librería, algo que debemos evitar.
Esta sección es específica para Tuktuk por con sus consecuentes modificaciones puede ser usada para cualquier otro framework HTML somo Bootstrap o Zurb, en este caso el framework usa Stylus para compilar a CSS y Coffeescript para compilar a javascript.
Así que necesitamos instalar algunos plugins para compilar estos fuentes, prefiero usar los plugins contrib debido a que los actualizan más frecuentemente,:
npm install grunt-contrib-coffee --save-dev
npm install grunt-contrib-stylus --save-dev
npm install grunt-contrib-copy --save-dev
y esta es mi configuración de plugins para esta compilación:
module.exports = function(grunt) {
grunt.initConfig({
coffee: {
engine: {
options:{ join: true, },
files: {
"<%= meta.tuktuk.jsEndpoint %>/<%= meta.tuktuk.file %>.js": [
"<%= source.tuktuk.coffee %>"
]
}
}
},
stylus: {
tuktuk: {
options: { compress: false},
files: {
'<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.css': '<%= source.tuktuk.stylus %>'
}
}
},
copy: {
main: {
files: [
{
src: '<%= source.tuktuk.icons %>',
dest: "<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.icons.css"
}
]
}
}
});
grunt.loadNpmTasks("grunt-contrib-coffee");
grunt.loadNpmTasks("grunt-contrib-stylus");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.registerTask("tuktuk", ["notify:init_tuktuk_compilation", "stylus:tuktuk", "coffee", "notify:end_tuktuk_compilation"]);
};
La tarea coffee compila todos los fuentes en un único archivo Javascript.
La tarea stylus:tuktuk compila cada archivo styluss que se encuentre en sources/tuktuk/sources/stylesheets/tuktuk.*.styl a un archivo CSS de mismo nombre con el contenido sin comprimir.
La última tarea copy copiará la CSS de iconos de los fuentes de la librería a su destino en el sitio.
Compilando mis fuentes Stylus.
El framework Tuktuk tiene archivos separados con propósitos de personalización y he decidido mover esos fuentes del framewrok a mi carpeta Stylus para acceder fácilmente a ellos.
También tengo 2 tareas, una para entorno de desarrollo cuando quiero cada archivo CSS por separado, y otra para producción donde quiero tener un único archivo CSS.
En el entorno de desarrollo no quiero tener que arrancar manualmente las tareas cada vez que actulice un fichero, por lo que necesito una tarea que automáticamente ejecuta mis tareas cuando modifique un archivo específico. Por lo que necesitamos instalarla:
npm install grunt-contrib-watch
Así, el contenido de mi gruntfile para estas tareas es:
module.exports = function(grunt) {
grunt.initConfig({
stylus: {
develop: {
options: { compress: false},
files: {
'<%= meta.cssEndpoint %>/imports.css': '<%= source.stylus.imports %>',
'<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.theme.css': '<%= source.tuktuk.theme %>',
'<%= meta.cssEndpoint %>/customization.css': '<%= source.stylus.customization %>'
}
},
production: {
options: {compress: true },
files: {
'<%= meta.cssEndpoint %>/profile.min.css':[
'<%= source.stylus.imports %>',
'<%= source.tuktuk.stylus %>',
'<%= source.tuktuk.theme %>',
'<%= source.stylus.customization %>'
]
}
}
},
copy: {
main: {
files: [
{
src: '<%= source.tuktuk.icons %>',
dest: "<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.icons.css"
},
{ expand: true,
cwd: "<%= meta.jsSourceBase %>",
src: ["**"],
dest: "<%= meta.jsDestBase %>"
}
]
}
},
cssmin: {
combine: {
files: {
'<%= meta.cssEndpoint %>/profile.min.css': [
'<%= meta.cssEndpoint %>/profile.min.css',
'<%= meta.tuktuk.cssEndpoint %>/<%= meta.tuktuk.file%>.icons.css'
]
}
}
},
watch: {
stylus: {
files: ['<%= source.tuktuk.theme %>', '<%= source.stylus.customization %>'],
tasks: ["stylus"]
}
}
});
grunt.loadNpmTasks("grunt-contrib-stylus");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.registerTask("develop", ["stylus:develop"]);
grunt.registerTask("production", ["stylus:production", "cssmin", "copy"]);
};
La tarea watch, bajo una propiedad con nombre de tarea, tiene la lista de archivos que observar por cambios para ejecutar las tareas consecuentes
Compilando HTML con Jade.
Instalar el plugin:
npm install grunt-contirb-jade --save-dev
Y como en la sección de Stylus, tengo 2 tareas una para producción y otra para desarrollo, y una tarea de observación. Por lo que he aquí el contenido (perdonar por usar otro resaltador de código, no sé por que pero el que suelo usar no me funciona con este contenido):
module.exports = function(grunt) {
grunt.initConfig({
jade: {
develop: {
options:{
pretty: true,
data: function(){return {developing: "true"}; }
},
files:[{ expand: true, src: "*.jade", dest: "site/", ext: ".html", cwd: "sources/jade/pages" }]
},
production: {
options:{
pretty: false,
data: function(){return {developing: "false"}; }
},
files:[{ expand: true, src: "*.jade", dest: "site/", ext: ".html", cwd: "sources/jade/pages" }]
}
},
watch: {
jade: {
files: ["sources/jade/**/*.jade"],
tasks: ["notify:init_develop_compilation", "jade:develop"]
}
}
});
grunt.loadNpmTasks("grunt-contrib-jade");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.registerTask("default", ["jade:develop", "watch"]);
grunt.registerTask("production", ["jade:production"]);
};
Cada tarea jade recibe un objeto que uso en los fuentes de Jade para renderizar distintos bloques de contenido.
He estructurado mis fuentes de jade de esta forma:
I have structured my Jade sources in this way:
- jade
- extensible
- tuktuk_layout.jade
!!! 5
html(lang='en')
include ../includes/tuktuk/header
body
block content
include ../includes/tuktuk/footer-scripts
- includes
- tuktuk
- footer-scripts.jade
- header.jade
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1, maximum-scale=1')
- if (developing == "true")
link(rel='stylesheet', href='css/imports.css')
link(rel='stylesheet', href='css/libs/tuktuk/tuktuk.css')
link(rel='stylesheet', href='css/libs/tuktuk/tuktuk.theme.css')
link(rel='stylesheet', href='css/libs/tuktuk/tuktuk.icons.css')
link(rel='stylesheet', href='css/customization.css')
- else
link(rel='stylesheet', href='css/profile.min.css')
- pages
- index.jade
extends ../extensible/tuktuk_layout
block content
section.padding.bck.color
.row.main_hero
Growl notify para no tener que mirar el terminal.
Uno de los problemas de Grunt es que como és una herramienta de Node se lanza desde el terminal, así que la información de salida de muestra también en el terminal, y no me gusta tener otra ventana abierta.
Y aquí es donde grunt-notify viene a ayudar. Requiere Growl o Growl para Windows (en sistemas Windows aseguraros de que la ruta a Growl está incluida en la variable PATH del sistema). Así que instalarlo!:
npm install grunt-notify --save-dev
Y en el gruntfile:
module.exports = function(grunt) {
grunt.initConfig({
notify: {
init_develop_compilation: {
options: {
title: "Init DEV compilation",
message: "Tasks: stylus:develop, jade:develop, copy, watch"
}
},
end_develop_compilation: {
options: {
title: "Finished DEV compilation",
message: "Tasks: stylus:develop, jade:develop, copy, watch"
}
},
init_production_compilation: {
options: {
title: "Init PRO compilation",
message: "Tasks: jade:production, uglify, stylus:production, cssmin"
}
},
end_production_compilation: {
options: {
title: "Finished PRO compilation",
message: "Tasks: jade:production, uglify, stylus:production, cssmin"
}
},
init_tuktuk_compilation: {
options: {
title: "Init Tuktuk compilation",
message: "Tasks: stylus:tuktuk, coffee"
}
},
end_tuktuk_compilation: {
options: {
title: "Finished Tuktuk compilation",
message: "Tasks: stylus:tuktuk, coffee"
}
}
}
});
grunt.loadNpmTasks('grunt-notify');
grunt.registerTask("tuktuk", ["notify:init_tuktuk_compilation", "stylus:tuktuk", "copy", "coffee", "notify:end_tuktuk_compilation"]);
grunt.registerTask("default", ["notify:init_develop_compilation", "stylus:develop", "jade:develop", "watch", "notify:end_develop_compilation"]);
grunt.registerTask("develop", ["notify:init_develop_compilation","stylus:develop", "jade:develop", "copy", "notify:end_develop_compilation"]);
grunt.registerTask("production", ["notify:init_production_compilation", "jade:production","uglify","stylus:production", "cssmin", "notify:end_tuktuk_compilation"]);
};
Como puedes ver he puesto una notificación al comienzo de cada tarea para tener un notificación visual de cuando Grunt empieza a trabajar.
Cuando cualquier tarea falla se muestra una notificación, pero tienes que arrancar grunt con la opción "--force".
All together now!
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON("package.json"),
meta: {
cssEndpoint: "site/css",
stylusSourceBase: "sources/stylus/",
jsSourceBase: "sources/js/",
jsDestBase: "site/js/",
libsJsSourcesBasePath: "sources/libs",
libsJsEndPoint: "site/js/libs",
tuktuk: {
sourcesBasePath: "sources/tuktuk/sources",
file: "tuktuk",
cssEndpoint: "<%= meta.cssEndpoint %>/libs/tuktuk",
jsEndpoint: "<%= meta.libsJsEndPoint %>"
}
},
source: {
tuktuk: {
coffee: ["<%= meta.tuktuk.sourcesBasePath %>/tuktuk.coffee", "<%= meta.tuktuk.sourcesBasePath %>/tuktuk.*.coffee"],
stylus: ["<%= meta.tuktuk.sourcesBasePath %>/stylesheets/tuktuk.*.styl"],
theme: ["<%= meta.stylusSourceBase %>/theme.bu.styl"],
icons: "<%= meta.tuktuk.sourcesBasePath %>/components/lungo.icon/lungo.icon.css"
},
stylus: {
imports:["<%= meta.stylusSourceBase %>/imports.styl"],
customization: ["<%= meta.stylusSourceBase %>/variables.styl","<%= meta.stylusSourceBase %>/customization.styl"]
},
libsJs: {
jquery: ["<%= meta.libsJsSourcesBasePath %>/jquery.js"],
require: ["<%= meta.libsJsSourcesBasePath %>/require.js"],
zepto: ["<%= meta.libsJsSourcesBasePath %>/zepto.js"]
}
},
coffee: {
engine: {
options:{ join: true, },
files: { "<%= meta.tuktuk.jsEndpoint %>/<%= meta.tuktuk.file %>.js": ["<%= source.tuktuk.coffee %>"] }
}
},
stylus: {
tuktuk: {
options: { compress: false},
files: { '<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.css': '<%= source.tuktuk.stylus %>' }
},
develop: {
options: { compress: false},
files: {
'<%= meta.cssEndpoint %>/imports.css': '<%= source.stylus.imports %>',
'<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.theme.css': '<%= source.tuktuk.theme %>',
'<%= meta.cssEndpoint %>/customization.css': '<%= source.stylus.customization %>'
}
},
production: {
options: {compress: true },
files: {
'<%= meta.cssEndpoint %>/profile.min.css':[
'<%= source.stylus.imports %>',
'<%= source.tuktuk.stylus %>',
'<%= source.tuktuk.theme %>',
'<%= source.stylus.customization %>'
]
}
}
},
jade: {
develop: {
options:{
pretty: true,
data: function(){return {developing: "true"}; }
},
files:[{ expand: true, src: "*.jade", dest: "site/", ext: ".html", cwd: "sources/jade/pages" }]
},
production: {
options:{
pretty: false,
data: function(){return {developing: "false"}; }
},
files:[{ expand: true, src: "*.jade", dest: "site/", ext: ".html", cwd: "sources/jade/pages" }]
}
},
copy: {
main: {
files: [
{
src: '<%= source.tuktuk.icons %>',
dest: "<%= meta.tuktuk.cssEndpoint %>/<%=meta.tuktuk.file%>.icons.css"
},
{ expand: true,
cwd: "<%= meta.jsSourceBase %>",
src: ["**"],
dest: "<%= meta.jsDestBase %>"
}
]
}
},
uglify: {
separate: {
files: {
'site/js/app/config.js': '<%= meta.jsSourceBase %>/app/config.js',
'site/js/app/main.js': '<%= meta.jsSourceBase %>/app/main.js'
}
}
},
cssmin: {
combine: {
files: {
'<%= meta.cssEndpoint %>/profile.min.css': [
'<%= meta.cssEndpoint %>/profile.min.css',
'<%= meta.tuktuk.cssEndpoint %>/<%= meta.tuktuk.file%>.icons.css'
]
}
}
},
watch: {
stylus: {
files: ['<%= source.tuktuk.theme %>', '<%= source.stylus.customization %>'],
tasks: ["notify:init_develop_compilation", "stylus"]
},
jade: {
files: ["sources/jade/**/*.jade"],
tasks: ["notify:init_develop_compilation", "jade:develop"]
}
},
notify: {
init_develop_compilation: {
options: {
title: "Init DEV compilation",
message: "Tasks: stylus:develop, jade:develop, copy, watch"
}
},
end_develop_compilation: {
options: {
title: "Finished DEV compilation",
message: "Tasks: stylus:develop, jade:develop, copy, watch"
}
},
init_production_compilation: {
options: {
title: "Init PRO compilation",
message: "Tasks: jade:production, uglify, stylus:production, cssmin"
}
},
end_production_compilation: {
options: {
title: "Finished PRO compilation",
message: "Tasks: jade:production, uglify, stylus:production, cssmin"
}
},
init_tuktuk_compilation: {
options: {
title: "Init Tuktuk compilation",
message: "Tasks: stylus:tuktuk, coffee"
}
},
end_tuktuk_compilation: {
options: {
title: "Finished Tuktuk compilation",
message: "Tasks: stylus:tuktuk, coffee"
}
}
}
});
grunt.loadNpmTasks("grunt-contrib-coffee");
grunt.loadNpmTasks("grunt-contrib-stylus");
grunt.loadNpmTasks("grunt-contrib-jade");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-watch");
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-notify');
grunt.registerTask("tuktuk", ["notify:init_tuktuk_compilation", "stylus:tuktuk", "copy", "coffee", "notify:end_tuktuk_compilation"]);
grunt.registerTask("default", ["notify:init_develop_compilation", "stylus:develop", "jade:develop", "watch", "notify:end_develop_compilation"]);
grunt.registerTask("develop", ["notify:init_develop_compilation","stylus:develop", "jade:develop", "copy", "notify:end_develop_compilation"]);
grunt.registerTask("production", ["notify:init_production_compilation", "jade:production","uglify","stylus:production", "cssmin", "notify:end_tuktuk_compilation"]);
};
Plugins extra que usaré: