PostHTML Static Site Starter

PostHTML Logo

TL;DR

Setup a simple and basic 'static site generator' with html partials/includes, linting, minification, along with live reload and a dev/build workflow.

Check out the static-site-starter which combines the dev and build processes from the next post on setting up a PostCSS workflow.

PostHTML and PostCSS

For simple static site development and even server side development, using NPM as my build tool and task runner has become my go to setup. You can get going with a straight forward approach.

I've just learned about PostCSS which has about 23K stars on github. It isn't just a SASS replacement, it allows you to do so many cool things which I'll cover in the next blog post.

After updating my static site starter repo by replacing node-sass with PostCSS I stumbled upon PostHTML.

This tool was the missing component to my workflow. In this post I'll cover getting started with NPM build scripts and setting up PostHTML to do templating, linting, minification and some other things. Checkout my first post on working with NPM to handle task running and build tools.

PostHTML is similar to PostCSS in that it takes raw HTML and parses it into a AST by way of JSON. You then modify and transform the AST with plug-ins. There are several middleware packages for express, hapi and koa as well for server side development.

Minimal Static Site Generator

While tools like Jekyll and Elevnty do many of the features of a static site generator (SSG) for us, PostHTML allows developers to build their own workflow using a plug-in based approach.

In my case, I've been looking for a tool that is capable of including partials much like using template tags in a SSG. I don't want to learn another SSG and have to learn the in's and out's of it.

The idea of rolling my own custom setup is more appealing and fun. Plus, there are tools that make it a relative breeze. Yes, a SSG provides many more features but for my approach I want to keep complexity minimal by design.

Setup

I've created a bash script that will setup our project structure and install all npm packages. Create a new folder and cd into and create a bash script called setup.sh.

Copy the below code and run bash setup.sh in your console.

If you have cloned the repo to your machine, run npm i to install the NPM packages.

setup.sh
      
        mkdir -p {css,html,js,img}
        cd css && mkdir {base,config,objects,globals,components,utilities,pages}
        touch styles.css
        cd ..

        cd js && touch scripts.js && echo "console.log('scripts.js is working');" > scripts.js && cd ..
        cd html && mkdir -p {pages,templates}
        cd templates && mkdir -p {views,components}
        cd views && touch index.html && cd ../../..
        touch README.md

        npm init -y
        npm i -D posthtml posthtml-cli posthtml-load-config html-minifier posthtml-modules posthtml-w3c posthtml-hint posthtml-classes posthtml-bem posthtml-favicons browser-sync npm-run-all onchange

        git init
        touch .gitignore
        echo "node_modules/" >> .gitignore
        echo "**/.DS_Store" >> .gitignore
      

One advantage with PostHTML is running the configuration process from your package.json file itself. posthtml-load-config provides several options to auto load plugins.

Here is the html directory:

  • html
    • pages
    • templates
      • components
      • views

Final rendered pages will be located in /pages and /components will contain our partials/includes — whatever naming convention works for you! The /views will contain the html files we use to create the pages. Feel free to change the folder names to your liking.

Includes

PostHTML makes includes and modules trivial. Let's see how easy this is to setup. There are two packages that provide this functionality, posthtml-modules and posthtml-include.

Simply include the module with the module tag:

      
        <module href="html/templates/components/head.html"></module>
      

The code below will render the head.html contents into our index.html.

html/templates/views/index.html
      
        <html class="no-js" lang="en" dir="ltr">

        <module href="html/templates/components/head.html"></module>

        <body class="index">

          <header class="index__header">
            <h1>PostHTML Classes</h1>
          </header>
          <main class="index__main">

          </main>
          <footer class="index__footer">Here is my lint page</footer>
        </body>
        <script src="js/scripts.js"></script>

        </html>
      
html/templates/components/head.html
      
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover">
          <meta name="robots" content="index,follow">
          <meta name="googlebot" content="index,follow">
          <title>PostHTML</title>
          <meta name="description" content="Description goes here">
          <link rel="stylesheet" href="css/styles.css" media="print" onload="this.media='all'">
          <link rel="icon" href="img/posthtml-logo.png" />
        </head>
      

Will compile into our complete page:

