Jekyll is great, and I’m a fan. It’s been around for a long time and remains a straightforward and simple solution to creating a static blog - in fact, this site is made with Jekyll. However, there are some more interactive pieces I’ve wanted to add here (including search) that require some fancier UI code but don’t necessitate a more powerful site generator. Enter Webpack and npm, which gives me access to node modules that were previously something I’d only look at wistfully. I find trying to let Jekyll handle javascripts annoying and tedious, so I like that with this I’m able to use an ecosystem that is specifically designed to do major lifting in compilation, testing, and minification.

You can view my Jekyll-Webpack boilerplate here, made for this post:

Scope

This is a very simple implementation and doesn’t rely on weird code acrobatics. It’s certified smell free :) I write my tutorials assuming people have shallow knowledge of the tools I use so that beginners may benefit from the extra explanation - more experienced devs should feel free to skim. In this post, however, I’m assuming readers have some experience with Jekyll. I’ll walk through how to create a basic jekyll-webpack integration, and then use it to create a simple React component.

Pre-work: Setting up your dev environment

We’re not using anything beyond Jekyll, Webpack, and Node’s package manager. Install all three with the most current versions.

  • Install npm: $ sudo install npm -g
    Make sure you have the latest version of npm, which at time of writing is 3. This prevents installing multiple copies of dependencies in your node_modules directory and significantly reduces npm’s footprint.
  • Install webpack: $ npm install webpack -g
  • Install jekyll: $ gem install jekyll -g

Now that we have everything ready to use, let’s get started!

1. Rearrange your project structure

$ jekyll new [project-name] scaffolds all partial files in the root. I like using a src/ (or, source) and public/ directory to organize my Jekyll projects, especially when using Jekyll with other frameworks. Here’s the difference:

generic jekyll scaffold:

.
├── _config.yml
├── _includes
│   ├── ...
├── _layouts
│   ├── default.html
│   ├── page.html
│   └── post.html
├── _posts
│   ├── ...
├── _sass
│   ├── _base.scss
│   ├── _layout.scss
│   └── _syntax-highlighting.scss
├── about.md
├── css
│   └── main.scss
├── feed.xml
└── index.html

A base example of what we’ll use:

.
├── _config.yml
├── package.json
├── public
│   ├── ...
├── src
│   ├── _includes
│   │   ├── ...
│   ├── _layouts
│   │   ├── default.html
│   │   ├── page.html
│   │   └── post.html
│   ├── _posts
│   │   ├── ...
│   ├── _sass
│   │   ├── ...
│   ├── about.md
│   ├── assets
│   │   ├── css
│   │   ├── images
│   │   └── javascripts
│   │       └── bundle.js
│   ├── feed.xml
│   └── index.html
├── webpack
│   ├── entry.js
│   └── components
│       └── ...
└── webpack.config.js

This requires that you move all of the content you want Jekyll to pick up for the build process to a src directory in the root, including the index.html. Keep the config.yml in the root. You’ll need to let Jekyll know you’re building from src into public, so, in your config file, give it some pointers:

destination: public
source: src

Because you’re building to a public folder, you can use Pow to serve the static site at your-project-name.dev, which I find nice for development.

2. Add package.json

npm won’t install your modules unless it can find a package.json in your root with a recognizable object (i.e. the file cannot be blank). Create the file with $ touch package.json and add an empty json object with $ echo {} >> package.json. Or, your can let npm create the package.json for you by running $ npm init in the root, which will walk you through a setup in the terminal and then generate a populated package.json. Mine looks something like this:

  {
  "name": "jekyll-webpack-es6-boilerplate",
  "version": "0.1.1",
  "description": "Example jekyll blog integrated with webpack and npm",
  "authors": [
    {
      "name": "Allison Zadrozny",
      "email": "allison@allizad.com"
    }
  ],
  "license": "MIT",
  "devDependencies": {
  }
}

3. Add Webpack folder

Create a webpack/ directory in the root project with an entry.js file. This folder is for development only. We’re keeping it out of the src directory because we’re going to configure Webpack to pick up entry.js and write a compiled version into the src files for Jekyll. Jekyll will then pick it up and serve it statically with the rest of the site.

Also, remove any Jekyll javascript files from your assets directory. Keep that javascripts folder in the assets folder, though - we’re going to use it!

4. Configure webpack

Create a webpack.config.js in the root. In the config.js, I’ve laid out where webpack can find entry.js and where I want it to compile to:

