Note
This is just a quick write up, I'll probably edit this post later to make it nicer to read

So the other night, I was reading an article on CSS tricks about writing HTML vs XHTML (why not?), and that linked off to another blog post which discussed the importance of validating your HTML.

Which got me thinking, why don't I start validating my HTML?

So I tried using W3's validator service, which broke when I tried to run it on this blog. After messing around with it for a bit I realised that I can take matters into my own hands...

The next night armed with Eleventy and the first NPM package I found on Google for HTML validation, I started hacking together something.

Essentially I already had some code that runs on the eleventy.after event, which formats my HTML/CSS/JS, so I hijacked that event and jammed in the html-validator package to check over my HTML files and log out when it found issues.

A simplified example of what I was doing:

const validate = require('validate.js');

module.exports = function (eleventyConfig) {
// do other stuff

eleventyConfig.on('elventy.after', validate);
};

Naturally it found a few issues, I fixed them, fired off a tweet and called it a night.

After seeing some interest in how I did things I decided to write a blog post, and ended up creating an NPM package instead.

eleventy-plugin-html-validate is a simple plugin that, when installed, runs on the eleventy.after event and scans through all the HTML files listed in the event's output.

To use it just install the package and in your .eleventy.js file:

const eleventyHTMLValidate = require('eleventy-plugin-html-validate');

module.exports = function (eleventyConfig) {
eleventyConfig.addPlugin(eleventyHTMLValidate);
};

If you're curious about whats under the hood, you can check out the source code, otherwise here's a slightly simplified version:

async function validateHTMLFiles(buildOutput) {
// grab all the HTML file paths
const htmlFilePaths = buildOutput.results
.map((r) => r.outputPath)
.filter((path) => path.match(/.html$/));

htmlFilePaths.forEach(async (filePath, i) => {
if (fs.existsSync(filePath)) {
try {
// load file
const options = {
format: 'text',
data: fs.readFileSync(filePath, 'utf8')
}

// validate
const result = await htmlValidator(options)
const pass = result.includes("The document validates according to the specified schema(s).")

// log out any errors
if (!pass) {
console.log(filePath + ' ❌');
console.log(result);
}
} catch (error) {
console.error(error);
}
}
}
}

Having referenced the 11ty docs again for writing this post I've realised that maybe I have access to the output of the eleventy.after event because I'm using one of the canary versions of Eleventy, but anyway you can try it out and see if it works 😅