html/templates/components/head.html
      
        <html class="no-js" lang="en" dir="ltr">

        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover">
          <meta name="robots" content="index,follow">
          <meta name="googlebot" content="index,follow">
          <title>PostHTML</title>
          <meta name="description" content="Description goes here">
          <link rel="stylesheet" href="css/styles.css" media="print" onload="this.media='all'">
          <link rel="icon" href="img/posthtml-logo.png">
        </head>


        <body class="index">

          <header class="index__header">
            <h1>PostHTML Classes</h1>
          </header>
          <main class="index__main">

          </main>
          <footer class="index__footer">Here is my lint page</footer>


          <noscript>
            <link rel="stylesheet" href="css/styles.css">
          </noscript>
        </body>
        <script src="js/scripts.js"></script>

        </html>
      

Basic stuff, but now you got an easier way to re-use markup across your site. You can also pass data into the template.

Next, we need to add the PostHTML object to the package.json. The plugins object will run your installed PostHTML plugins in the order they are listed.

package.json
      
        "posthtml": {
          "plugins": {
            "posthtml-modules": {
              "root": "html/templates/views"
            }
          }
        }
      

The root tells the plugin the root folder to look for our html for module lookup.

Now, we can run the posthtml command on our /views and output to /pages.

package.json
      
        "html:process": "posthtml 'html/templates/views/*.html' -o html/pages"
      

Now let's run the html:process command in the console to process our html.

      
        npm run html:process
      

Watch For Changes

We'll be using browser-sync and onchange to reload our changes when we make an edit and hit the save command. html:watch will watch for changes specifically within the html/templates directory and not the entire html folder.

browser-sync handles the live reloading and syncs across multiple browsers. This tool alone will save you so much time and clicks.

The html:process command compiles the final html to html/pages. We will serve our static files from this directory by using --ss and passing our path. The --files flag will watch for changes and live reload the browser when a change is detected on the set of paths/files we provide. You'll need to wrap them in '' when passed to the flag. --no-notify and will display a notice message when updated and --no-open will not open a new browser.

package.json
      
        "html:watch": "onchange 'html/templates' -- run-p html:process",
        "dev:server": "browser-sync start --server --ss 'html/pages' --files 'html/templates/**/*.html' --no-notify --no-open"
      

Watch Out For Endless Loops

I found that if I watched the entire /html folder that onchange would invoke an endless loop in my console. The html:process command is run anytime a change is made. New html is generated in the /pages folder. When this output is generated, onchange detects this as a change event and fires the html:process command again ad infinitum. This requires a little structure and specificity in your build scripts, which ain't a bad thing.

package.json
      
        "html:watch": "onchange 'html' -- run-p html:process" // will cause onchange to run in an endless loop
      

Linting and Validating

At this point, we have two paths to take. Keep adding plugins to our PostHTML configuration or separate things into different build steps within our NPM scripts section to be executed at a specific time or for a certain need.

We can lint our html as we develop and validate before we build our code into the /dist. In other words, we don't need to validate our html against W3C every single time, only when we are ready to publish.

I'm going to use the posthtml-hint package to lint our html.

package.json
      
        "posthtml": {
          "plugins": {
            "posthtml-modules": {
              "root": "html/templates/views"
            },
            "posthtml-hint": {}
          }
        }
      

When we run html:process, the linter output is shown in the console. We can use posthtml-w3c to validate our html against the W3C validator for a build step.

We'll create a specific build step in our scripts section that calls the PostHTML command using the --use flag with posthtml-w3c.

package.json
      
        "build:html-validate": "posthtml 'html/pages/*html' --use posthtml-w3c"
      

HTML to Classes

I'm tired of finishing up my markup only to have to re-write the same structure in CSS. There are handy web tools that will convert your markup to CSS. Or you can just run it from your command line with posthtml-classes. This will output nested SASS like CSS with ampersands and all.

In the utilities/html-css.js add this code:

utilities/html-css.js
      
        const fs = require('fs');
        const posthtml = require('posthtml');
        const config = {
                fileSave: true,
                filePath: 'css/index.css', // change css output name
                overwrite: true,
                eol: '\n',
                nested: true,
                curlbraces: true,
                elemPrefix: '__',
                modPrefix: '_',
                modDlmtr: '_'
            }
        const html = fs.readFileSync('html/templates/views/index.html');

        posthtml()
            .use(require('posthtml-classes')(config))
            .process(html);
      

