DEV Community

Cover image for Chapter 6 HTML part one
Ross Angus
Ross Angus

Posted on

Chapter 6 HTML part one

Cover image by Antoni Shkraba

Recap

Last time, we installed Webpack so we could process our JavaScript. With more JavaScript. We learned:

  • Webpack does for JavaScript what SASS does for CSS (and SASS)
  • How to configure Webpack for development
  • Two different types of module imports in JavaScript
  • Why we might use NPX as well as NPM
  • How to configure Webpack for production
  • How to keep these two configuration files DRY
  • What IIFEs are and why we use them
  • Writing client-side JavaScript modules and the correct way to import them

This time, we're finally going to work on generating HTML files.

Working with HTML

We're on the home straight now. We've done images, JavaScript and CSS. Just HTML to go. Soon, you'll be able to write your own code and discard me like an old sock.

HTML is less of a focus for a lot of Single Page Applications (called "SPA" by its fam). This is because most of the work of rendering snippets of HTML is taken up by JavaScript - the HTML is really just a wrapper into which JavaScript plonks markup.

But you might not want to do that. Wouldn't it be nice to split HTML into modules, so that repeated parts (such as the header and the footer) could be found in one place and then pulled into the page?

To achieve this goal, we'll need to import four different modules. Spoilers follow:

  1. PostHTML gives JavaScript the power to mess around with HTML
  2. posthtml-cli allows us to control PostHTML using Node, so we can string all this together
  3. posthtml-modules lets us cut up our page into tiny wee bits (also known as modules)
  4. htmlnano takes a HTML page and removes all the repeated whitespace, just like we've already done with our JavaScript and CSS

These packages aren't as popular as the ones we've been using up until now. That's for a couple of reasons:

MVC

Single-page applications don't need to generate multiple HTML pages. They build them on-the-fly.

MVC stands for "Model View Controller". This is one way to split up an application's code:

  • "Model" is basically the same thing as the data. It's what's feeding into the application and needs to be displayed
  • "View" is closest to a template. It's the structure which sits around the data and gives it shape and allows the user to visually group together similar parts of the page
  • "Controller" is all the business logic which decides what happens when the user interacts with the user interface in some way, such as clicking a "like" button

This way of splitting up the code is another example of separation of concerns.

The MVC approach is dominating the application market at the time of writing. The three main front-end frameworks which do this are React, Vue and Angular but there are many, many more.

Front-end MVC frameworks run client-side. This means all the JavaScript is parsed by the web browser. Our approach with PostHTML will be rendered server side (also called SSR)1. Because MVC frameworks handle everything which PostHTML and its pals do, PostHTML is not as popular as React.

JAMSTACK

The second reason that PostHTML isn't a very popular package is JAMSTACK. JAMSTACK sits on top of an MVC application and allows it to build out individual pages to create a multi-paged site. These pages exist as actual HTML pages for users and search engines to visit but once a modern browser lands on any single page, it kind of fakes a reload when the user requests to move to a different page.

