Modularizing Your Bootstrap Template With Zurb Panini

Recently I’ve been doing web development with Bootstrap. I felt there must be a better way than constantly adding the same head tag with minor modification for title. As development progress, the same header, navigation, footer, and side bar elements of each page need to be added as well. Repeating these many times can introduce errors and waste valuable time. This is a perfect opportunity to keeping my code DRY (Don’t Repeat Yourself).

At first I knew there were “HTML Preprocessors”. They allow efficient coding by reducing repetitive tasks. Some well known preprocessors include Pug and Handlebars. These turned me off because they had more features than I needed. Plus some preprocessors like Pug changes the way code is written by eliminating standard HTML tags making the development process more like Python. Plus the documentation for these preprocessors can be too technical and didn’t directly address my use case.

I also looked at Server Side Includes such as Apache or PHP. There’s probably a performance hit when a full HTML page is assembled on the server for every page request. What I really needed was a way to write my code modularly on my workstation. Then compile / transpile the HTML into assembled versions for the server.

One of the project that fit my use case is called “Zurb Panini” made by the same team that created the Foundation front end framework. It is integrated into the Foundation CLI allowing you to easily scaffold a new project with Foundation. Since I’m using Bootstrap 4 Beta, it was very easy to integrate Zurb Panini into my Bootstrap, Gulp, BrowserSync workflow. Below is a step by step guide on using Zurb Panini with Bootstrap 4 Beta:

Setting Up Zurb Panini

Step 1: Initialize package.json in a new project folder

npm init

Fill in your information or accept the defaults. If you do not have npm installed, its most likely you don’t have NodeJS installed on your system. This guide only covers the steps after installing NodeJS. There are many guides on installing NodeJS for your environment which will not be covered here.

If you have not installed Gulp globally, you’ll need to install Gulp using the following command:

npm -g install gulp

Step 2: Install npm dev dependencies

npm install -D panini gulp browser-sync

Your updated package.json file should look like the following

{
  "name": "panini-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "browser-sync": "^2.18.13",
    "gulp": "^3.9.1",
    "panini": "^1.5.1"
  }
}

Step 3: Create gulpfile.js in project root

Configure a basic gulpfile for the installed npm dev dependencies and folder structure:

var
    gulp = require('gulp'),
    panini = require('panini'),
    browserSync = require('browser-sync').create(),

    // folders
    reload = browserSync.reload,
    folder = {
        src: 'app/',
        build: 'dist/'
  };

Step 4: Add Gulp task for Panini file processing

We’re going to be storing our source files in the ‘app’ directory and all HTML files in the ‘html’ directory under ‘app’. Panini’s folder structure and how they’re used can be found in the Panini Library Guide.

gulp.task('html', function () {
    var url = 'html/',
        htmlRoot = folder.src + url,
        out = folder.build;
    return gulp.src(htmlRoot + 'pages/' + '**/*.html')
        .pipe(panini({
            root: htmlRoot + 'pages/',
            layouts: htmlRoot + 'layouts/',
            partials: htmlRoot + 'partials/',
            helpers: htmlRoot + 'helpers/',
            data: htmlRoot + 'data/'
        }))
        .pipe(gulp.dest(out));
});

As you can see in the Gulp task calling Panini, the ‘root’ designates the folder structure of the web pages e.g. index.html files and sub-directory structure of the site. We’re only going to be using one file called ‘default.html’ under the ‘layouts’ folder which defines a default layout of all pages. ‘partials’ are individual HTML files that incorporate a section of a HTML page such as the HEAD tag, HEADER, FOOTER, or Sidebar sections. I’m not using ‘helpers’ or ‘data’ folders but still keeping those folder structure in case I need to use them in the future.

Step 5: Make Panini refresh its cache

By default Panini has a cache and will only process files that have changed. The problem with this is that if you only modified a partial, then only the partial is processed. The partials won’t be integrated with files in the ‘pages’ folder. Every time we modify a file we need Panini to clear the cache before we reassemble all the files with Panini.

gulp.task('resetPages', (done) => {
    panini.refresh();
    done();
});

Step 6: Setup BrowserSync to process and detect changes in the HTML directory

gulp.task('serve:dist', ['html'], function () {
    browserSync.init({
        server: './dist',
        port: 3000
    });

    gulp.watch([folder.src + 'html/{pages,layouts,partials,helpers,data}/**/*.html'], ['resetPages', 'html', reload]);
});