Add some basic markup to the index.html.

html/templates/views/index.html
      
        <body class="index">
          <header class="index__header">
            <h1>PostHTML Classes</h1>
          </header>
          <main class="index__main">

          </main>
          <footer class="index__footer">Here is my lint page</footer>
        </body>
      

Update the package.json with this:

package.json
      
        "html:css": "node utilities/html-css.js"
      

And run from the command line:

      
        npm run html:css
      

And you'll get the below code. This can be helpful for large pages of markup!

css/index.css
      
        .index {
          &__header {
          }
          &__main {
          }
          &__footer {
          }
        }
      

BEM

BEM is a naming method for writing modular CSS. BEM is an approach to writing maintainable CSS and is what I'm using at the current moment, so skip over if not interested.The posthtml-bem will convert some special html markup into plain html with the BEM-ified naming structure.

html/templates/bem/index.html
      
        <body block="page">
          <header elem="header">
            <h1>PostHTML Classes</h1>
          </header>
          <main elem="main">

          </main>
          <footer elem="footer"></footer>


          <noscript>
            <link rel="stylesheet" href="css/styles.css">
          </noscript>
        </body>
      

This compiles the markup to:

html/templates/views/index.html
      
        <body class="page">

          <header class="page__header">
            <h1>PostHTML Classes</h1>
          </header>
          <main class="page__main">

          </main>
          <footer class="page__footer"></footer>

          <noscript>
            <link rel="stylesheet" href="css/styles.css">
          </noscript>
        </body>
      

I can see a workflow like this:

  1. Write html using BEM markup and compile to regular HTML
  2. Convert markup to CSS structure with above the posthtml-classes script
  3. Make changes to new the final markup and styles as needed

This could be helpful when first starting out on a project where your doing more markup coding and figuring how things will flow. I usually do a rewrite at least 1-3 times before I have settled on my final structure. The above workflow can be helpful once you get used to it. Definitely follows in the steps of keeping things DRY.

You could totally run this process with onchange to compile BEM markup to HTML but you would need to modify bem.js to be dynamic and watch the /bem directory. Sounds like an idea for another PostHTML plugin.

Favicons

You always need favicons and posthtml-favicons has got you covered with extensive configuration options. With default settings enabled you will get tons of favicon sizes for all types of sources You do not need them all, so spend some time to select those you need otherwise you will have megabytes of image files.

The outDir will process these to dist/img/favicons and set the href path with to be img/icons.

utlities/favicons.js
      
        const fs = require('fs');
        const posthtml = require('posthtml');
        const favIconPlugin = require("posthtml-favicons");
        const html = fs.readFileSync('html/templates/components/head.html');

        posthtml()
            .use(favIconPlugin({ outDir: "./dist/img/favicons", configuration: { path: "img/favicons" } }))
            .process(html)
            .then((res) => {
                fs.writeFileSync('html/templates/components/head.html', res.html);
            });
      

We're reading in the head.html which contains a link to an image. This tells the favicon package to use this as the basis for our favicon image. We export this back into the same file so that we can use this new code for compiling the final markup.

Add it to your npm scripts:

package.json
      
        "html:favicon": "node utilities/favicons.js",
      

And let's run it:

      
        npm run html:favicons
      

Build Steps

Cool, we learned some really awesome features of PostHTML and improved our dev workflow along the way. Let's publish our site.

I tried using posthtml-minifier but it didn't work for some reason. It is basically a wrapper around html-minifier. We simply create a /dist folder with some directories for our files, minify our html files and copy them into /dist.

We can run all the build steps in serial (one after each other in sequence) with the run-s command:

      
        "build:dist": "mkdir -p dist/{css,js,img,fonts}",
        "build:html-validate": "posthtml 'html/pages/*html' --use posthtml-w3c",
        "build:minify": "html-minifier --input-dir html/pages --output-dir dist --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-tag-whitespace",
        "build:html-dist": "npm run html:process && cp html/pages/*.html dist",
        "build": "run-s build:*"
      

Next Level

In terms of performance, the next level would be to uglify CSS classnames and ID's but that is bit cumbersome and tedious with my current setup. While def not necessary, it can only help with site speed. This shows where rolling your own build tool begins to breakdown. Webpack provides packages that do this.

That's it! This should cover simple web development workflows.