node package manager

habit

A simple static site generator powered by nunjucks, LESS, and markdown.

habit

A simple static site generator based on nunjucks, LESS and markdown.

We wanted a static site generator that uses the same frontend stack as Apostrophe. We also wanted strong support for building accordion-style navigation, breadcrumb trails, "next" buttons and the like easily.

There is a sample site provided in the "test" folder:

cd test
node ../app.js

This creates a _site folder and copies and converts your files like this:

  • .md files are compiled with markdown and converted to .html as described below. They must include metadata, at least enough to specify the title as shown below.
  • main.less files are compiled with LESS and converted to .css as described below.
  • Files and folders starting with . or _ are ignored.
  • Everything else is simply copied.

By default each markdown file is fed to the nunjucks template at _layouts/default.html. The HTML generated by markdown is passed as the content variable.

Your markdown file must begin with a metadata section, like this:

---
title: "This is the title of my page"
---

Any metadata you provide is passed to the nunjucks layout template that renders your page.

Metadata is parsed as YAML, so you may pass as much data as you like. All of your metadata becomes available to Nunjucks templates, when rendering that page or any other that references it as a child, ancestor, etc. Note that Nunjucks auto-escaping is in effect, so use | safe in your layouts if you are intentionally passing markup.

Just use metadata:

---
title: "This is the title of my page"
layout: alternate
---

This will use the nunjucks template _layouts/alternate.html instead.

Yes, you may use the nunjucks extends keyword to extend another layout, override blocks and all that cool stuff.

To make relative links between pages just use the standard Markdown syntax. Keep in mind each file will have a .html extension.

If your site is going to be a standalone website living at root, do whatever you like with URLs that start with /. The --server convenience option pairs well with this.

If you need to build a site that can be moved around from folder to folder within a larger site, preface "absolute" links in your layouts with {{ root }}. This ensures that you can copy your _site folder into github pages or another environment where it will be in a subdirectory and not at the actual root of the site. If you know your content will be at the root you may ignore this.

If you wish, habit can provide you with all the information you need to build navigation links such as breadcrumb trails, lists of child pages, and "previous" and "next" links.

To take advantage, you must:

  • Ensure that every folder has an index.md page. This is considered the home page for that folder.
  • Include a children property in the metadata of that page. That property should be a list of the filenames of the child pages, without the file extension, in the order you want them displayed.

Confused? Here's a simple example of a page with children:

    ---
    title: "Advanced"
    children:
      - flossing
      - mortgaging
      - dancing
    ---
 
## Advanced life skills
 
Here you'll learn how to really rock your life.

That page would be called index.md, and the same folder would contain flossing.md, mortgaging.md, and dancing.md.

Once you do this, the following additional variables are visible in your page templates:

children is an array of objects with information about the child pages, with title and url properties, in addition to other metadata.

parent is an object with information about the parent of the current page. For flossing.md, this would be index.md. For index.md, it would be the index.md file of the parent folder.

ancestors is an array of objects with information about all of the ancestors of the current page plus itself. ancestors[0] is the home page (index.md in the root directory of your proejct). The last entry in ancestors is the current page.

previous is an object with information about the previous page with the same parent, if any.

next is an object with information about the next page with the same parent, if any.

Note that all of these objects have complete information. You may loop over ancestors in your nunjucks template and then loop over the children of each ancestor to create accordion-style navigation.

There is a complete example in the test folder. Just check out test/layouts/default.html to see how all of these variables are used.

If there is a file called main.less, it is compiled with LESS, and the result is stored as main.css. Yes, you may @import more .less files from your main.less file. You may have multiple main.less files in separate folders, if you are really on fire to have separate LESS compilation for them for some reason.

You may also use plain old .css files, which are simply copied, like anything else that doesn't start with _.

Need static assets like fonts and images? No problem; they are automatically copied, because they are not .less or .md files.

Install habit globally:

npm install -g habit

Now you can just cd to a folder containing the appropriate content and type:

habit

To rebuild the _site folder.

But for testing, this is more convenient:

habit --server

This will rebuild your _site folder, then launch a little webserver ready for you to check out your pages at http://localhost:3000 by default. You may change that with the ADDRESS and PORT environment variables.

Up to you, but here's a little shell script we use:

#!/bin/bash 
 
habit &&
rsync --delete -a _site/ user@example-server.com:/var/www/mysite &&
echo "Done!"

0.4.1: clarify that --server does rebuild your site first. No code changes.

0.4.0:

  • New --server convenience option, which listens at http://localhost:3000 by default. You may change that with the ADDRESS and PORT environment variables.
  • Updated dependencies, notably less and nunjucks.
  • nunjucks now auto-escapes the data you pass in via YAML. If you are passing something that should be used directly as markup, use the | safe nunjucks filter in your layout. You do not need to use | escape or | e.
  • ignore a top-level node_modules folder. This makes sense because it allows a site to be built with habit as a dependency rather than using habit globally. Thanks to Alex Gleason.

0.3.4: added a process.exit(0) call to address a situation where habit does not otherwise terminate in node 0.12.7 on a Mac when installed globally. Despite chasing this with process._getActiveHandles and process._getActiveRequests I still don't know why.

¯\_(ツ)_/¯

0.3.3: filename is reported when a nunjucks error occurs.

0.3.2: filename is reported when a markdown error occurs.

0.3.1: linked to github repository. No code changes.

0.3.0: linking to headings within a page is much nicer. Headings now contain anchor elements with the anchor class, as seen on github. The name of each heading is hyphenated, like a CSS class name. This automatically does the right thing whether the original name was "a sentence with spaces", "aCamelCaseFunctionName", or "an_underscored_function_name". If a heading contains (, that character and everything after it is ignored when constructing the name, allowing you to easily link to documentation for a function that includes the parameters in the header. However, if two headings would otherwise have the same name, 2 is appended to the second one, 3 to the third one and so on.

0.2.2: just documentation fixes.

0.2.1: The "next" and "previous" properties now allow you to traverse the entire site depth-first. That is, you can read through the entire "book" by continuing to click "next."

0.2.0: metadata and navigation-building data.

0.1.6: fixed @import in LESS.

0.1.3, 0.1.4, 0.1.5: renamed habit.

0.1.2: global install works.

0.1.1: introduced the policy of ignoring dotfiles and _ files, filtering markdown and LESS, and just copying everything else. This makes much more sense than a long list of special cases. This change renames the layouts folder to _layouts so they won't be copied.