Tuesday, January 30, 2018

Create static HTML website generator using Nunjucks and Gulp


In this article, we will learn how to create simple static website generator using Nunjucks and Gulp. Our implementation does not require any specific knowledge, just some basic JavaScript, and HTML. Of course, complexity depends on website functionality, but in our scenario, we will focus on the main idea of static website generation.

Tools

Project structure

|-- .editorconfig
|-- .gitattributes
|-- .gitignore
|-- src
    |-- build.sh
    |-- build.cmd
    |-- package.json
    |-- run.sh
    |-- run.cmd
    |-- gulpfile.js
    |-- build  // output directory for generated HTML
    |-- config // directory contains configuration for web server
    |-- css
    |-- img
    |-- pages // directory with web-site pages
        |-- 404.html
        |-- index.html
    |-- templates // nunjucks templates
        |-- macros
            |-- _header.html
        |-- parts
            |-- _footer.html
        |-- _globals.html
        |-- _layout.html

There are 3 files in the root of the project. .editorconfig is the interesting one. EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs. More information about EditorConfig could be found using the following link http://editorconfig.org/.

Building common layout 

Nunjucks is templating engine with block inheritance, auto-escaping, macros, asynchronous control, and more. To build our website we will start with defining master page layout using nunjucks way of templating. Link to nunjucks templating documentation: https://mozilla.github.io/nunjucks/templating.html

_layout.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="/css/style.css" />
  {% block head %}
    <title>My site</title>
    <meta name="keywords" content="">
    <meta name="description" content="">
  {% endblock %}
</head>
<body>
  {% block header %} {% endblock %}
  <div class="main">
    {% block content %} {% endblock %}
  </div>
  {% include "parts/_footer.html" %}
  <!-- Common scripts placeholder. -->
  {% block scripts %}
  {% endblock %}
</body>
</html>

As you can see layout contains the reference to _footer.html. Nunjucks allows you to extract several parts of  HTML markup into reusable components.

_footer.html

{% import '_globals.html' as globals %}

<footer class="footer">
 Check out website <a href="{{globals.website_url}}">{{globals.website_url}}</a>
</footer>

Website shared settings

It is useful to have one common place where you can store global settings for your website. For such purposes, we will create the_globals.html file, where will store shared configuration. On previous code snippet, you can find an example how to use global settings.

_globals.html

{% set website_url = "http://www.maniuk.net" %}

Creating page

All pages go to pages directory. The page might extend any of specified base layouts. Following code example shows markup for a simple index page.

index.html

{% extends "_layout.html" %}
{% import 'macros/_header.html' as header %}

{% block head %}
  <title>My site</title>
  <meta name="keywords" content="">
  <meta name="description" content="">
{% endblock %}

{% block header %}
{{ header.renderHeader('index') }}
{% endblock %}

{% block content %}
Hello world!
{% endblock %}

We want current page to be highlighted in navigation menu so we will create nunjucks macro to achieve that.

_header.html

{% macro renderHeader(activePage='index') %}
<header class="header">
  <div class="logo">
    <a href="/">
      <img src="/img/logo.png" alt="logo" />
    </a>
  </div>
  <nav class="navigation">
    <ul class="navigation-list">
      <li class="navigation-list-item {%if activePage == 'index' %}current{% endif %}">
        <a href="/" class="navigation-list-link">Index</a>
      </li>
      <li class="navigation-list-item {%if activePage == '404' %}current{% endif %}">
        <a href="/404.html" class="navigation-list-link">404 Page</a>
      </li>
    </ul>
  </nav>
</header>
{% endmacro %}

Building project with gulp

First of all, we need to specify the list of dependencies we need for a successful build.

"devDependencies": {
    "browser-sync": "^2.18.13",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^4.0.0",
    "gulp-clean-css": "^3.9.0",
    "gulp-concat": "^2.6.1",
    "gulp-htmlmin": "^3.0.0",
    "gulp-imagemin": "^3.3.0",
    "gulp-nunjucks-render": "^2.2.1",
    "gulp-sass": "^3.1.0",
    "gulp-uglify": "^3.0.0"
  },

End now it is time for a gulpfile.js file with build configuration.

gulpfile.js

var gulp =            require('gulp'),
    browserSync =     require('browser-sync').create(),
    sass =            require('gulp-sass'),
    uglify =          require('gulp-uglify'),
    autoprefixer =    require('gulp-autoprefixer'),
    cleanCSS =        require('gulp-clean-css'),
    imagemin =        require('gulp-imagemin'),
    nunjucksRender =  require('gulp-nunjucks-render'),
    concat =          require('gulp-concat'),
    htmlmin =         require('gulp-htmlmin');

