Brunch is served — A JAMSTACK Story

In alphabetical order, let us go over the glossary of terms for this article.

  • Brunch: an ultra fast html build tool, that uses simpler config files compared to taskrunner Gulp, and module bundler Webpack. Read more about their differences here
  • JAMSTACK: Modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup.

Intro

Content management software frameworks are great, but time and time again they suck out they joy of web development. A world made of just HTML, CSS and Javascript is rosy, but throw in those darn backend languages like PHP, Python and Ruby then monstrosity is born (well this is just a theatrical statement). What I am trying to say is that it would be nice if all it took to build web projects were frontend tools, without need to wrangle with backend code. This is the sole reason why I am so keen on kicking my Wordpress habit, where appropriate if I might add.

For the love of sanity, I still want to have template files in my codebase, my favorite being Django templating language, which is similar in syntax to Jinja2 (Python), which is similar in syntax to Twig (PHP), which is similar to Nunjucks (Javascript). In my weird way I have just told you why I chose Nunjucks for my project. If I wanted to be more accurate, then I would quote the Nunjucks documentation, which mentions that Nunjucks is heavily inspired by jinja2.

I can hear you screaming - why HTML templates (well maybe not screaming), and why not em fancy web components, react components, vue components. My aim is to build a website and not a single page app, so I do not think them necessary, plus I do not want to start thinking about prerendering and what not (I would have stuck with Django CMS or Wordpress).

Without further ado, let us set up a simple JAMSTACK project with brunch. whoop whoop !!! (or just clone the Github repo)

Midtro

Fire up your terminal, and cd to an appropriate directory to create our project, which I shall fondly refer to as jamit . The command to aptly create the folder is below (don’t whine windows folks, I hear you guys have bash or something)

mkdir jamit

Let us now initialize our project and open it up in visual studio code (yea I wrote VS code, not atom and not sublime😅)

cd jamit
npm init -y
code .

Feel free to update the description in the newly created file, package.json. Now let’s install some dependencies,

  • brunch: build tool
  • bootstrap-sass: official SASS port of Bootstrap 3
  • copycat-brunch: Brunch plugin for copying files
  • jquery: world famous Javascript library
  • nunjucks-brunch: Brunch plugin for processing Nunjucks templates
  • sass-brunch: Brunch plugin for processing SASS

