gulp - cllu/.rc GitHub Wiki

Setup

Stack

  • ES6 with babel
  • liveroading
    • browsersync for web project, provides more features
    • gulp-livereload for browser extension projects, (since it only requires WebSocket, while browser-sync use socket.io)
  • require-dir and then split tasks into files
  • libsass via gulp-sass for speed (instead of gulp-ruby-sass)
  • webpack instead of browserify

Install dependencies:

npm install --save-dev babel babel-core babel-eslint babel-loader babel-runtime del eslint eslint-loader glob gulp gulp-if gulp-imagemin gulp-livereload gulp-minify-css gulp-sass gulp-sequence gulp-sourcemaps gulp-util gulp-zip json-loader lodash require-dir webpack yargs

Directory structure:

.
├── README.md
├── app
│   ├── _locales
│   ├── fonts
│   ├── images
│   ├── index.html
│   ├── scripts
│   └── styles
├── dist
├── .eslintrc
├── gulpfile.babel.js
├── tasks
│   ├── styles.js
│   ├── scripts.js
│   ├── livereload.js
│   ├── default.js
│   ├── images.js
│   ├── fonts.js
│   └── clean.js
└── package.json
  • Definition of gulp tasks will be put in ./tasks/ directory
  • use --watch to enable watching
  • use --production to enable compression etc
  • use --verbose to show verbose logs

gulpfile.babel.js

Gulp supports ES6, create a file named gulpfile.babel.js:

import requireDir from 'require-dir';
requireDir('./tasks');

Common tasks

clean.js

import gulp from 'gulp';
import del from 'del';

gulp.task('clean', (cb) => {
  del('dist/**/*').then(() => {
    cb();
  });
});

default.js

import gulp from 'gulp';
import gulpSequence from 'gulp-sequence';

gulp.task('default', gulpSequence(
  'clean', [
    'manifest',
    'scripts',
    'styles',
    'html',
    'locales',
    'images',
    'fonts',
    'livereload'
  ]
));

html.js

import gulp from 'gulp';
import gulpif from 'gulp-if';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let production = !!argv.production;
let watch = !!argv.watch;

gulp.task('html', () => {
   return gulp.src('app/pages/**/*.html')
    .pipe(gulp.dest('dist/pages'))
    .pipe(gulpif(watch, livereload()));
});

scripts.js

import gulp from 'gulp';
import gutil from 'gulp-util';
import path from 'path';
import glob from 'glob';
import _ from 'lodash';
import webpack from 'webpack';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let production = !!argv.production;
let watch = !!argv.watch;
let verbose = !!argv.verbose;

let appDir = path.resolve(__dirname, '../app');

function globToEntryMap(...args) {
  var files = glob.sync(...args)
  return _(files)
    .map(function(filePath) {
      var bundleName = path.basename(filePath, '.js');
      return [bundleName, `./${filePath}`];
    })
    .zipObject()
    .value();
}

function createWebpackConfig() {
  return {
    devtool: production ? null : 'inline-source-map',
    watch: watch,
    context: appDir,
    entry: globToEntryMap('scripts/*.js', {
      cwd: appDir
    }),
    output: {
      path: 'dist/scripts',
      filename: '[name].js'
    },
    plugins: [
      new webpack.DefinePlugin({
        'ENV': JSON.stringify(production ? 'production' : 'development')
      }),
    ].concat(production ? [
      new webpack.optimize.UglifyJsPlugin()
    ] : []),
    module: {
      loaders: [{
        test: /\.js$/,
        loaders: ['babel-loader?optional=runtime'],
        exclude: /node_modules/
      }, {
        test: /\.json$/, 
        loader: 'json-loader'
      }],
      preLoaders: [{
        test: /\.js$/,
        loader: 'eslint-loader',
        exclude: /node_modules/
      }],
    },
    eslint: {
      configFile: '.eslintrc'
    },
    node: {
      console: true,
      fs: 'empty',
      tls: 'empty',
      net: 'empty'
    }
  };
};

gulp.task('scripts', (cb) => {
  let styledName = gutil.colors.cyan(`'webpack'`);
  gutil.log('Starting', styledName);
  webpack(createWebpackConfig(), (err, stats) => {
    if (err) throw new gutil.PluginError('webpack', err);
    gutil.log('Finished', styledName + ':\n', stats.toString({
      colors: true,
      reasons: verbose,
      hash: verbose,
      version: verbose,
      timings: verbose,
      chunks: verbose,
      chunkModules: verbose,
      cached: verbose,
      cachedAssets: verbose,
      children: verbose
    }));

    if (watch) {
      livereload.reload()
    } else {
      cb();
    }

  });
});

styles.js

I choose scss for css preprocessor.

