Menu icon Foundation
Foundation For Email - Screenshots (Webshots)

Hey guys when I was playing with Ink v1. I built a grunt flow that used phantomjs to generate desktop and mobile screenshots for our emails. Our team found it super helpful especially during the build process and keeping tabs on global changes that may effect other tempaltes. I started a new workflow for the new Ink 2.0 and here's what I came up with i'd love feedback on how to tighten it up or improve it.

Thought Process

There are two primary views that are important, Desktop (600px up) and Mobile (min width 340 for smaller iPhones) I had the flow generate two views one at 600px and one at 340px.

After a little research, it seemed https://www.npmjs.com/package/gulp-webshot was most recently maintained and has some great options like image quality, file type, and delay to account for image loading (Which Uses Node Webshot). The one thing I think it lacks is the ability to pass multiple screen sizes to one process so ATM I'm calling two processes until I find a more elegant solution.

Generating screenshots can be a time consuming task, especially when you have multiple emails templates, so there are a few ways I reduced the overhead. 

1. The screenshot is optional instead of running ‘~ npm start` you run `npm run webshots` to watch .HTML files in dist for changes.

2. The screenshot generation process happens in parallel with the browser reload so that the browser refreshes immediately while the screenshots generate in the background.

3. You can pass an argument to specify what file/files you want to continually generate as you work. There are wild cards before and after the argument so you can pass a specific file name like `npm run webshots template-name` will look for “*template-name*.html” and like wise you could pass `npm run webshots marketing` which will target any template with the string “*marketing*” like signup-marketing.html and unsubscribe-marketing.html. This helps reduce the wait time from screenshot regeneration when you're working on a specific email or group of emails but don't want unchanged emails to be effected.

ProTip: You can also modify the gulp process to copy the files over to a dropbox folder so that your team can stay in sync anywhere.

 Setup Changes

1. Install NPM Package

~ npm install gulp-webshot --save-dev

 

2. Update Package.json - Adds a webshots script command on line 10

"scripts": {
    "start": "gulp",
    "build": "gulp --production",
    "webshots": "gulp --webshots"
  },

 

3. Update your gulpfile.babel.js to process the webshots in the correct order. (removed foundation comments to highlight changes)

import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
// Import Gulp Webshot
import webshot  from 'gulp-webshot';

const $ = plugins();

const PRODUCTION = !!(yargs.argv.production);

// Check whether webshots was called as an argument and set template tag accordingly
const WEBSHOTS   = !!(yargs.argv.webshots);
if (yargs.argv.webshots == true ) {
  // If no argument is sent the value is 'true' so we don't want to use that as a template name.
  var template = '';
} else {
  // Otherwise if there was an argument set the template to target only that file/files
  var template = yargs.argv.webshots;
}
const TEMPLATE = template;


gulp.task('build',
  gulp.series(clean, pages, sass, images, inline));

// Add a new task called webshots that get's called to run desktop and mobile screenshots
gulp.task('webshots',
  gulp.series(webshots_desktop, webshots_mobile));

// Default build process but now watch and webshots run in parallel be because watch doesn't "end" so we want screenshots to keep being generated.
gulp.task('default',
  gulp.series('build', server, gulp.parallel( watch, 'webshots')));

// Define both the desktop and mobile functions
// Webshot options documentation https://github.com/brenden/node-webshot
function webshots_desktop(done) {
  if (WEBSHOTS) {
    return gulp.src('dist/**/*'+ TEMPLATE +'*.html')
          .pipe(webshot({
            root:'./dist',
            dest:'screenshots/desktop',
            quality: 100,
            windowSize: { width: 600, height: 600 },
            shotSize: { width: 'window' , height: 'all' }
          }))
  } else {
    return done()
  }
}
function webshots_mobile(done) {
  if (WEBSHOTS) {
    return gulp.src('dist/**/*'+ TEMPLATE +'*.html')
          .pipe(webshot({
            root:'./dist',
            dest:'screenshots/mobile',
            quality: 100,
            windowSize: { width: 340, height: 340 },
            shotSize: { width: 'window' , height: 'all' }
          }))
  } else {
    return done()
  }
}


function clean(done) {
  rimraf('dist', done);
}

function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

function resetPages(done) {
  panini.refresh();
  done();
}

function sass() {
  return gulp.src('src/assets/scss/app.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

function images() {
  return gulp.src('src/assets/img/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

function inline() {
  return gulp.src('dist/**/*.html')
    .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
    .pipe(gulp.dest('dist'));
}

function server(done) {
  browser.init({
    server: 'dist'
  });
  done();
}

// Watch for file changes
// Watch needed to be modified too so that the webshot and browser.reload happen in parallel.
function watch() {
  gulp.watch('src/pages/**/*.html', gulp.series(pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*'], gulp.series(resetPages, pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss'], gulp.series(sass, pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch('src/img/**/*', gulp.series(images, gulp.parallel( browser.reload, 'webshots')));
}

function inliner(css) {
  var css = fs.readFileSync(css).toString();
  var mqCss = siphon(css);

  var pipe = lazypipe()
    .pipe($.inlineCss, {
      applyStyleTags: false
    })
    .pipe($.injectString.replace, '<!-- <style> -->', `<style>${mqCss}</style>`)
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}

screenshotsnpmgulp

Hey guys when I was playing with Ink v1. I built a grunt flow that used phantomjs to generate desktop and mobile screenshots for our emails. Our team found it super helpful especially during the build process and keeping tabs on global changes that may effect other tempaltes. I started a new workflow for the new Ink 2.0 and here's what I came up with i'd love feedback on how to tighten it up or improve it.

Thought Process

There are two primary views that are important, Desktop (600px up) and Mobile (min width 340 for smaller iPhones) I had the flow generate two views one at 600px and one at 340px.

After a little research, it seemed https://www.npmjs.com/package/gulp-webshot was most recently maintained and has some great options like image quality, file type, and delay to account for image loading (Which Uses Node Webshot). The one thing I think it lacks is the ability to pass multiple screen sizes to one process so ATM I'm calling two processes until I find a more elegant solution.

Generating screenshots can be a time consuming task, especially when you have multiple emails templates, so there are a few ways I reduced the overhead. 

1. The screenshot is optional instead of running ‘~ npm start` you run `npm run webshots` to watch .HTML files in dist for changes.

