module.exports = function(grunt) {
grunt.initConfig({
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
globals: {
jQuery: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['jshint']);
};
-
Caractísticas
- Filosofía de configuración sobre codigo
- Basado en archivos temporales
- Ecosistema fuerte
- Poco flexible para cosas fuera de lo común
- Ficheros de configuración sobredimensionados
- Facilmente se desactualiza
-
Instalación
- Instalamos Gulp global
npm install -g grunt-cli
- Incluimos la dependencia en package.json
npm install grunt --save-dev
-
Tareas por defecto
- Creamos gruntfile.js y encapsulamos
- Definición
module.exports = function(grunt) { //... resto del código de grunt... };
- Instanciamos la configuración
module.exports = function(grunt) { grunt.initConfig({ //...resto de configuración... }) //... resto del código de grunt... };
- Cargamos las dependencias (plugins)...
module.exports = function(grunt) { grunt.initConfig({ //...resto de configuración... }) //... resto del código de grunt... }; grunt.loadNpmTasks('grunt-contrib-NOMBRE-PLUGIN'); grunt.loadNpmTasks('grunt-contrib-NOMBRE-PLUGIN'); //...
- Creamos la tarea por defecto
module.exports = function(grunt) { grunt.initConfig({ jshint: { files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], options: { // options here to override JSHint defaults globals: { jQuery: true, console: true, module: true, document: true } } } }) }; grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', ['jshint']);
- Creamos tarea watch...
module.exports = function(grunt) { grunt.initConfig({ jshint: { files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], options: { // options here to override JSHint defaults globals: { jQuery: true, console: true, module: true, document: true } } }, watch: { files: ['<%= jshint.files %>'], tasks: ['jshint'] } }) }; grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', ['jshint']);
-
Lanzamiento
- Lanzar la tarea por defecto
grunt
- Lanzar el watch
grunt watch
- lanzar una tarea personalizada
grunt TAREA
- Lanzar la tarea por defecto
- The 26 grunt plugins we use at qrator.com | Andy's Blog
- Living list of most useful plugins for Grunt
- 6 essential Grunt plugins you should be using | Creative Bloq
- 7 Essential Plug-ins for those New to GruntJS - Web Design Ledger
- Essential Grunt Plugins - Donna Peplinskie
- Psychedelic Grunt Plugins - Web Design Weekly
- 5 Grunt Plugins for a Better Workflow | Chase Adams
- Building a Better Grunt Plugin | Sprout Social
- Supercharging your Gruntfile
- Take Grunt to the Next Level — Jonathan Suh
Grunt: gruntfile.js
// Generated on 2018-03-31 using
// generator-webapp 1.1.2
'use strict';
// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// If you want to recursively match all subfolders, use:
// 'test/spec/**/*.js'
module.exports = function (grunt) {
// Time how long tasks take. Can help when optimizing build times
require('time-grunt')(grunt);
// Automatically load required grunt tasks
require('jit-grunt')(grunt, {
useminPrepare: 'grunt-usemin'
});
// Configurable paths
var config = {
app: 'app',
dist: 'dist'
};
// Define the configuration for all the tasks
grunt.initConfig({
// Project settings
config: config,
// Watches files for changes and runs tasks based on the changed files
watch: {
bower: {
files: ['bower.json'],
tasks: ['wiredep']
},
babel: {
files: ['<%= config.app %>/scripts/{,*/}*.js'],
tasks: ['babel:dist']
},
babelTest: {
files: ['test/spec/{,*/}*.js'],
tasks: ['babel:test', 'test:watch']
},
gruntfile: {
files: ['Gruntfile.js']
},
sass: {
files: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'],
tasks: ['sass', 'postcss']
},
styles: {
files: ['<%= config.app %>/styles/{,*/}*.css'],
tasks: ['newer:copy:styles', 'postcss']
}
},
browserSync: {
options: {
notify: false,
background: true,
watchOptions: {
ignored: ''
}
},
livereload: {
options: {
files: [
'<%= config.app %>/{,*/}*.html',
'.tmp/styles/{,*/}*.css',
'<%= config.app %>/images/{,*/}*',
'.tmp/scripts/{,*/}*.js'
],
port: 9000,
server: {
baseDir: ['.tmp', config.app],
routes: {
'/bower_components': './bower_components'
}
}
}
},
test: {
options: {
port: 9001,
open: false,
logLevel: 'silent',
host: 'localhost',
server: {
baseDir: ['.tmp', './test', config.app],
routes: {
'/bower_components': './bower_components'
}
}
}
},
dist: {
options: {
background: false,
server: '<%= config.dist %>'
}
}
},
// Empties folders to start fresh
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= config.dist %>/*',
'!<%= config.dist %>/.git*'
]
}]
},
server: '.tmp'
},
// Make sure code styles are up to par and there are no obvious mistakes
eslint: {
target: [
'Gruntfile.js',
'<%= config.app %>/scripts/{,*/}*.js',
'!<%= config.app %>/scripts/vendor/*',
'test/spec/{,*/}*.js'
]
},
// Mocha testing framework configuration options
mocha: {
all: {
options: {
run: true,
urls: ['http://<%= browserSync.test.options.host %>:<%= browserSync.test.options.port %>/index.html']
}
}
},
// Compiles ES6 with Babel
babel: {
options: {
sourceMap: true,
presets: ['es2015']
},
dist: {
files: [{
expand: true,
cwd: '<%= config.app %>/scripts',
src: '{,*/}*.js',
dest: '.tmp/scripts',
ext: '.js'
}]
},
test: {
files: [{
expand: true,
cwd: 'test/spec',
src: '{,*/}*.js',
dest: '.tmp/spec',
ext: '.js'
}]
}
},
// Compiles Sass to CSS and generates necessary files if requested
sass: {
options: {
sourceMap: true,
sourceMapEmbed: true,
sourceMapContents: true,
includePaths: ['.']
},
dist: {
files: [{
expand: true,
cwd: '<%= config.app %>/styles',
src: ['*.{scss,sass}'],
dest: '.tmp/styles',
ext: '.css'
}]
}
},
postcss: {
options: {
map: true,
processors: [
// Add vendor prefixed styles
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']
})
]
},
dist: {
files: [{
expand: true,
cwd: '.tmp/styles/',
src: '{,*/}*.css',
dest: '.tmp/styles/'
}]
}
},
// Automatically inject Bower components into the HTML file
wiredep: {
app: {
src: ['<%= config.app %>/index.html'],
exclude: ['bootstrap.js'],
ignorePath: /^(\.\.\/)*\.\./
},
sass: {
src: ['<%= config.app %>/styles/{,*/}*.{scss,sass}'],
ignorePath: /^(\.\.\/)+/
}
},
// Renames files for browser caching purposes
filerev: {
dist: {
src: [
'<%= config.dist %>/scripts/{,*/}*.js',
'<%= config.dist %>/styles/{,*/}*.css',
'<%= config.dist %>/images/{,*/}*.*',
'<%= config.dist %>/fonts/{,*/}*.*',
'<%= config.dist %>/*.{ico,png}'
]
}
},
// Reads HTML for usemin blocks to enable smart builds that automatically
// concat, minify and revision files. Creates configurations in memory so
// additional tasks can operate on them
useminPrepare: {
options: {
dest: '<%= config.dist %>'
},
html: '<%= config.app %>/index.html'
},
// Performs rewrites based on rev and the useminPrepare configuration
usemin: {
options: {
assetsDirs: [
'<%= config.dist %>',
'<%= config.dist %>/images',
'<%= config.dist %>/styles'
]
},
html: ['<%= config.dist %>/{,*/}*.html'],
css: ['<%= config.dist %>/styles/{,*/}*.css']
},
// The following *-min tasks produce minified files in the dist folder
imagemin: {
dist: {
files: [{
expand: true,
cwd: '<%= config.app %>/images',
src: '{,*/}*.{gif,jpeg,jpg,png}',
dest: '<%= config.dist %>/images'
},{
expand: true,
cwd: '<%= config.app %>',
src: '*.{ico,png}',
dest: '<%= config.dist %>'
}]
}
},
svgmin: {
dist: {
files: [{
expand: true,
cwd: '<%= config.app %>/images',
src: '{,*/}*.svg',
dest: '<%= config.dist %>/images'
}]
}
},
htmlmin: {
dist: {
options: {
collapseBooleanAttributes: true,
collapseWhitespace: true,
conservativeCollapse: true,
removeAttributeQuotes: true,
removeCommentsFromCDATA: true,
removeEmptyAttributes: true,
removeOptionalTags: true,
// true would impact styles with attribute selectors
removeRedundantAttributes: false,
useShortDoctype: true
},
files: [{
expand: true,
cwd: '<%= config.dist %>',
src: '{,*/}*.html',
dest: '<%= config.dist %>'
}]
}
},
// By default, your `index.html`'s <!-- Usemin block --> will take care
// of minification. These next options are pre-configured if you do not
// wish to use the Usemin blocks.
// cssmin: {
// dist: {
// files: {
// '<%= config.dist %>/styles/main.css': [
// '.tmp/styles/{,*/}*.css',
// '<%= config.app %>/styles/{,*/}*.css'
// ]
// }
// }
// },
// uglify: {
// dist: {
// files: {
// '<%= config.dist %>/scripts/scripts.js': [
// '<%= config.dist %>/scripts/scripts.js'
// ]
// }
// }
// },
// concat: {
// dist: {}
// },
// Copies remaining files to places other tasks can use
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= config.app %>',
dest: '<%= config.dist %>',
src: [
'*.txt',
'images/{,*/}*.webp',
'{,*/}*.html',
'fonts/{,*/}*.*'
]
}, {
expand: true,
dot: true,
cwd: '.',
src: 'bower_components/bootstrap-sass/assets/fonts/bootstrap/*',
dest: '<%= config.dist %>'
}]
}
},
// Generates a custom Modernizr build that includes only the tests you
// reference in your app
modernizr: {
dist: {
devFile: 'bower_components/modernizr/modernizr.js',
outputFile: '<%= config.dist %>/scripts/vendor/modernizr.js',
files: {
src: [
'<%= config.dist %>/scripts/{,*/}*.js',
'<%= config.dist %>/styles/{,*/}*.css',
'!<%= config.dist %>/scripts/vendor/*'
]
},
uglify: true
}
},
// Run some tasks in parallel to speed up build process
concurrent: {
server: [
'babel:dist',
'sass'
],
test: [
'babel'
],
dist: [
'babel',
'sass',
'imagemin',
'svgmin'
]
}
});
grunt.registerTask('serve', 'start the server and preview your app', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'browserSync:dist']);
}
grunt.task.run([
'clean:server',
'wiredep',
'concurrent:server',
'postcss',
'browserSync:livereload',
'watch'
]);
});
grunt.registerTask('server', function (target) {
grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
grunt.task.run([target ? ('serve:' + target) : 'serve']);
});
grunt.registerTask('test', function (target) {
if (target !== 'watch') {
grunt.task.run([
'clean:server',
'concurrent:test',
'postcss'
]);
}
grunt.task.run([
'browserSync:test',
'mocha'
]);
});
grunt.registerTask('build', [
'clean:dist',
'wiredep',
'useminPrepare',
'concurrent:dist',
'postcss',
'concat',
'cssmin',
'uglify',
'copy:dist',
'modernizr',
'filerev',
'usemin',
'htmlmin'
]);
grunt.registerTask('default', [
'newer:eslint',
'test',
'build'
]);
};
-
Caractísticas
- Filosofía de código sobre configuración
- Basado en stream
- No es necesario usar archivos temporales
- Claridad en creación de tareas y seguimiento de procesos
- Gran cantidad de Plugins
- Cuenta con una comunidad sólida y madura
- El sistema de
streams
yPromises
no es sencillo para developers juniors
-
Instalación
- Instalamos Gulp global
npm install --global gulp
- Incluimos la dependencia en package.json
npm install --save-dev gulp
-
Tareas por defecto
- Creamos gulpfile.js y agregamos dependencias y la primera tarea por defecto
- Definición
var gulp = require('gulp'); gulp.task('default', function() { console.log("Estas en la tarea por defecto!") });
- Lanzamiento
gulp
-
Más tareas
- Creamos una tarea nueva para gestionar la concatenación y minificación de los archivos js.
- Definición
var gulp = require('gulp'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); gulp.task('concat-ugly', function() { console.log("Estas en la tarea de concatenación!") gulp.src('js/sources/*.js') .pipe(concat('app.min.js')) .pipe(uglify()) .pipe(gulp.dest('dist/js')) });
- Instalamos las nuevas dependencias
npm install -save gulp-concat && npm install -save gulp-uglify
- Lanzamiento
gulp concat-ugly
-
Agrupando tareas
- Definición
gulp.task('distro-lista', ['imagenes', 'css', 'js']);
- Lanzamiento
gulp distro-lista
-
Concatenando tareas
- Definición
gulp.task('css-paso-2', ['css-paso-1'], function( console.log("css-paso-2 empieza solo cuando... css-paso-1 haya termiando!") ));
- Lanzamiento
gulp css-paso-2
Entendiendo Gulp
- gulp.src() y gulp.dest()
- Un solo archivo
gulp.src('client/templates/index.jade') // .pipe(...)
- Múltiples archivos
gulp.src(['client/*.js', '!client/b*.js', 'client/bad.js']) // .pipe(...)
- Múltiples archivos y carpetas
gulp.src('client/templates/**/*.jade') // .pipe(...)
- Exclusión
!js/secreto-config.js
- Especificando la extensión
publico/*.+(js|css)
- gulp.watch()
- Monitoriza de manera activa uno o varios archivos y dispara tareas específicas cuando se hayan modificado
gulp.watch('js/source/*.js', ['js']);
- gulp-debug: BÁSICO para debugear las tareas de GULP y los ficheros en scope de la tarea
- gulp-concat: Concatenación de archivos
- gulp-uglify: Comprime javascript usando UglifyJS2
- gulp-stylus: Compilar de .styl a .css
- gulp-coffee: Compilar de .coffee a .js
- gulp-jade: Compilador de .jade a .html
- gulp-if: Control adiccional para el flujo de subtareas
- gulp-imagemin: Minificación de imágenes con formato .png, .jpeg, .gif y .svg, más opciones
- gulp-jshint: JSHint
- gulp.spritesmith: Crea Sprites y el css adiccional en diversos formatos (.css, .json, Sass, Less)
- gulp-zip: Compresor ZIP
- gulp-csslint: CSS Linter
- gulp-eslint: ESLint
- gulp-gh-pages: Gestiona la publicación en GitHub Pages
- gulp-git: Gestiona Git desde Gulp
- gulp-htmlmin: Minificador de HTML
- gulp-iconfont: Creando fuentes de Iconos desde archivos vectoriales
- gulp-jsonlint: Linter para json
- gulp-markdown: Markdown a HTML
- gulp-sourcemaps: Crea SourceMaps
- gulp-uncss: Elimina CSS que no se use
- gulp-jsdoc-to-markdown: Conversor de jsdocs a markdown
- gulp-unzip: Descompresor ZIP
- gulp-webstandards: Verifica prefijos CSS, Versión de librerías js, dcoType, compatibildiad entre navegadores, etc...
- gulp-filesize: Muestra el tamaño de los archivos.
- gulp-grunt: Tareas de Grutn funcionan en Gulp
- gulp-shell: Manejando comandos de terminal
- Easy Accessibility Testing with aXe
- puppeteer: Headless Chrome Node API
- pageres: Genera pantallazos de la web en diversos tamaños
- PSI: PageSpeed Insights desde la terminal
- Lighthouse: Auditing, performance metrics, and best practices for Progressive Web Apps
Gulp: gulpfile.js
// generated on 2018-03-31 using generator-webapp 3.0.1
const gulp = require('gulp');
const gulpLoadPlugins = require('gulp-load-plugins');
const browserSync = require('browser-sync').create();
const del = require('del');
const wiredep = require('wiredep').stream;
const runSequence = require('run-sequence');
const $ = gulpLoadPlugins();
const reload = browserSync.reload;
let dev = true;
gulp.task('styles', () => {
return gulp.src('app/styles/*.scss')
.pipe($.plumber())
.pipe($.if(dev, $.sourcemaps.init()))
.pipe($.sass.sync({
outputStyle: 'expanded',
precision: 10,
includePaths: ['.']
}).on('error', $.sass.logError))
.pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']}))
.pipe($.if(dev, $.sourcemaps.write()))
.pipe(gulp.dest('.tmp/styles'))
.pipe(reload({stream: true}));
});
gulp.task('scripts', () => {
return gulp.src('app/scripts/**/*.js')
.pipe($.plumber())
.pipe($.if(dev, $.sourcemaps.init()))
.pipe($.babel())
.pipe($.if(dev, $.sourcemaps.write('.')))
.pipe(gulp.dest('.tmp/scripts'))
.pipe(reload({stream: true}));
});
function lint(files) {
return gulp.src(files)
.pipe($.eslint({ fix: true }))
.pipe(reload({stream: true, once: true}))
.pipe($.eslint.format())
.pipe($.if(!browserSync.active, $.eslint.failAfterError()));
}
gulp.task('lint', () => {
return lint('app/scripts/**/*.js')
.pipe(gulp.dest('app/scripts'));
});
gulp.task('lint:test', () => {
return lint('test/spec/**/*.js')
.pipe(gulp.dest('test/spec'));
});
gulp.task('html', ['styles', 'scripts'], () => {
return gulp.src('app/*.html')
.pipe($.useref({searchPath: ['.tmp', 'app', '.']}))
.pipe($.if(/\.js$/, $.uglify({compress: {drop_console: true}})))
.pipe($.if(/\.css$/, $.cssnano({safe: true, autoprefixer: false})))
.pipe($.if(/\.html$/, $.htmlmin({
collapseWhitespace: true,
minifyCSS: true,
minifyJS: {compress: {drop_console: true}},
processConditionalComments: true,
removeComments: true,
removeEmptyAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
})))
.pipe(gulp.dest('dist'));
});
gulp.task('images', () => {
return gulp.src('app/images/**/*')
.pipe($.cache($.imagemin()))
.pipe(gulp.dest('dist/images'));
});
gulp.task('fonts', () => {
return gulp.src(require('main-bower-files')('**/*.{eot,svg,ttf,woff,woff2}', function (err) {})
.concat('app/fonts/**/*'))
.pipe($.if(dev, gulp.dest('.tmp/fonts'), gulp.dest('dist/fonts')));
});
gulp.task('extras', () => {
return gulp.src([
'app/*',
'!app/*.html'
], {
dot: true
}).pipe(gulp.dest('dist'));
});
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
gulp.task('serve', () => {
runSequence(['clean', 'wiredep'], ['styles', 'scripts', 'fonts'], () => {
browserSync.init({
notify: false,
port: 9000,
server: {
baseDir: ['.tmp', 'app'],
routes: {
'/bower_components': 'bower_components'
}
}
});
gulp.watch([
'app/*.html',
'app/images/**/*',
'.tmp/fonts/**/*'
]).on('change', reload);
gulp.watch('app/styles/**/*.scss', ['styles']);
gulp.watch('app/scripts/**/*.js', ['scripts']);
gulp.watch('app/fonts/**/*', ['fonts']);
gulp.watch('bower.json', ['wiredep', 'fonts']);
});
});
gulp.task('serve:dist', ['default'], () => {
browserSync.init({
notify: false,
port: 9000,
server: {
baseDir: ['dist']
}
});
});
gulp.task('serve:test', ['scripts'], () => {
browserSync.init({
notify: false,
port: 9000,
ui: false,
server: {
baseDir: 'test',
routes: {
'/scripts': '.tmp/scripts',
'/bower_components': 'bower_components'
}
}
});
gulp.watch('app/scripts/**/*.js', ['scripts']);
gulp.watch(['test/spec/**/*.js', 'test/index.html']).on('change', reload);
gulp.watch('test/spec/**/*.js', ['lint:test']);
});
// inject bower components
gulp.task('wiredep', () => {
gulp.src('app/styles/*.scss')
.pipe($.filter(file => file.stat && file.stat.size))
.pipe(wiredep({
ignorePath: /^(\.\.\/)+/
}))
.pipe(gulp.dest('app/styles'));
gulp.src('app/*.html')
.pipe(wiredep({
exclude: ['bootstrap-sass'],
ignorePath: /^(\.\.\/)*\.\./
}))
.pipe(gulp.dest('app'));
});
gulp.task('build', ['lint', 'html', 'images', 'fonts', 'extras'], () => {
return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});
gulp.task('default', () => {
return new Promise(resolve => {
dev = false;
runSequence(['clean', 'wiredep'], 'build', resolve);
});
});
- Nuevo sistema de dependencias
gulp.task()
solo admite 2 argumentos gulp.series()
y 'gulp.parallel()' son ahora nativas. ¡Adios runSequence!.- El orden de las tareas es clave y ya no podemos organizarlas como queramos, deben ir por orden de carga
- Es necesario terminar cada tarea con un callback o resolver la promesa. En Gulp3 no era necesario.
- Por el momento tiene poca adopción
Si tienes un
gulpile.js
funcionando con Gulp 3.x, no merece la pena migrarse.
Antes - Gulp 3.9.x
gulp.task('a', function () {
// Do something.
});
gulp.task('b', ['a'], function () {
// Do some stuff.
});
gulp.task('c', ['b'], function () {
// Do some more stuff.
});
Ahora - Gulp 4.x
- Las tareas solo tienen 2 argumentos.
- La concatenación de tareas puede ser serializada con
gulp.series()
o paralelizada con 'gulp.parallel()'
gulp.task('my-tasks', gulp.series('a', 'b', 'c', function() {
// Do something after a, b, and c are finished.
}));
gulp.task('build', gulp.parallel('styles', 'scripts', 'images', function () {
// Build the website.
}));
- The Complete-Ish Guide to Upgrading to Gulp 4
- Web Tooling and Automatisation using gulp 4
- How to install Gulp 4 before it's officially released
- Migrating to gulp 4 by example
- How to Upgrade to Gulp 4
- Getting started with Gulp 4 for Angular
- A quick guide for switching to gulp 4
var gulp = require('gulp');
var less = require('gulp-less');
var babel = require('gulp-babel');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var cleanCSS = require('gulp-clean-css');
var del = require('del');
var paths = {
styles: {
src: 'src/styles/**/*.less',
dest: 'assets/styles/'
},
scripts: {
src: 'src/scripts/**/*.js',
dest: 'assets/scripts/'
}
};
/* Not all tasks need to use streams, a gulpfile is just another node program
* and you can use all packages available on npm, but it must return either a
* Promise, a Stream or take a callback and call it
*/
function clean() {
// You can use multiple globbing patterns as you would with `gulp.src`,
// for example if you are using del 2.0 or above, return its promise
return del([ 'assets' ]);
}
/*
* Define our tasks using plain functions
*/
function styles() {
return gulp.src(paths.styles.src)
.pipe(less())
.pipe(cleanCSS())
// pass in options to the stream
.pipe(rename({
basename: 'main',
suffix: '.min'
}))
.pipe(gulp.dest(paths.styles.dest));
}
function scripts() {
return gulp.src(paths.scripts.src, { sourcemaps: true })
.pipe(babel())
.pipe(uglify())
.pipe(concat('main.min.js'))
.pipe(gulp.dest(paths.scripts.dest));
}
function watch() {
gulp.watch(paths.scripts.src, scripts);
gulp.watch(paths.styles.src, styles);
}
/*
* You can use CommonJS `exports` module notation to declare tasks
*/
exports.clean = clean;
exports.styles = styles;
exports.scripts = scripts;
exports.watch = watch;
/*
* Specify if tasks run in series or parallel using `gulp.series` and `gulp.parallel`
*/
var build = gulp.series(clean, gulp.parallel(styles, scripts));
/*
* You can still use `gulp.task` to expose tasks
*/
gulp.task('build', build);
/*
* Define default task that can be called by just running `gulp` from cli
*/
gulp.task('default', build);
Grunt:
Gulp:
¿Por que abandonar los tasks runners?
- dependencias con poco mantenimiento desarrolladas por terceras partes
- La complejidad para depurar el sistema
- Recopilar documentación apra muchas combinaciones es muy dificultoso
- Abstracciones muy complejas con muchos puntos debiles que no vemos
- Dependencias, dependencias y más dependencias...
¿Qué me aporta NPM Scripts?
- Portabildiad entre plataformas
- No es necesario tener un gran conocimiento de terminal
- Todo el poder de la terminal y comandos del Unix / Linux como
grep
,sed
,awk
,pipes
, etc... - +200k paquetes disponibles vs +5k de Grunt o +3k de Gulp
- Añadiendo comandos:
// ...
"scripts": {
"test": "npm -v",
"start": "node -v",
"hola": "echo 'Hola mundo!'"
}
// ...
- Mostrando todos los comandos:
npm run
- Ejecutando comandos:
- test
npm test
- start
npm start
- hola
npm run hola
Recursos
- Why I Left Gulp and Grunt for npm Scripts by Cory House
- Task automation with npm run by James Holliday
- Advanced front-end automation with npm scripts by Kate Hudson
- How to use npm as a build tool by Kieth Cirkel
- Introduction to npm as a Build Tool by Marcus Hammarberg
- Gulp is awesome, but do we really need it? by Gonto
- No conoces a Ashley Williams ni has visto "You Don't Know npm". Slides
- Existen convenciones en el
package.json
que solemos ignorar comopreinstall
,install
,postinstall
,uninstall
,test
,start
, etc... - Puedes pasarte variables de entorno en los comandos
NODE_ENV=production server.js
- Puedes ejecutar otros comandos del
package.json
, con solo mencionarlosnpm run otro-comando && echo otras cosas después...
- Si quieres mantener soporte con todas las plataformas, debes evitar el uso de comandos de shell y abstraerlo con dependencias. Por ejemplo
rm -rf /
vs rimraf - Puedes agrupar comandos en ficheros y siplemente ejecutarlos como una tarea más,
node fichero-comandos.js
- No agrupes comandos complejos o muy largos, divide y venceras
- Recuerda que existen multiples operadores en Linux y OSX:
- Puedes paralelismo dentro del mismo comando usando el operador
&
y no unicamente&&
, ejemplo:npm run watch-js & npm run watch-css
- Puedes unir dos tareas usando
&&
. La seguna tarea solo se ejecutará cuando la primera haya concluido con éxito, ejemplo:npm install && npm start
- Puedes usar
;
para ejecutar el siguiente comando aun cuando el primero no funcionó correctamente, ejemplo:npm run algo-raro; npm run watch-css
- Puedes usar
||
para ejecutar el siguiente comando solo cuando el primero no funcionó correctamente, ejemplo:npm install-unix || npm install windows
- Puedes combinar comandos como
[ -f ~/fichero.txt ] && echo "el fichero existe." || touch ~/fichero.txt
, es decir, si el fichero existe me avisa, pero si no... lo crea!
- Puedes paralelismo dentro del mismo comando usando el operador
- Si un comando es muy largo... solo necesitas crearte un
fichero.sh
encabezado por#!/bin/bash
y agrupando comandos con parentesis. No te olvides de los permisos de ejecuación conchmod +x FICHERO.sh
#!/bin/bash
(cd site/main; echo banana)
(cd site/xyz; echo lorem ipsum...)