The data for just that next page is pulled into the MVC application and the current page is rebuilt with the new content. Just in case the user looks at the address bar, that is rewritten with the new URL (even though that flat HTML page wasn't loaded).

This approach has a few advantages:

  • The smallest possible parcel of data is sent across the internet
  • The web browser usually doesn't have to re-render the whole page, just a small part of it
  • The user doesn't have to suffer the existential doubt with comes when they look at an empty screen for a fraction of a second
  • Because the pages all exist on the web server, the site can easily be spidered by search engines and users can enter the site on "deep" pages
  • Some code files split the JavaScript into what is running on the server and what is running in the browser. This means we can manipulate sensitive data (such as API keys) with JavaScript without the risk of exposing this to a public URL.

JAMSTACK frameworks do both server side rendering and client-side rendering. Twice the work!

But all of this is a course in it's own right and won't be covered here. Sorry! Let's get back to the more traditional, static approach for now.

Installing PostHTML, posthtml-cli, posthtml-modules and htmlnano

Let's install all four of the packages we want at once. In a terminal, type:

npm i -D posthtml posthtml-cli posthtml-modules htmlnano
Enter fullscreen mode Exit fullscreen mode

Congratulations! You have unlocked a new achievement! "Downloaded your first security vulnerability". Note that your terminal says:

5 moderate severity vulnerabilities
Enter fullscreen mode Exit fullscreen mode

What can you do about this? Glad you asked - usually, bugger-all.


Side quest: Node security woes
So we can run npm audit from a terminal to see a list of these issues. It tells us:
got  <11.8.5
Severity: moderate
Got allows a redirect to a UNIX socket - https://github.com/advisories/GHSA-pfrx-2q88-qq97
fix available via `npm audit fix --force`
Will install posthtml-cli@0.7.7, which is a breaking change
node_modules/got
  package-json  <=6.5.0
  Depends on vulnerable versions of got
  node_modules/package-json
    latest-version  0.2.0 - 5.1.0
    Depends on vulnerable versions of package-json
    node_modules/latest-version
      update-notifier  0.2.0 - 5.1.0
      Depends on vulnerable versions of latest-version
      node_modules/update-notifier
        posthtml-cli  >=0.8.0
        Depends on vulnerable versions of update-notifier
        node_modules/posthtml-cli
Enter fullscreen mode Exit fullscreen mode

Wait, so all versions of posthtml-cli greater than 0.7.7 depend upon a vulnerable version of got? And the fix is to install an older version of posthtml-cli? How does this make sense? Also to downgrade to the "safe" version of posthtml-cli is a breaking change. How broken exactly? No idea what to do with that information.

Should I even care?

The vulnerability is to posthtml_cli which helps us interact with PostHTML using the terminal. posthtml_cli sits within the devDependencies node of package.json. Which means it never makes it to the live site. It wouldn't make any sense on the live site. So this situation is only risky if a hacker had direct access to your computer, ran the site locally, then used the compromised version of got to ... do something?

Pro-tip: if a hacker has control over your computer, the gig is already up. They will be too busy stealing your data to bother running a dev environment.

Instead of forcing a breaking change, let's try the gentle method and see how far it gets us. In a terminal, type:

npm audit fix
Enter fullscreen mode Exit fullscreen mode

Did that fix the issue? No. Did it break anything else? Also no. Did it make us feel better? Little bit. Let's move on.



Configuring PostHTML

Remember when we installed Webpack and it came with it's own configuration file which needed to sit in the root of our project? PostHTML's got one of those too. It's called posthtml.json and looks like this:

{
  "input": "src/views/**/*.html",
  "output": "dist",
  "plugins": {
      "posthtml-modules": {
          "root": "./src/views",
          "initial": true
      },
      "htmlnano": {}
  }
}
Enter fullscreen mode Exit fullscreen mode

So create that, if you could. It sits along site package.json and all the rest in the root of the Node application.

You might have noticed that this is pointing at a directory which doesn't exist yet - a sub-folder of src called views. Why is this?

We need somewhere to put all our HTML where it won't get mixed up with all the other file types. For example, if we pointed PostHTML at the src directory and then set up a watch task to look for changes, this task would trigger every time we did something inside the scss or js folders too.

The reason it's called views echoes what I said earlier about MVC - the views part of MVC is the presentational HTML.


Wildcards
You might spot a bunch of asterisks hanging out on this line of posthtml.json2:
"input": "src/views/**/*.html",
Enter fullscreen mode Exit fullscreen mode

In this context, the asterisks are behaving as a "wildcard" (in normal JavaScript, a single asterisk would act as a multiplication symbol). Wildcard characters can stand for anything, like in that card game I've never played because it doesn't interest me. So the path here translates into English as "any files which end with a html file extension inside the src/views directory or any of the sub-directories (or no sub-directory)". So:

  Any sub-directory ↓↓ ↓ any file name
"input": "src/views/**/*.html",
or no sub-directory ↑↑
Enter fullscreen mode Exit fullscreen mode

You might reasonably assume that this pattern works in the same way as the sass package does - that it watches a directory and replicates anything it finds there into a different directory. Sadly, this isn't the case. But we'll fix that issue in a bit.



Finally, the plugins node calls two other packages we just installed, posthtml-modules and htmlnano. posthtml-modules has a number of different options we can specify. We've simply pointed it at our views directory and asked that when it first starts up, it should automatically run.

The second plugin is htmlnano the configuration of which is ... not well documented. So we've passing it an empty configuration object, which is the same as telling it to run with the default configuration. If we don't pass it something, it won't run at all.

Setting up the new commands

Back in package.json, let's set up our develop and build tasks for HTML files. They'll sit inside the scripts node and look like this:

"html-build": "posthtml -c posthtml.json",
"watch-html": "onchange \"src/views\" \"src/fragments\" -- npm run html-build",
Enter fullscreen mode Exit fullscreen mode

I've added them inside the scripts node at the bottom.

Hang on - there's another new directory referenced here called fragments. What's that all about?

More new directories

PostHTML will process any and all html files inside our views directory and we also want to use little fragments of HTML to include into these pages. By splitting these out into a new, sibling directory, we can ensure that these wee bits never end up as html files in their own right on the live site. So the directory structure of the src folder should look like this:

...
🗀 src
  🗀 fragments
  🗀 img
  🗀 js
  🗀 scss
  🗀 views
...
Enter fullscreen mode Exit fullscreen mode

Both the fragments and the views directories need to be scrutinised by the watch task, so if we change a file inside either, the page will rebuild.

What the new package.json commands mean

The html-build task invokes posthtml with a c flag:

"html-build": "posthtml -c posthtml.json",
Enter fullscreen mode Exit fullscreen mode

I can't find this in the documentation but my guess is that it stands for "configuration". The next argument passed is the path to the configuration JSON, so this checks out.

onchange watching two different directories

The other new command we've just added to package.json does something a little new:

"watch-html": "onchange \"src/views\" \"src/fragments\" -- npm run html-build",
Enter fullscreen mode Exit fullscreen mode

The package onchange can watch two different directories at the same time, and then run the specified command if either changes. This means we can use the watch-html command to look into both src/views and src/fragments and run the html-build command if any files in either directory changes.

Adding the new commands to the start and prepare commands

Your start and prepare commands should include our new commands, so they now look like this:

"start": "concurrently \"npm run serve\" \"npm run sass-dev\" \"npm run watch-images\" \"npm run watch-js\" \"npm run watch-html\"",
"prepare": "concurrently \"npm run sass-prod\" \"node tools/image-compress.js\" \"npx webpack --config webpack.prod.mjs\" \"npm run html-build\""
Enter fullscreen mode Exit fullscreen mode

Moving and splitting the HTML

Drag your index.html from dist to src/views. Let's break it up!

Create a new file inside your fragments directory called head.html. It should look like this:

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/main.css">
Enter fullscreen mode Exit fullscreen mode

Now edit the head tag of your index.html so it looks like this:

<head>
  <title>Hello Worm</title>
  <module href="src/fragments/head.html"></module>
</head>
Enter fullscreen mode Exit fullscreen mode

We've moved all the tags which are common to all pages on the site and put them inside one fragment. That path doesn't make a lot of sense, does it? The way we usually navigate between directories is to use ../ to move into the parent directory and to just add the sub-directory name in the same way as src is used here. This implies that all of the src directory lives under the views directory, which we know isn't the case.

This is happening because posthtml-modules is the package which is interpreting this path and as far as it's concerned, it's running in the root of the Node application. And the root of the Node application is where the src and dist directories live. But this certainly is confusing.

Let's check if this all works.

In a terminal, type:

npm start
Enter fullscreen mode Exit fullscreen mode

Hmm. Nothing there. Let's debug this. Is there anything in the dist directory yet? No? Of course - PostHTML only builds a new HTML file once there's been a change, which there hasn't been since we started the web server.

Add a space, then delete it, in src/views/index.html. Wait for the terminal to finish, then refresh your web browser.

Hopefully the web page should appear. Now change the Hello Worm text in index.html however you like, to make sure it updates automatically once you save.

Inside head.html, break the path to the CSS by changing the line:

<link rel="stylesheet" href="/css/main.css">
Enter fullscreen mode Exit fullscreen mode

For example:

<link rel="stylesheet" href="/css/main2.css">
Enter fullscreen mode Exit fullscreen mode

After you save the file, the text should change to Times New Roman, the most boring of typefaces. Change it back and the correct typeface should reappear!

Now open up the index.html file inside the dist directory. It should look like this, more or less:

<!doctype html><html class="no-js" lang="en-GB"><head><title>Hello Worm</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/css/main.css"></head><body> <p>Hello Worm</p> <p><img src="/img/example-01.webp" alt="An animal, yesterday"></p> <script src="/js/main.js"></script> </body></html>
Enter fullscreen mode Exit fullscreen mode

Ah, barely room to swing a cat in there. Just the way we like it.

Summary

What we've got so far does the following:

  • Allows us to create a page with server-side includes (that's another way of describing what posthtml-modules does)
  • Converts our SCSS to CSS then minifys it
  • Concatenates (joins together) and minifies our JavaScript files
  • Converts our images to next-gen versions

This is a great place to start! But I'd like to handle those images in a slightly better way which uses both posthtml-modules and all those next-generation image files we generated with sharp. Let's do that in the next part.

Let's review what we learned:

  • Currently, MVC is the dominant paradigm for application development on the web
  • JAMSTACK is MVC but with an added static website
  • How to handle Node security concerns
  • How to configure PostHTML
  • Splitting HTML into modules can save us effort

View Chapter 6 code snapshot on GitHub

Quiz


  1. Technically, we'll be creating the static HTML pages before the code even reaches the server. We're doing it on our local machine. Perhaps there's a word for this, but I couldn't find one in a cursory search. 

  2. Perhaps you're wondering what swear word you're supposed to type there instead. 

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs