Your jekyll site hosted on github pages with bower support

22 Jul 2014

This blog post is a tutorial to setup a jekyll site with bootstrap installed thanks to bower, and fully hosted on github pages.

Jekyll support on github pages is great, but there are some limitations that prevent me from using it:

  • I want to use bower to manage dependencies, and without pushing the bower components directory to git
  • I want to be able to use custom jekyll plugins

So I prefer to directly push the generated site on github pages.

Code for this tutorial is available here: https://github.com/aymerick/jekyll-example

Result website is here: http://jekyll-example.aymerick.com

Websites I built with that setup:

Setup the master branch

WARNING: This tutorial covers the setup of a Project Page, and as so the master branch is used as the source one, and the gh-pages branch holds the generated site. However, if you setup a User Page then the master branch holds the generated site, and so you have to create a new branch (eg: source) to hold your site source.

First, create an empty jekyll-example repository on github via the web interface.

Then generate the website:

$ jekyll new jekyll-example

Init git:

$ cd jekyll-example
$ git init
$ git add .
$ git commit -m "Generated by Jekyll v2.1.1"

Push master to github:

$ git remote add origin git@github.com:aymerick/jekyll-example.git
$ git push -u origin master

Setup the gh-pages branch

Create an orphan gh-pages branch:

$ git checkout --orphan gh-pages
$ git reset .
$ rm -r *
$ rm .gitignore
$ echo 'Coming soon' > index.html
$ git add index.html
$ git commit -m "init"

That way the master and the gh-pages branches are totally independant ones, they share no history at all.

Push gh-pages to github:

$ git push -u origin gh-pages

Now you should see Coming soon message when browsing to: http://aymerick.github.io/jekyll-example/

Checkout the gh-pages branch in _dist directory

Let's work in master branch:

$ git checkout master

Checkout the gh-pages branch into _site directory:

$ git clone git@github.com:aymerick/jekyll-example.git -b gh-pages _site

The _site directory is already in .gitignore so we are just fine.

Let's test our new website:

$ jekyll serve

Browse to http://127.0.0.1:4000/ and Bingo!

Push the generated site on github:

$ cd _site
$ git add .
$ git commit -m "First generation"
$ git push

Now browse to http://aymerick.github.io/jekyll-example/ and ... wow... the jekyll site is displayed but the CSS is broken. Why ? Because you have to set the baseurl setting in the main config file.

So edit the _config.yml file and set:

baseurl: "/jekyll-example"
url: "http://aymerick.github.io"

Regenerate the site, and push result on github:

$ cd ..
$ jekyll build
$ cd _site
$ git add .
$ git commit -m "Fixes baseurl"
$ git push

Now browse to http://aymerick.github.io/jekyll-example/ and everything should be fine.

Let's go back locally:

$ cd ..
$ jekyll serve

Note that the local site is now accessible at http://127.0.0.1:4000/jekyll-example/

Disable jekyll build on github

To be sure that jekyll generation is not triggered on github, you must add an empty .nojekyll file.

Then edit the _config.yml file and set:

include:
  - .nojekyll

Setup bower

Install bower:

$ npm install -g bower
$ bower init

The bower.json file should have been created with something like that:

{
  "name": "jekyll-example",
  "version": "0.0.0",
  "homepage": "https://github.com/aymerick/jekyll-example",
  "authors": [
    "Aymerick Jéhanne"
  ],
  "description": "Jekyll setup example",
  "license": "MIT",
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ]
}

Now, we need to exclude bower files from jekyll build. Edit the _config.yml file and adds:

exclude:
  - "bower_components"
  - "bower.json"

Make git ignore bower components by adding that line to .gitignore file:

/bower_components/

Install bootstrap

Let's install bootstrap with bower:

$ bower install bootstrap --save

Now bootstrap and jquery files are available here:

/bower_components/bootstrap/dist/css/bootstrap.min.css
/bower_components/bootstrap/dist/css/bootstrap.min.js
/bower_components/jquery/dist/css/jquery.min.js

I really don't want to push the whole bower_components directory to github. So let's setup a build system with grunt that will replace jekyll tool and that will copy the vendor files as I want it.

Setup npm

Init npm:

$ npm init

The package.json file should have been created with something like that:

{
  "name": "jekyll-example",
  "version": "0.0.0",
  "description": "Jekyll setup example",
  "main": "_site/index.html",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/aymerick/jekyll-example.git"
  },
  "author": "Aymerick Jéhanne",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/aymerick/jekyll-example/issues"
  },
  "homepage": "https://github.com/aymerick/jekyll-example"
}

To exclude npm files from jekyll build, edit the _config.yml file and adds:

exclude:
  - "node_modules"
  - "package.json"
  - ...

Make git ignore npm packages by adding that line to .gitignore file:

/node_modules/
...

The grunt file

First, install needed grunt packages:

$ npm install -g grunt-cli
$ npm install grunt grunt-bower-task grunt-contrib-connect grunt-contrib-copy grunt-contrib-watch grunt-exec --save-dev

Now the fun part begins, let's create the grunt file. I'm not a big fan of coffescript, but for the purpose of a makefile I think its syntax is elegant and cleaner than raw javascript.

So here is the Gruntfile.coffee file:

#global module:false

"use strict"