// Static Server + watching scss/html files.
gulp.task('serve', ['sass', 'nunjucks-html-watch'], function() {
    browserSync.init({
        server: './build'
    });

    gulp.watch('css/dev/*.scss', ['sass']);
    gulp.watch('./**/*.html', ['nunjucks-html-watch'])
});

gulp.task('sass', function() {
    return gulp.src('css/style.scss')
        .pipe(sass())
        .pipe(autoprefixer({
            browsers: ['last 2 versions'],
            cascade: false
        }))
        .pipe(cleanCSS())
        .pipe(gulp.dest('./build/css'))
        .pipe(browserSync.stream());
});

gulp.task('compressJs', function () {
    return gulp.src('js/*.js')
        .pipe(uglify())
        .pipe(gulp.dest('build/js'))
});

gulp.task('compressImage', function () {
    return gulp.src('img/**')
        .pipe(imagemin({
            progressive: true,
            optimizationLevel: 3
        }))
        .pipe(gulp.dest('build/img'))
});

gulp.task('nunjucks', function() {
  return gulp.src('pages/**/*.+(html|nunjucks)')
    .pipe(nunjucksRender({
      path: ['templates']
    }))
    .pipe(htmlmin(
      {
        collapseWhitespace: true,
        removeComments: true
      }))
    .pipe(gulp.dest('build'))
});

// Create a task that ensures the `nunjucks` task is complete before reloading browsers.
gulp.task('nunjucks-html-watch', ['nunjucks'], function () {
  browserSync.reload();
});

gulp.task('vendors-scripts', function() {
  return gulp.src([
      './node_modules/jquery/dist/jquery.min.js'])
    .pipe(concat('vendors.js'))
    .pipe(gulp.dest('build/js/'));
});

gulp.task('copy-files', function() {
  gulp.src([
    'config/web.config'
  ])
  .pipe(gulp.dest('build'));
});

// Compile project.
gulp.task('build-project',
  ['sass', 'compressImage', 'compressJs', 'nunjucks', 'vendors-scripts', 'copy-files']);

// Compile and start project.
gulp.task('default', ['build-project', 'serve']);

Local development

I find it handy to use gulp build-in HTTP server, developers can easily emulate a server on their machine. Following bash script runs simple HTTP server for local development.

run.sh

#!/usr/bin/env bash
echo "Installing dependencies..."
npm install

echo "Running gulp"
./node_modules/.bin/gulp

Conclusion

Gulp and Nunjucks are great tools and it is possible to create static site generator using them. Full source code could be found on GitHub repo: https://github.com/aliakseimaniuk/blog-examples/tree/master/static-site-generator.

8 comments:

  1. After examine a couple of of the weblog posts on your web site now, and I really like your manner of blogging. I bookmarked it to my bookmark website record and will probably be checking back soon. Pls take a look at my web page as well and let me know what you think. brand

    ReplyDelete
  2. It is a great website.. The Design looks very good.. Keep working like that!. offshore software outsourcing

    ReplyDelete
  3. I found that site very usefull and this survey is very cirious, I ' ve never seen a blog that demand a survey for this actions, very curious... Groovepages

    ReplyDelete
  4. cool thanks for reis posting! btw are there feeds to your blog? I’d love to add them to my reader device mockup

    ReplyDelete
  5. I have to convey my respect for your kindness for all those that require guidance on this one field. Your special commitment to passing the solution up and down has been incredibly functional and has continually empowered most people just like me to achieve their dreams. Your amazing insightful information entails much to me and especially to my peers. Thanks a ton; from all of us. macbook mockups

    ReplyDelete
  6. This is such a great resource that you’re offering and you provide out at no cost. I appreciate seeing sites that realize the worth of offering a perfect useful resource totally free. I genuinely loved reading your submit. android mockup

    ReplyDelete
  7. Howdy! Do you know if they make any plugins to safeguard against hackers? I’m kinda paranoid about losing everything I’ve worked hard on. Any suggestions? webflow designers

    ReplyDelete
  8. If you happen to excited about eco items, sometimes be tough shock to anyone them recognise that to help make unique baskets just for this quite liquids carry basic steps liters associated ceiling fan oil producing. dc free mommy blog giveaways family trip home gardening house power wash baby laundry detergent webflow company

    ReplyDelete