2. The screenshot generation process happens in parallel with the browser reload so that the browser refreshes immediately while the screenshots generate in the background.

3. You can pass an argument to specify what file/files you want to continually generate as you work. There are wild cards before and after the argument so you can pass a specific file name like `npm run webshots template-name` will look for “*template-name*.html” and like wise you could pass `npm run webshots marketing` which will target any template with the string “*marketing*” like signup-marketing.html and unsubscribe-marketing.html. This helps reduce the wait time from screenshot regeneration when you're working on a specific email or group of emails but don't want unchanged emails to be effected.

ProTip: You can also modify the gulp process to copy the files over to a dropbox folder so that your team can stay in sync anywhere.

 Setup Changes

1. Install NPM Package

~ npm install gulp-webshot --save-dev

 

2. Update Package.json - Adds a webshots script command on line 10

"scripts": {
    "start": "gulp",
    "build": "gulp --production",
    "webshots": "gulp --webshots"
  },

 

3. Update your gulpfile.babel.js to process the webshots in the correct order. (removed foundation comments to highlight changes)

import gulp     from 'gulp';
import plugins  from 'gulp-load-plugins';
import browser  from 'browser-sync';
import rimraf   from 'rimraf';
import panini   from 'panini';
import yargs    from 'yargs';
import lazypipe from 'lazypipe';
import inky     from 'inky';
import fs       from 'fs';
import siphon   from 'siphon-media-query';
// Import Gulp Webshot
import webshot  from 'gulp-webshot';

const $ = plugins();

const PRODUCTION = !!(yargs.argv.production);