import gulp from 'gulp';
import gulpif from 'gulp-if';
import gutil from 'gulp-util';
import sourcemaps from 'gulp-sourcemaps';
import sass from 'gulp-sass';
import minifyCSS from 'gulp-minify-css';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let production = !!argv.production;
let watch = !!argv.watch;

gulp.task('styles:css', function() {
  return gulp.src('app/styles/*.css')
    .pipe(gulpif(production, minifyCSS()))
    .pipe(gulp.dest('dist/styles'))
    .pipe(gulpif(watch, livereload()));
});

gulp.task('styles:sass', function() {
  return gulp.src('app/styles/*.scss')
    .pipe(gulpif(!production, sourcemaps.init()))
    .pipe(sass().on('error', function(error) {
      gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message));
      this.emit('end');
    }))
    .pipe(gulpif(production, minifyCSS()))
    .pipe(gulpif(!production, sourcemaps.write()))
    .pipe(gulp.dest('dist/styles'))
    .pipe(gulpif(watch, livereload()));
});

gulp.task('styles', [
  'styles:css',
  'styles:sass'
]);

livereload.js

import gulp from 'gulp';
import gutil from 'gulp-util';
import gulpSequence from 'gulp-sequence';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let watch = !!argv.watch;
let verbose = !!argv.verbose;

gulp.task('livereload', (cb) => {

  // This task runs only if the
  // watch argument is present!
  if (!watch) return cb();

  // Start livereload server
  livereload.listen({
    reloadPage: 'Extension',
    quiet: !verbose
  });
  gutil.log('Starting', gutil.colors.cyan('\'livereload-server\''));

  // Hint: Scripts are being watched by webpack!
  //       For more info checkout ./webpack.js

  gulp.watch('app/styles/**/*.css', ['styles:css']);
  gulp.watch('app/styles/**/*.less', ['styles:less']);
  gulp.watch('app/styles/**/*.scss', ['styles:sass']);
  gulp.watch('app/pages/**/*.html', ['pages']);
  gulp.watch('app/_locales/**/*', ['locales']);
  gulp.watch('app/images/**/*', ['images']);
  gulp.watch('app/fonts/**/*.{woff,ttf,eot,svg}', ['fonts']);

});

images.js

images are assumed to located at app/images, it will be copied to dist/images. If in production, imagemin will be used to compress images.

import gulp from 'gulp';
import gulpif from 'gulp-if';
import imagemin from 'gulp-imagemin';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let production = !!argv.production;
let watch = !!argv.watch;

gulp.task('images', () => {
   return gulp.src('app/images/**/*')
    .pipe(gulpif(production, imagemin()))
    .pipe(gulp.dest('dist/images'))
    .pipe(gulpif(watch, livereload()));
});

fonts.js

fonts are located at app/fonts, it would be copied to dist/fonts

import gulp from 'gulp';
import gulpif from 'gulp-if';
import imagemin from 'gulp-imagemin';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let watch = !!argv.watch;

gulp.task('fonts', () => {
  return gulp.src('app/fonts/**/*.{woff,ttf,eot,svg}')
    .pipe(gulp.dest('dist/fonts'))
    .pipe(gulpif(watch, livereload()));
});

Chrome extension

package.js

import gulp from 'gulp';
import zip from 'gulp-zip';
import packageDetails from '../package.json';

gulp.task('package', () => {
  let name = packageDetails.name;
  let version = packageDetails.version;
  let filename = `${name}-${version}.zip`;
  gulp.src('dist/*')
    .pipe(zip(filename))
    .pipe(gulp.dest('./packages'));
});

manifest.js

import gulp from 'gulp';
import gulpif from 'gulp-if';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let watch = !!argv.watch;

gulp.task('manifest', () => {
  gulp.src('app/manifest.json')
    .pipe(gulp.dest('dist'))
    .pipe(gulpif(watch, livereload()));
});

locales.js

import gulp from 'gulp';
import gulpif from 'gulp-if';
import livereload from 'gulp-livereload';
import yargs from 'yargs';

let argv = yargs.argv;
let watch = !!argv.watch;

gulp.task('locales', () => {
  gulp.src('app/_locales/**/*')
    .pipe(gulp.dest('dist/_locales'))
    .pipe(gulpif(watch, livereload()));
});

Web

deploy.js

Make sure there is an orphan branch gh-pages first (gulp-gh-pages does not create an orphan branch for us):

git checkout --orphan gh-pages
git rm -rf .
touch README.md
git add README.md
git commit -m "Init gh-pages"
git push --set-upstream origin gh-pages
git checkout master
var fs = require('fs');
import gulp from 'gulp';
import ghPages from 'gulp-gh-pages';

import config from './config';

gulp.task('cname', () => {
  fs.writeFileSync('dist/CNAME', config.deploy.cname);
})

gulp.task('deploy', ['cname'], function() {
  return gulp.src('./dist/**/*')
    .pipe(ghPages(config.deploy));
});