module.exports = {
  // webpack folder's entry js - excluded from jekll's build process.
  entry: "./webpack/entry.js",
  output: {
    // we're going to put the generated file in the assets folder so jekyll will grab it.
      path: 'src/assets/javascripts/',
      filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.jsx?$/,
        exclude: /(node_modules)/,
        loader: 'babel', // 'babel-loader' is also a legal name to reference
        query: {
          presets: ['react', 'es2015']
        }
      }
    ]
  }
};

Notice that the output.path is within the src/ directory.

5. Ignore all the things!

You’ll absolutely need to tell both github and Jekyll to ignore node_modules. I forgot to do this at first, and Jekyll was taking ~30-28s to generate because of the ridiculous amounts of node_module files. This was also pre-node 3, which meant dependencies were loading multiple copies of their own dependencies. It was dependencies all the way down, with a 208MB mark at first. Lesson: don’t use npm 2.

In your .gitignore, add src/assets/bundle.js, public, and node_modules. Since bundle.js is just a compiled version of of entry.js there’s no reason to version it, and same idea with public. It’s expected with using npm to always ignore the modules directory so that pushing and pulling doesn’t take up a ton of bandwidth.

.sass-cache
.jekyll-metadata
public
src/assets/bundle.js
node_modules

Tell Jekyll to ignore node_modules when building:

exclude: ['node_modules']

6. Include your bundle.js in your html

In your _layouts/default.html, add your compiled bundle.js file.

<!DOCTYPE html>
<html>
  ...
  <body>
    ...

    <script type="text/javascript" src="/assets/javascripts/bundle.js" charset="utf-8"></script>

  </body>
</html>

If you’re using the jekyll-assets gem::

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    
    {% javascript bundle %}
    
  </body>
</html>

7. Install dependencies

I like writing my javascript in ES6, so let’s install the loaders and libraries we’ll need to compile and run the es6 React we’ll write in the next step. Run $ npm install webpack babel-core babel-loader babel-preset-es2015 babel-preset-react react react-addons-update react-dom --save-dev. The save-dev flag will write the packages to the package.json devDependencies. Now your package.json should look something like this:

{
  "name": "jekyll-webpack-es6-boilerplate",
  "version": "0.1.1",
  "description": "Example jekyll blog integrated with webpack and npm",
  "authors": [
    {
      "name": "Allison Zadrozny",
      "email": "allison@allizad.com"
    }
  ],
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.7.7",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "react": "^15.0.2",
    "react-addons-update": "^15.0.2",
    "react-dom": "^15.0.2"
  }
}

8. Add some test javascript to entry.js

Just to test everything is working correctly once we build, add something like console.log("Hello, world!"); to your entry.js.

9. Build your site!

First, run $ webpack to generate the new bundle.js from entry.js. If you don’t do this, Jekyll won’t have a bundle.js to pick up and reference in default.html. Next, run $ jekyll build to build your site. Open up your dev site, pop open the console, and double check that your statement was logged. If not, there’s something amiss, so double check your config files and the paths you’ve set for webpack. If things are working, excellent! Move on to Step 10 and let’s get to writing some React!

10. Create a React Component

The nice thing about having a webpack directory is now we can organize our components in a new folder and import the things we need in entry.js in a modular way. Create a components folder inside our webpack directory, and create a new file called Hello.js.

In Hello.js, create a simple static component:

import React, { Component } from 'react';

class Hello extends Component {
  render() {
    return (
      <div>Hey, yall!!</div>
    )
  }
}
export default Hello;

In entry.js load react and import your component:

import React, { Component } from 'react';
import {render} from 'react-dom';
import Hello from './components/Hello';

class App extends Component {
  render() {
    return (
      <Hello />
    )
  }
}

render(<App />, document.getElementById('root'));

10. Mount your component!

Add the div React will render your component in your default.html (or wherever you please):

<!DOCTYPE html>
<html>
  ...
  <body>
    ...

    <div id="root"></div>
    
    {% javascript bundle %}
    
  </body>
</html>

11. Rerun your build and see your component!

Run $ webpack to compile your js and $ jekyll build so Jekyll will catch the newly compiled file. You should now be able to see a lovely React component on your Jekyll page!

In Closing

This should work fairly easily, so if you have any troubles with it please let me know! I’m happy to help. The annoying part is constantly having to run webpack after making changes, but you can keep Jekyll watching changes to bundle.js with jekyll build -w AND webpack watching with webpack -w. This way, moving back and forth to the terminal is minimal.