hit control + ` to open the terminal in visual studio code

npm install --save-dev brunch bootstrap-sass copycat-brunch jquery nunjucks-brunch sass-brunch

With the plugins installed, let us setup the app directory the way I want it (I am no best practice guru so follow at your own whim), create a source folder named src and in it create a few sub folders .

mkdir src src/js src/media src/pages src/pages-templates src/pages-templates/layouts src/pages-templates/partials src/sass

The intended usage for the directories are

  • src: for source files
  • src/media: for assets like images that will be copied during build
  • src/pages: for Nunjucks templates for pages to be built
  • src/pages-templates: for Nunjucks layouts and partials
  • src/sass: for SASS files

Next, create a config file for brunch named brunch-config.js , and add the following content

module.exports = {
files: {
javascripts: {
joinTo: {
'assets/js/vendor.js': /^node_modules/,
'assets/js/site.js': /^src\/js/,
},
},
stylesheets: {
joinTo: {
'assets/css/vendor.css': /^node_modules/,
'assets/css/site.css': /^src\/sass/,
},
},
},
modules: {
nameCleaner: path => path.replace(/^src\/js\//, ''),
},
npm: {
enabled: true,
globals: {
jQuery: 'jquery',
$: 'jquery',
bootstrap: 'bootstrap-sass'
},
},
paths: {
public: 'public',
watched: ['src'],
},
plugins: {
copycat: {
media: ['src/media', 'public/media'],
fonts: ['node_modules/bootstrap-sass/assets/fonts', 'public/fonts'],
verbose: false,
onlyChanged: false,
},
nunjucks: {
dataDir: 'src/pages-data',
templatePath: 'src/pages',
pattern: /\.njk?$/,
ext: '.html',
layoutPath: 'src/pages-templates',
},
sass: {
options: {
includePaths: ['node_modules/bootstrap-sass/assets/stylesheets/'],
},
},
},
};

For brevity I will break down this brunch config in an optional section below, but for now let us build our project by executing the command brunch build , if my PR has not been accepted your terminal console should be innundated with errors that starts as follow:

(node) v8::ObjectTemplate::Set() with non-primitive values is deprecated
(node) and will stop working in the next major release.
==== JS stack trace =========================================

The error is due to outdated dependencies in nunjucks-brunch , being the nice guy that I am, I have forked the repo, updated the dependencies and added some crappy CoffeeScript to make the plugin more configurable (I am shy to admit that it was my first time of seeing CoffeScript code). So please update the value of nunjucks-brunch in your package.json from

"nunjucks-brunch": "^0.3.3",

to

"nunjucks-brunch": "https://github.com/olufotebig/nunjucks-brunch-personal.git",

and run npm install

Before we run the build again, let us add some files that will actually get built and a .gitignore

touch src/js/app.js src/pages/index.njk src/pages-templates/layouts/_base.njk src/sass/base.scss .gitignore

Update content of .gitignore

node_modules/
public/

Update content of src/js/app.js

// src/js/app.js
const App = {
init() {
$(function(){
console.log('init and jquery is global');
});
},
};
module.exports = App;

Update content of src/pages-templates/layouts/_base.njk

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="">
<title>{% block title %} JAMIT {% endblock %}</title>
<!-- Search Engine -->
<meta name="description" content="">
<meta name="image" content="">
<meta name="keywords" content="">
<!-- Schema.org for Google -->
<meta itemprop="name" content="">
<meta itemprop="description" content="">
<meta itemprop="image" content="">
<!-- Twitter -->
<meta name="twitter:card" content="">
<meta name="twitter:title" content="">
<meta name="twitter:description" content="">
<meta name="twitter:site" content="">
<meta name="twitter:creator" content="">
<meta name="twitter:image:src" content="">
<!-- Open Graph general (Facebook, Pinterest & Google+) -->
<meta property="og:title" content="">
<meta property="og:description" content="">
<meta property="og:image" content="">
<meta property="og:url" content="">
<meta property="og:site_name" content="">
<meta property="fb:admins" content="">
<meta property="og:type" content="">
<link rel="icon" type="image/png" href="/media/favicon/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/media/favicon/favicon-16x16.png" sizes="16x16" />
<link rel="stylesheet" href="/assets/css/site.css"> 
{% block styles %} {% endblock %}
</head>
<body>
{% block content %} {% endblock %}
{% block footer %} {% endblock %}
<script src="/assets/js/vendor.js"></script>
<script src="/assets/js/site.js"></script>
<script>
require("app").init();
</script>
{% block scripts %}{% endblock %}
</body>
</html>

Update content of src/pages/index.njk

{% extends "layouts/_base.njk" %}
{% block content %}
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">
JAMIT
</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li><a class="logo" href="#">Home</a></li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
<div class="container">
<p> Hi, I am an arrow pointing right <span class="glyphicon glyphicon-menu-right" aria-hidden="true"></span></p>
</div>
{% endblock %}

and finally,update the content of src/sass/base.scss

// Override bootstrap variables here
$text-color: #292f36;
$icon-font-path: '/fonts/bootstrap/';
@import "bootstrap";

We can now run the build again by executing brunch build , the build output would be in a folder named public. Alternatively we can watch our source directory and serve the built code, by executing the command build watch -s

Outro

Netlify is perfect for hosting static sites, and it builds and deploys brunch projects with ease. We actually used Brunch + Netlify for our the public site of our minimum viable product at JoppaLogic. I encourage you to try out Brunch, as I also look to bringing in some Vue into my Brunch mix.

Unabashed Marketing

If you are in Accra, try out our delivery/errand service at JoppaLogic.com (I am one of the founders 😂).

And More…

You can skip this section by reading the Brunch config doc here

files: {
javascripts: {
joinTo: {
'assets/js/vendor.js': /^node_modules/,
'assets/js/site.js': /^src\/js/,
},
},
stylesheets: {
joinTo: {
'assets/css/vendor.css': /^node_modules/,
'assets/css/site.css': /^src\/sass/,
},
},
},

In the code section above, we are specifying which files exactly should Brunch generate and how. For Javascript, every source file that is a npm dependency are outputted into theassets/js/vendor.js file, while our source file are outputted into the assets/js/site.js file. It’s similar too for the SASS stylesheets.

nameCleaner: path => path.replace(/^src\/js\//, ''),

nameCleaner function Allows us to set filterer function for module names, for example, change all 'src/js/file' to 'file'.

npm: {
enabled: true,
globals: {
jQuery: 'jquery',
$: 'jquery',
bootstrap: 'bootstrap-sass'
},
},

Here we just set the jQuery and Bootstrap libraries as global libraries in our built code.

paths: {
public: 'public',
watched: ['src'],
},

We specified that the public directory for the built code, be aptly named public .

plugins: {
copycat: {
media: ['src/media', 'public/media'],
fonts: ['node_modules/bootstrap-sass/assets/fonts', 'public/fonts'],
verbose: false,
onlyChanged: false,
},
nunjucks: {
dataDir: 'src/pages-data',
templatePath: 'src/pages',
pattern: /\.njk?$/,
ext: '.html',
layoutPath: 'src/pages-templates',
},
sass: {
options: {
includePaths: ['node_modules/bootstrap-sass/assets/stylesheets/'],
},
},
},

In this final code block, we configured our plugins, copycat copies from the media and bootstrap fonts folder to our public directory, we tell nunjucks-brunch where to find our template files and we tell sass-brunch to include the bootstrap sass files.