Publishing hugo website with gulp

Sun, Jan 17, 2016

Here I will be talking about how to setup a Hugo powered website and publish it on Amazon S3 storage. As minor improvements, we will be doing following tasks with help of gulp:

  • Minifying CSS, JS, HTML
  • gzip them
  • Upload files to be served on S3
  • Configure metadata on S3 to enable caching on browser

First of all you need to have npm and hugo installed, and their executables on your user’s PATH. You can probably find better and more updated tutorials on these online. The versions I’m using are:

$ npm -v
2.11.3
$ hugo version
Hugo Static Site Generator v0.15 BuildDate: 2015-11-25T14:35:20+02:00

First of all, create the website skeleton, replacing mywebsite with whatever you want.

$ hugo new site mywebsite

After this point you might want to check official hugo tutorial on how to create pages and edit them. A simple tutorial on hugo is here.

After finishing your first version of website, change to your project directory, and set it as a node project:

$ cd mywebsite/
$ npm init
name: (mywebsite)
version: (1.0.0)
description: my simple web site powered with hugo
.........
{
  "name": "mywebsite",
  "version": "1.0.0",
  "description": "my simple web site powered with hugo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

This creates package.json description file in project directory. You may edit the file and clean the unnecessary parts if you like.

Now install gulp and gulp modules we will be using. If you’re a gulp rookie (like me), I suggest that you read a few tutorials on it and visit back. Run following command on root directory of your project.

npm install --save-dev gulp gulp-clean gulp-uglify gulp-cssnano gulp-htmlmin gulp-shell gulp-awspublish concurrent-transform

After installation is complete, create the configuration file in project directory gulpconfig.json as follow and edit it:

{
  "webpage": "http://mywebsite.com",
  "sourceDir": "public",
  "assetsDir": "static/assets",
  "awsConcurrency": 10,
  "awsOptions": {
    "region": "eu-central-1",
    "accessKeyId": "aws-access-key",
    "secretAccessKey": "aws-secret-key",
    "sslEnabled": true,
    "params": {
      "Bucket": "aws-s3-bucket-name"
    }
  },
  "headers": {
    "styles": {
      "Cache-Control": "max-age=3000000, no-transform, public"
    },
    "scripts": {
      "Cache-Control": "max-age=3000000, no-transform, public"
    },
    "html": {
      "Cache-Control": "max-age=3600, no-transform, public"
    },
    "xml": {
      "Cache-Control": "max-age=3600, no-transform, public"
    },
    "assets": {
      "Cache-Control": "max-age=3000000, no-transform, public"
    }
  }
}

To simply explain each key:

  • webpage: The address of your website
  • sourceDir: public directory created by hugo
  • assetsDir: Assets directory where unprocessed files will be stored
  • awsConcurrency: Number of connections to be used to upload files to S3
  • awsOptions: AWS configuration
  • headers: Headers allow specifying different headers (such as cache duration) for each kind of file
    • styles: CSS files
    • scripts: Javascript files
    • html: HTML files
    • xml: XML files
    • assets: Everything else

Now create gulpfile.js and start adding tasks.

In case you’re type of guy who forgets the things easily, create a watch task to start running hugo web server, which is helpful to check real-time results of your posts locally.

gulp.task('watch', shell.task([
    'hugo server -D=true'
]));

Now when you run gulp watch command on your project directory, hugo will compile and serve current edition of your website on http://localhost:1313

$ gulp watch
[23:28:37] Using gulpfile D:\temp\mywebsite\gulpfile.js
[23:28:37] Starting 'watch'...
2 of 2 drafts rendered
0 future content
5 pages created
1 paginator pages created
10 categories created
in 25 ms
Watching for changes in D:\temp\mywebsite/{content,static,themes}
Serving pages from memory
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

After completing creating your posts, editing your website etc and ready to publish, start editing gulpfile.js to load configuration file and initialize modules:

var config = require('./gulpconfig.json');

var gulp = require('gulp');
var uglify = require('gulp-uglify');
var cssnano = require('gulp-cssnano');
var htmlmin = require('gulp-htmlmin');
var shell = require('gulp-shell');
var clean = require('gulp-clean');
var awspublish = require('gulp-awspublish');
var parallelize = require('concurrent-transform');

And add those scripts to your file, just for somehow more readable code

var src = {
    dir: [config.sourceDir],
    styles: [config.sourceDir + '/**/*.+(css)'],
    scripts: [config.sourceDir + '/**/*.+(js)'],
    html: [config.sourceDir + '/**/*.+(html)'],
    xml: [config.sourceDir + '/**/*.+(xml)']
};
// Assets and everything else other than html/xml/css/js
src.assets = [config.assetsDir  + '/**/*.+(*)', 
				config.sourceDir + '/**/*.+(*)', 
				'!' + src.styles[0], 
				'!' + src.scripts[0], 
				'!' + src.html[0], 
				'!' + src.xml[0]];

First task is straight-forward, it’s for cleaning public directory before re-building with hugo:

gulp.task('clean', function(){
	return gulp.src(src.dir, {read: false})
		.pipe(clean());
});

Then the second task is for building website with hugo:

gulp.task('hugo', ['clean'], shell.task([
    'hugo -b "' + config.webpage + '" '
]));

Now we can start minimizing CSS, gzip them and upload them to S3 with desired headers:

gulp.task('buildCSS', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.styles)
        .pipe(cssnano())
        .pipe(awspublish.gzip())
		.pipe(parallelize(publisher.publish(config.headers.styles), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});

The process is pretty similiar for JS, HTML and other files, so I’ll be omitting them. You may check the complete gulpfile.js at the end of the page. Now create a default gulp task for releasing hugo website:

gulp.task('default', ['clean', 'hugo', 'buildCSS', 'buildJS', 'buildHTML', 'buildAssets', 'buildXML'],
    function() {});

Once the gulpfile.js is complete, just run gulp command at project root directory and your minified and gzipped website will be on S3 storage.

Enjoy!

Complete gulpfile.js :

// Config file
var config = require('./gulpconfig.json');

// Modules
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var cssnano = require('gulp-cssnano');
var htmlmin = require('gulp-htmlmin');
var shell = require('gulp-shell');
var clean = require('gulp-clean');
var awspublish = require('gulp-awspublish');
var parallelize = require('concurrent-transform');

var src = {
    dir: [config.sourceDir],
    styles: [config.sourceDir + '/**/*.+(css)'],
    scripts: [config.sourceDir + '/**/*.+(js)'],
    html: [config.sourceDir + '/**/*.+(html)'],
    xml: [config.sourceDir + '/**/*.+(xml)']
};
// Assets and everything else other than html/xml/css/js
src.assets = [config.assetsDir  + '/**/*.+(*)', 
				config.sourceDir + '/**/*.+(*)', 
				'!' + src.styles[0], 
				'!' + src.scripts[0], 
				'!' + src.html[0], 
				'!' + src.xml[0]];

// Tasks
gulp.task('clean', function(){
	return gulp.src(src.dir, {read: false})
		.pipe(clean());
});

gulp.task('hugo', ['clean'], shell.task([
    'hugo -b "' + config.webpage + '" '
]));

gulp.task('watch', shell.task([
    'hugo server -D=true'
]));

gulp.task('buildCSS', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.styles)
        .pipe(cssnano())
        .pipe(awspublish.gzip())
		.pipe(parallelize(publisher.publish(config.headers.styles), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});

gulp.task('buildAssets', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.assets)
		.pipe(parallelize(publisher.publish(config.headers.assets), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});

gulp.task('buildJS', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.scripts)
        .pipe(uglify())
        .pipe(awspublish.gzip())
        .pipe(publisher.publish(config.headers.scripts))
		.pipe(parallelize(publisher.publish(config.headers.scripts), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});

gulp.task('buildHTML', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.html)
        .pipe(htmlmin({
            collapseWhitespace: true
        }))
        .pipe(awspublish.gzip())
        .pipe(parallelize(publisher.publish(config.headers.html), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});

gulp.task('buildXML', ['hugo'], function() {
	var publisher = awspublish.create(config.awsOptions);
    return gulp.src(src.xml)
        .pipe(parallelize(publisher.publish(config.headers.xml), config.awsConcurrency))
        .pipe(publisher.cache())
        .pipe(awspublish.reporter());
});


gulp.task('default', ['clean', 'hugo', 'buildCSS', 'buildJS', 'buildHTML', 'buildAssets', 'buildXML'],
    function() {});


  « Previous: