Inspired by Matt's tweet about adding an HTML validator to his blog, I decided to build one for my site tinytip.co that is built with Eleventy 1.0.
My implementation is a bit different. I use the Eleventy Linter feature to run the validation and a different validator package that works offline. Let's implement it.
Add a linter
Add a new file html-validator.js
next to your .eleventy.js
config and export a validator function. The function will receive the content, the input path and the output path of the file processed by Eleventy:
exports.validate = function validate(content, inputPath, outputPath) {
// TODO
console.log("Hello from HTML validator");
};
Then we add the function as a linter to Eleventy. This will run our validate
function whenever a file is processed by Eleventy:
const htmlValidator = require("./html-validator");
module.exports = function (eleventyConfig) {
eleventyConfig.addLinter("html-validator", htmlValidator.validate);
};
Implement the validator
Next we want to validate our HTML. I use the npm package html-validate for that which works offline and comes with TypeScript definitions (which useful even if you don't use TypeScript). Install it as a dependency:
npm install -D html-validate
Now we can import the package, configure it and use it in our validate
function. We first make sure that the file is an HTML file and then call validateString()
to validate the content:
const { HtmlValidate, StaticConfigLoader } = require("html-validate");
const loader = new StaticConfigLoader();
const htmlValidate = new HtmlValidate(loader);
exports.validate = function validate(content, inputPath, outputPath) {
if (!outputPath.endsWith(".html")) return;
const validationResult = htmlValidate.validateString(content);
console.log(`${validationResult.valid ? "✅" : "❌"} ${outputPath}`);
};
Start your Eleventy site and you should see the output for all generated pages in your terminal.
Store the results
We currently only output the validation result (valid or invalid) but no details. For actually fixing issues we need to know what exactly is wrong. We could print more details but doing that in the terminal is not very readable. Instead, we can store the validation result in a file.
We create a results
object, store the result per file in that object and add a storeResults
function that creates a JSON file. I put the file into the output directory of Eleventy, but you can change that path to any other directory you like.
const fs = require("fs");
exports.results = {};
exports.storeResults = function storeResults() {
const content = JSON.stringify(exports.results, null, 2);
fs.writeFileSync("_site/html-validation.json", content);
};
exports.validate = function validate(content, inputPath, outputPath) {
// ...
exports.results[outputPath] = validationResult.valid
? undefined
: validationResult;
};
Finally we need to call the storeResults
function after Eleventy has finished processing all files. We can do that using the eleventy.after
event:
module.exports = function (eleventyConfig) {
eleventyConfig.on("eleventy.after", htmlValidator.storeResults);
eleventyConfig.addLinter("html-validator", htmlValidator.validate);
};
When running Eleventy, you should see the output for all generated pages in your terminal and the results in a JSON file.
Add more context
The validator provides the line on which each error occurred. To make the output more useful, we can add some context by adding this line of code (and some lines before and after) to the output:
exports.validate = function validate(content, inputPath, outputPath) {
// ...
const validationResult = htmlValidate.validateString(content);
const lines = content.split(/\r?\n/);
validationResult.results.forEach((result) => {
result.messages.forEach((message) => {
message.codeLines = lines.slice(
// Three lines before + actual line
Math.max(message.line - 4, 0),
// Three lines after
message.line + 3
);
});
});
// ...
};
This will add the actual code to the validation result.
Configure the validator
You can configure the validator by passing a config object to the StaticConfigLoader
constructor. This way you can for example disable some rules if you can't (or don't want to) fix them.
const loader = new StaticConfigLoader({
extends: ["html-validate:recommended"],
rules: {
"no-trailing-whitespace": "off",
},
});
That's it. You now see if your HTML is valid and if not you can fix the errors.
I implemented this validator for my site tinytip.co. Check it out if are interested in frontend tips, but please don't inspect the HTML code, I did not yet fix the errors 🙈