In the above BrowserSync Gulp task we’re running the ‘html’ task upon execution. Then we’re watching for changes in the Panini sub directories under the ‘html’ folder. If there’s any changes to these files, we’re calling ‘resetPages’ to clear the Panini cache. Then running ‘html’ task to assemble all the Panini files into full HTML files. Finally we’re reloading the browser to view the updated files.

The full gulpfile.js

var
    gulp = require('gulp'),
    panini = require('panini'),
    browserSync = require('browser-sync').create(),

    // folders
    reload = browserSync.reload,
    folder = {
        src: 'app/',
        build: 'dist/'
    };

gulp.task('html', function () {
    var url = 'html/',
        htmlRoot = folder.src + url,
        out = folder.build;
    return gulp.src(htmlRoot + 'pages/' + '**/*.html')
        .pipe(panini({
            root: htmlRoot + 'pages/',
            layouts: htmlRoot + 'layouts/',
            partials: htmlRoot + 'partials/',
            helpers: htmlRoot + 'helpers/',
            data: htmlRoot + 'data/'
        }))
        .pipe(gulp.dest(out));
});

gulp.task('resetPages', (done) => {
    panini.refresh();
    done();
});

gulp.task('serve:dist', ['html'], function () {
    browserSync.init({
        server: './dist',
        port: 3000
    });

    gulp.watch([folder.src + 'html/{pages,layouts,partials,helpers,data}/**/*.html'], ['resetPages', 'html', reload]);
});

To run BrowserSync from the command line, run the following command. Each time a change is detected, the tasks in Gulp watch will run and refresh the browser.

gulp serve:dist

Creating a Panini Template

Step 1: Setting Up the Panini Folder Structure

app
--> html
----> data
----> helpers
----> layouts
----> pages
----> partials

The current source folder structure is shown above. We have the main ‘app’ folder under our project. We’ll then have the ‘html’ folder inside ‘app’ for our HTML source files. Within ‘html’ we’ll have several folders for Panini templates. For this guide, I’m only going to use ‘layouts’, ‘pages’, and ‘partials’.

Step 2: Setting up the Default Template

Within the ‘layouts’ folder, we need to setup a default template called ‘default.html’. All pages within the site will use this default template.

<!-- default.html -->
<!DOCTYPE html>
<html lang="en">
  {{> head}}
  <body id="home">
    {{> header}}
    {{> body}}
    {{> footer}}
    {{> js}}
  </body>
</html>

You’ll notice in the content, there’s some non-HTML code structured in the format {{> head}}. This notation tells the Panini parser to look for files named within the brackets in the ‘partials’ folder. If a file is found, the content of that file will be inserted in into the default template at compile time.

There’s one Panini notation called {{> body}}. This is not loaded from the ‘partials’ folder. The ‘body’ is actually any HTML file from the ‘pages’ folder. So any file thats considered a page will have its content inserted into the body tag area of the template.

Step 3: Setting up the Head Partial

In step 2, I’m loading a ‘head’ partial in the default template. This will be the content within the head tag thats required in every HTML file.

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <meta name="description" content="{{description}}">

  <title>{{title}} | Panini Demo</title>

  <!-- Core CSS -->
  <link rel="stylesheet" href="{{root}}css/font-awesome.min.css">
  <link rel="stylesheet" href="{{root}}css/bootstrap.css">

  <!-- Custom CSS -->
  <link rel="stylesheet" href="{{root}}css/style.css">

</head>

You’ll notice within this partial I have several Panini tags such as {{title}} ,  {{description}}, and  {{root}}. The first two (title and description) are Panini variables that I created within the files in the ‘pages’ folder. The variables allow me to have dynamic page title and description.

The root variable is a Panini system variable. It references the site root. So if its placed in front of a URL, it’ll automatically update the URL in the generated HTML. More details on this variable can be found on Zurb’s website.

Step 4: Setting up the Header, and Footer Partials

The header and footer partial are mainly used for the header, footer, and navigation area appearing on every page of the site. Below is an example:

