Maarten van Spil Maarten van Spil - 1 year ago 138
Node.js Question

Parse main bower files (js, css, scss, images) to distribution via Gulp

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:

- 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','123'));

// 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)

// Sass Task
gulp.task('sass', ['sprite'], function() {
return gulp.src(stylesSrc)
errorHandler: onSassError
includePaths: [
outputStyle: 'compressed'

// Concatenate & Minify JS
gulp.task('script', function() {
return gulp.src(scriptSrc)

// 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')

var frontend = gulp.src(mainBowerFiles())

return merge(modernizr, frontend);

// Imagemin Task (compress images)
gulp.task('imagemin', function () {
return gulp.src([imgSrc, '!src/images/sprite{,/**}'])
use: [pngquant()]

// Compile sass into CSS & auto-inject into browsers
gulp.task('browser-sync', function() {
// Serve files from the root of this project
proxy: "localhost/insyde/website_v6_devtools/site"

// add browserSync.reload to the tasks array to make
// all browsers reload after tasks are complete."./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
width: '50%',
height: '50%'
}), os.cpus().length
.pipe(rename(function(path) {
path.basename = path.basename.slice(0, -3); //remove @2x label

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 CSS stream through CSS optimizer and onto disk
var cssStream = spriteData.css

// Return a merged stream to handle both `end` events
return merge(imgStream, cssStream);

// Watch Files For Changes
gulp.task('watch', function() {'./src/script/**/*.js', ['lint', 'script']);'./src/styles/**/*.scss', ['sass']);'./src/images/**', ['imagemin']);'./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);


Got it working!

I used this as an example:

What these tasks do is:

bower:assets Copy all asset files (images and fonts) that are defined in the 'main' property of bower packages (which we find by using main-bower-files) to site/dist/ while keeping the original folder layout of the packages themself.

bower:styles Parse each stylesheet that comes from main-bower-files (excluding two packages: foundation and compass-mixins) and rework the urls that point to the images and fonts that we copied earlier. This differs from the example in a way that the files in my situation aren't first copied to a .tmp directory, but get dealed with and then written to the site/css folder directly. I concatenate and minify the css, while using sourcemaps to make debugging easier.

//copy bower assets that need copying
gulp.task('bower:assets', function() {
    return gulp.src(mainBowerFiles(), {
        base: './bower_components'

//generate bower stylesheets with correct asset paths
gulp.task('bower:styles', function() {
    return gulp.src(mainBowerFiles(), {
        base: './bower_components'
    .pipe(foreach(function(stream, file) {
        var dirName = path.dirname(file.path);
        return stream
            .pipe(rework(reworkUrl(function(url) {
                var fullUrl = path.join(dirName, url);
                if (fs.existsSync(fullUrl)) {
                    console.log(path.relative('css', fullUrl).replace(/bower_components/, 'dist'));
                    return path.relative('css', fullUrl).replace(/bower_components/, 'dist');
                return url;

This all results in the following directory sturcture:

  • bower_components
  • site
    • css
      • bower.css
    • dist
      • bower-component-a
        • images
          • arrow.png
          • gradient.png
      • bower-component-b
        • images
          • arrow.png
          • gradient.png