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:
- PostHTML gives JavaScript the power to mess around with HTML
- posthtml-cli allows us to control PostHTML using Node, so we can string all this together
- posthtml-modules lets us cut up our page into tiny wee bits (also known as modules)
- 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
Congratulations! You have unlocked a new achievement! "Downloaded your first security vulnerability". Note that your terminal says:
5 moderate severity vulnerabilities
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
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
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": {}
}
}
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.json
2:
"input": "src/views/**/*.html",
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 ↑↑
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",
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
...
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",
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",
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\""
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">
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>
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
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">
For example:
<link rel="stylesheet" href="/css/main2.css">
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>
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
Top comments (0)