We have recently switched from using a PHP asset manager to Gulp. We use bower to pull in our frontend packages and their dependencies. Using simple Bower packages that only have JS files listed in 'main' is pretty straightforward and is easily done. Using 'main-bower-files' we grab the required js files and concatenate them into one script file which we send to our site/script folder. The fonts we can collect and move to a fonts/ folder in our site. Then we hit a wall... What to do with images and their paths in the corresponding css/scss files. To further complicate things we want images to keep some of their original folder layout so they don't get overridden. We want to grab the css and scss (we use libsass) files and merge them with our own styles into one .css file. But how can we make sure they still have working paths to the images that come with them.
The desired folder layout is as follows:
bower.json
gulpfile.js
package.json
bower_components/
site/
- css/styles.css
- fonts/
- images/
- bower-package-a/
- arrow.png
- gradient.png
- themea-a/
- arrow.png
- theme-b/
- arrow.png
- bower-package-b/
- arrow.png
- gradient.png
- script/
This is our Gulpfile so far:
// Include gulp
var gulp = require('gulp');
// Include Plugins
var bower = require('gulp-bower');
var browserSync = require('browser-sync').create();
var concat = require('gulp-concat');
var del = require('del');
var filter = require('gulp-filter');
var gutil = require('gulp-util');
var imagemin = require('gulp-imagemin');
var jshint = require('gulp-jshint');
var mainBowerFiles = require('main-bower-files');
var merge = require('merge-stream');
var newer = require('gulp-newer');
var plumber = require('gulp-plumber');
var pngquant = require('imagemin-pngquant');
var rename = require('gulp-rename');
var sass = require('gulp-sass');
var sourcemaps = require('gulp-sourcemaps');
var changed = require('gulp-changed');
var parallel = require("concurrent-transform");
var os = require("os");
var imageResize = require('gulp-image-resize');
var spritesmith = require('gulp.spritesmith');
var uglify = require('gulp-uglify');
// Paden
var bowerDest = 'site/script/lib/bower';
var imgSrc = 'src/images/**';
var imgDest = 'site/images';
var spriteImgDest = './src/images/sprite/';
var scriptSrc = 'src/script/**/*.js';
var scriptDest = 'site/script';
var stylesSrc = 'src/styles/styles.scss';
var stylesDest = 'site/css';
// Helpers
var onSassError = function(error) {
gutil.log('Sass Error', gutil.colors.red('123'));
gutil.beep();
console.log(error);
this.emit('end');
}
// Clean images Task
gulp.task('clean:images', function () {
return del([imgDest]);
});
// Clean script Task
gulp.task('clean:script', function () {
return del([scriptDest]);
});
// Clean image Task
gulp.task('clean:styles', function () {
return del([stylesDest]);
});
// Lint Task
gulp.task('lint', function() {
return gulp.src(scriptSrc)
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'))
;
});
// Sass Task
gulp.task('sass', ['sprite'], function() {
return gulp.src(stylesSrc)
.pipe(plumber({
errorHandler: onSassError
}))
.pipe(sourcemaps.init())
.pipe(sass({
includePaths: [
'bower_components/compass-mixins/lib',
'bower_components/foundation/scss'
],
outputStyle: 'compressed'
}))
.pipe(sourcemaps.write('./map'))
.pipe(gulp.dest(stylesDest))
.pipe(browserSync.stream())
;
});
// Concatenate & Minify JS
gulp.task('script', function() {
return gulp.src(scriptSrc)
.pipe(concat('script.js'))
.pipe(gulp.dest(scriptDest))
.pipe(rename('script.min.js'))
.pipe(uglify())
.pipe(gulp.dest(scriptDest))
;
});
// Voeg de JS bestanden die gebruikt worden vanuit bower samen. Let op: modernizr volgt niet de standaard
// en wordt daarom niet meegenomen in mainBowerFiles(). Deze voegen we dus los toe.
gulp.task('bower:js', function() {
var modernizr = gulp.src('bower_components/modernizr/modernizr.js')
.pipe(rename('modernizr.min.js'))
.pipe(uglify())
.pipe(gulp.dest(scriptDest))
;
var frontend = gulp.src(mainBowerFiles())
.pipe(sourcemaps.init())
.pipe(filter('*.js'))
.pipe(concat('frontend.min.js'))
.pipe(uglify())
.pipe(sourcemaps.write('maps'))
.pipe(gulp.dest(scriptDest))
;
return merge(modernizr, frontend);
});
// Imagemin Task (compress images)
gulp.task('imagemin', function () {
return gulp.src([imgSrc, '!src/images/sprite{,/**}'])
.pipe(newer(imgDest))
.pipe(imagemin({
use: [pngquant()]
}))
.pipe(gulp.dest(imgDest))
.pipe(browserSync.stream())
;
});
// Compile sass into CSS & auto-inject into browsers
gulp.task('browser-sync', function() {
// Serve files from the root of this project
browserSync.init({
proxy: "localhost/insyde/website_v6_devtools/site"
});
// add browserSync.reload to the tasks array to make
// all browsers reload after tasks are complete.
gulp.watch("./src/script/**/*.js", ['scripts-watch']);
});
// generate the x1 images from the big ones
gulp.task('generate-small-sprite-images', function () {
return gulp.src('./src/images/sprite/*-2x.png')
.pipe(newer(rename(function(path) {
path.basename = path.basename.slice(0, -3); //remove @2x label
})))
.pipe(parallel(
imageResize({
width: '50%',
height: '50%'
}), os.cpus().length
))
.pipe(rename(function(path) {
path.basename = path.basename.slice(0, -3); //remove @2x label
}))
.pipe(gulp.dest(spriteImgDest))
;});
gulp.task('sprite', ['generate-small-sprite-images'], function () {
var spriteData = gulp.src('./src/images/sprite/**/*.png').pipe(spritesmith({
imgName: 'sprite.png',
retinaImgName: 'sprite-2x.png',
cssName: 'sprite.scss',
imgPath: '../images/sprite.png',
retinaImgPath : '../images/sprite-2x.png',
retinaSrcFilter: '**/*-2x.png'
}));
// Pipe image stream through image optimizer and onto disk
var imgStream = spriteData.img
.pipe(imagemin())
.pipe(gulp.dest(imgDest));
// Pipe CSS stream through CSS optimizer and onto disk
var cssStream = spriteData.css
.pipe(gulp.dest('./src/styles/'));
// Return a merged stream to handle both `end` events
return merge(imgStream, cssStream);
});
// Watch Files For Changes
gulp.task('watch', function() {
gulp.watch('./src/script/**/*.js', ['lint', 'script']);
gulp.watch('./src/styles/**/*.scss', ['sass']);
gulp.watch('./src/images/**', ['imagemin']);
gulp.watch('./templates/**/*.html').on('change', browserSync.reload);
});
// Default Tasks
gulp.task('default', ['lint', 'sass', 'bower:js', 'script', 'imagemin', 'watch']);
gulp.task('frontend', ['lint', 'sprite', 'sass', 'bower:js', 'script', 'imagemin', 'browser-sync', 'watch']);
gulp.task('clean', ['clean:images', 'clean:script', 'clean:styles']);
// create a task that ensures the `scripts` task is complete before
// reloading browsers
gulp.task('scripts-watch', ['script'], browserSync.reload);