<nav class="navbar navbar-expand-sm bg-dark navbar-dark fixed-top">
  <div class="container">
    <button class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
      <span class="navbar-toggler-icon"></span>
    </button>
    <a href="index.html" class="navbar-brand">LoopLAB</a>
    <div class="collapse navbar-collapse" id="navbarCollapse">
      <ul class="navbar-nav ml-auto">
        <li class="nav-item">
          <a href="#home" class="nav-link">Home</a>
        </li>
        <li class="nav-item">
          <a href="#explore-head-section" class="nav-link">Explore</a>
        </li>
        <li class="nav-item">
          <a href="#create-head-section" class="nav-link">Create</a>
        </li>
        <li class="nav-item">
          <a href="#share-head-section" class="nav-link">Share</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

<header id="home-section">
  <div class="dark-overlay">
    <div class="home-inner">
      <div class="container">
        <div class="row">
          <div class="col-lg-8 d-none d-lg-block">
            <h1 class="display-4">Build <strong>social profiles</strong> and gain revenue and <strong>profits</strong></h1>
            <div class="d-flex flex-row">
              <div class="p-4 align-self-start">
                <i class="fa fa-check"></i>
              </div>
              <div class="p-4 align-self-end">
                Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque modi sed molestiae nihil autem totam, atque ex veniam. Magnam, suscipit! Incidunt qui sint nemo temporibus alias asperiores aliquam excepturi quas.
              </div>
            </div>
            <div class="d-flex flex-row">
                <div class="p-4 align-self-start">
                  <i class="fa fa-check"></i>
                </div>
                <div class="p-4 align-self-end">
                  Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque modi sed molestiae nihil autem totam, atque ex veniam. Magnam, suscipit! Incidunt qui sint nemo temporibus alias asperiores aliquam excepturi quas.
                </div>
              </div>
              <div class="d-flex flex-row">
                  <div class="p-4 align-self-start">
                    <i class="fa fa-check"></i>
                  </div>
                  <div class="p-4 align-self-end">
                    Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque modi sed molestiae nihil autem totam, atque ex veniam. Magnam, suscipit! Incidunt qui sint nemo temporibus alias asperiores aliquam excepturi quas.
                  </div>
                </div>
          </div>
          <div class="col-lg-4">
            <div class="card bg-primary text-center card-form">
              <div class="card-body">
                <h3>Sign Up Today</h3>
                <p>Please fill out this form to register</p>
                <form>
                  <div class="form-group">
                    <input type="text" class="form-control form-control-lg" placeholder="Username">
                  </div>
                  <div class="form-group">
                    <input type="email" class="form-control form-control-lg" placeholder="Email">
                  </div>
                  <div class="form-group">
                    <input type="password" class="form-control form-control-lg" placeholder="Password">
                  </div>
                  <div class="form-group">
                    <input type="password" class="form-control form-control-lg" placeholder="Confirm Password">
                  </div>
                  <input type="submit" class="btn btn-outline-light btn-block">
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</header>

Step 5: Setting up the Site Pages

Within the site, we’ll be having multiple pages. The common standard is to have ‘index.html’ within sub folders of the site. Every HTML file within the ‘pages’ folder and sub folders will be processed and inserted into the ‘body’ area in the default template. The filenames for the pages will be same when processing is finished and outputted into the ‘dist’ folder. Think of the pages in the ‘pages’ folder as your actual site structure. Using  {{root}} Panini tag when linking from page to page can help eliminate mistakes.

---
title: Panini Demo
description: Lorem ipsum.
---
<div class="container">
  <div class="starter-template">
    <h1>Bootstrap starter template A</h1>
    <p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
  </div>
</div><!-- /.container -->

As you can see above, this ‘index.html’ in the ‘pages’ folder has two variables for title and description at the top of the page. Panini will process the variable and insert the content of each variable into the final HTML file if it finds a match which we’ve added to the ‘head’ partial. Make sure to keep the dash lines in the source file so that Panini can find the beginning and end of the variable section.

Step 6: Setting up the JS Partials

The final partial we’ll be loading are the JavaScript files before the body tag. We’re loading the JS here to optimize page load speed.

<script src="{{root}}js/jquery.min.js"></script>
<script src="{{root}}js/popper.min.js"></script>
<script src="{{root}}js/bootstrap.min.js"></script>

We’re using the Panini root tag again, but you can easily replace local site references for common CSS frameworks and JS libraries.

That’s it to using Zurb Panini to modular web development workflow. I’m really loving the efficiency with Panini. Let me know what you think and what can be improved.

Leave a Comment