// Check whether webshots was called as an argument and set template tag accordingly
const WEBSHOTS   = !!(yargs.argv.webshots);
if (yargs.argv.webshots == true ) {
  // If no argument is sent the value is 'true' so we don't want to use that as a template name.
  var template = '';
} else {
  // Otherwise if there was an argument set the template to target only that file/files
  var template = yargs.argv.webshots;
}
const TEMPLATE = template;


gulp.task('build',
  gulp.series(clean, pages, sass, images, inline));

// Add a new task called webshots that get's called to run desktop and mobile screenshots
gulp.task('webshots',
  gulp.series(webshots_desktop, webshots_mobile));

// Default build process but now watch and webshots run in parallel be because watch doesn't "end" so we want screenshots to keep being generated.
gulp.task('default',
  gulp.series('build', server, gulp.parallel( watch, 'webshots')));

// Define both the desktop and mobile functions
// Webshot options documentation https://github.com/brenden/node-webshot
function webshots_desktop(done) {
  if (WEBSHOTS) {
    return gulp.src('dist/**/*'+ TEMPLATE +'*.html')
          .pipe(webshot({
            root:'./dist',
            dest:'screenshots/desktop',
            quality: 100,
            windowSize: { width: 600, height: 600 },
            shotSize: { width: 'window' , height: 'all' }
          }))
  } else {
    return done()
  }
}
function webshots_mobile(done) {
  if (WEBSHOTS) {
    return gulp.src('dist/**/*'+ TEMPLATE +'*.html')
          .pipe(webshot({
            root:'./dist',
            dest:'screenshots/mobile',
            quality: 100,
            windowSize: { width: 340, height: 340 },
            shotSize: { width: 'window' , height: 'all' }
          }))
  } else {
    return done()
  }
}


function clean(done) {
  rimraf('dist', done);
}

function pages() {
  return gulp.src('src/pages/**/*.html')
    .pipe(panini({
      root: 'src/pages',
      layouts: 'src/layouts',
      partials: 'src/partials'
    }))
    .pipe(inky())
    .pipe(gulp.dest('dist'));
}

function resetPages(done) {
  panini.refresh();
  done();
}

function sass() {
  return gulp.src('src/assets/scss/app.scss')
    .pipe($.if(!PRODUCTION, $.sourcemaps.init()))
    .pipe($.sass({
      includePaths: ['node_modules/foundation-emails/scss']
    }).on('error', $.sass.logError))
    .pipe($.if(!PRODUCTION, $.sourcemaps.write()))
    .pipe(gulp.dest('dist/css'));
}

function images() {
  return gulp.src('src/assets/img/*')
    .pipe($.imagemin())
    .pipe(gulp.dest('./dist/assets/img'));
}

function inline() {
  return gulp.src('dist/**/*.html')
    .pipe($.if(PRODUCTION, inliner('dist/css/app.css')))
    .pipe(gulp.dest('dist'));
}

function server(done) {
  browser.init({
    server: 'dist'
  });
  done();
}

// Watch for file changes
// Watch needed to be modified too so that the webshot and browser.reload happen in parallel.
function watch() {
  gulp.watch('src/pages/**/*.html', gulp.series(pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch(['src/layouts/**/*', 'src/partials/**/*'], gulp.series(resetPages, pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch(['../scss/**/*.scss', 'src/assets/scss/**/*.scss'], gulp.series(sass, pages, inline, gulp.parallel( browser.reload, 'webshots')));
  gulp.watch('src/img/**/*', gulp.series(images, gulp.parallel( browser.reload, 'webshots')));
}

function inliner(css) {
  var css = fs.readFileSync(css).toString();
  var mqCss = siphon(css);

  var pipe = lazypipe()
    .pipe($.inlineCss, {
      applyStyleTags: false
    })
    .pipe($.injectString.replace, '<!-- <style> -->', `<style>${mqCss}</style>`)
    .pipe($.htmlmin, {
      collapseWhitespace: true,
      minifyCSS: true
    });

  return pipe();
}
Ethan Hackett over 3 years ago

Sorry I didn't know how to best highlight new/modifications to the gulp file so it is a little lengthy.