module.exports = (grunt) ->
  grunt.loadNpmTasks "grunt-bower-task"
  grunt.loadNpmTasks "grunt-contrib-connect"
  grunt.loadNpmTasks "grunt-contrib-copy"
  grunt.loadNpmTasks "grunt-contrib-watch"
  grunt.loadNpmTasks "grunt-exec"

  grunt.initConfig

    copy:
      jquery:
        files: [{
          expand: true
          cwd: "bower_components/jquery/dist/"
          src: "jquery.min.js"
          dest: "vendor/js/"
        }]
      bootstrap:
        files: [{
          expand: true
          cwd: "bower_components/bootstrap/dist/css/"
          src: "bootstrap.min.css"
          dest: "vendor/css/"
        },
        {
          expand: true
          cwd: "bower_components/bootstrap/dist/js/"
          src: "bootstrap.min.js"
          dest: "vendor/js/"
        }]

    exec:
      jekyll:
        cmd: "jekyll build --trace"

    watch:
      options:
        livereload: true
      source:
        files: [
          "_drafts/**/*"
          "_includes/**/*"
          "_layouts/**/*"
          "_posts/**/*"
          "css/**/*"
          "js/**/*"
          "_config.yml"
          "*.html"
          "*.md"
        ]
        tasks: [
          "exec:jekyll"
        ]

    connect:
      server:
        options:
          port: 4000
          base: '_site'
          livereload: true

  grunt.registerTask "build", [
    "copy"
    "exec:jekyll"
  ]

  grunt.registerTask "serve", [
    "build"
    "connect:server"
    "watch"
  ]

  grunt.registerTask "default", [
    "serve"
  ]

First let's exclude the grunt file from jekyll build, by editing the _config.yml file and adding:

exclude:
  - "Gruntfile.coffee"
  - ...

The grunt tasks

The copy subtask copies the jquery and bootstrap files to a new vendor directory. That directory will be copied as is by jekyll to _dist so it acts as a temporary zone and must be ignored by git. Adds that line to .gitignore file:

  /vendor/
  ...

The exec:jekyll subtask invokes the jekyll tool to build the site into _site directory.

The connect:server subtask launches a server on port 4000.

The watch subtask rebuilds the site when a source file changes. The list of all watched source files must be provided in the files setting. Remember to update that setting when you add custom directories or files.

Note the use of livereload: true in both watch and connect:server subtasks. Thanks to that setting, your browser will reload automatically when one of the source files changes.

Include bootstrap files

Let's include vendor files in our jekyll site:

Edit the _includes/head.html file and add that line:

<!-- Custom CSS -->
<link rel="stylesheet" href="/vendor/css/bootstrap.min.css">

Edit the _layouts/default.html file and add those lines before </body>:

<script src="/vendor/js/jquery.min.js"></script>
<script src="/vendor/js/bootstrap.min.js"></script>

The baseurl dilemma

So now, you can replace the jekyll serve command with:

$ grunt

Or if you want verbose logs:

$ grunt -v

But there is a problem:

That's because jekyll is configured with baseurl: "/jekyll-example" so that it is browsable at http://aymerick.github.io/jekyll-example/ but there is no such setting with grunt-contrib-connect. The grunt-contrib-connect package expects you to browse http://127.0.0.1:4000/.

Custom domain name

I personnally always host my jekyll sites on custom domain names, so I will host that example on http://jekyll-example.aymerick.com instead of http://aymerick.github.io/jekyll-example/.

That way I don't need a baseurl setting and this setup works the same for development and for production. If you really need to keep the baseurl setting then you should probably create a new 'production specific' grunt task that will build jekyll with the --baseurl URL option.

So, first you need to comment the baseurl setting in the _config.yml file:

# baseurl: "/jekyll-example"

Then create a CNAME file with the custom domain name:

jekyll-example.aymerick.com

Finally go to your DNS provider website and setup a CNAME from jekyll-example.aymerick.com to aymerick.github.io.

Let's push everything:

$ grunt build
$ cd _site
$ git add .
$ git commit -m "Setup bootstrap and custom domain"
$ git push
$ cd ..

And now, browse to http://jekyll-example.aymerick.com.

You should probably see a 404 There isn't a GitHub Page here. error, that's because it takes some time for github to setup your new custom domain configuration. Just wait a few minutes then try again, you will eventually see your site.

The deploy task

It is tedious to manually commit and push the gh-pages branch when you want to deploy to github pages. Let's create a rake task that will help us with that.

Create a Rakefile file:

require "rubygems"

desc "Deploy to Github Pages"
task :deploy do
  puts "## Deploying to Github Pages"

  puts "## Generating site"
  system "grunt build"

  cd "_site" do
    system "git add -A"

    message = "Site updated at #{Time.now.utc}"
    puts "## Commiting: #{message}"
    system "git commit -m \"#{message}\""

    puts "## Pushing generated site"
    system "git push"

    puts "## Deploy Complete!"
  end
end

Exclude Rakefile from jekyll build, by editing the _config.yml file and adding:

exclude:
  - "Rakefile"
  - ...

And now, deploying your site on github page is as simple as running:

$ rake deploy

Conclusion

With that setup, you only have two commands to run:

  • grunt while developing, with live reload support
  • rake deploy to deploy the site in production

You can now add more bower packages, use custom jekyll plugins, and have fun :)

(You can contact me @aymerick and I want to thanks @octplane for reviewing this tutorial)