add html validation plugin

pyrox.dev 8d856805 e0c7f287

verified
+1 -1
TODO.md
···
- Performance, Accessibility, Best Practices, and SEO with
[lighthouse-ci](https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/getting-started.md)
- HTML, CSS, and JS validation
-
- [HTML-Validate](https://html-validate.org/)
+
- [x] [HTML-Validate](https://html-validate.org/)
- [csstree-validator](https://www.npmjs.com/package/csstree-validator)
- Grammar checking for source files(including inclusive language, spell checking, and other basic
checks)
+8 -3
_config.ts
···
import transform_images from "lume/plugins/transform_images.ts";
// Markdown-it plugins
-
import { default as md_linenums } from "npm:markdown-it-inject-linenumbers@0.3.0";
import { BiDirectionalLinks } from "npm:@nolebase/markdown-it-bi-directional-links";
import { default as mdItObsidianCallouts } from "npm:markdown-it-obsidian-callouts";
···
import googleFonts from "lume/plugins/google_fonts.ts";
// // Optimization
import lightningcss from "lume/plugins/lightningcss.ts";
-
import purgecss from "lume/plugins/purgecss.ts";
+
+
// Validation
+
import validateHTML from "./plugins/validateHTML.ts";
// Disabled Plugins:
// import og_images from "lume/plugins/og_images.ts";
// import pagefind from "lume/plugins/pagefind.ts";
+
// import purgecss from "lume/plugins/purgecss.ts";
// import sri from "lume/plugins/sri.ts";
// import nav from "lume/plugins/nav.ts";
+
// import { default as md_linenums } from "npm:markdown-it-inject-linenumbers@0.3.0";
// To Add:
-
// https://deno.land/x/lume_markdown_plugins@v0.8.0 (toc and footnotes plugins)
+
// https://deno.land/x/lume_markdown_plugins@v0.8.0 (footnotes plugins)
// https://deno.land/x/lume_shiki@0.0.16
const site = lume({
···
// Source Map Generation
// Applies to CSS and JS
site.use(source_maps());
+
+
site.use(validateHTML());
// Minify HTML Output
site.use(minify_html({
+2 -1
deno.json
···
"@nolebase/markdown-it-bi-directional-links": "npm:@nolebase/markdown-it-bi-directional-links@^2.14.0",
"lume/": "https://cdn.jsdelivr.net/gh/lumeland/lume@f6e8b76726f84055e7d56f201bad44ab4162ed9c/",
"markdown-it-obsidian-callouts": "npm:markdown-it-obsidian-callouts@^0.3.1",
-
"sharp": "npm:sharp@0.33.5"
+
"sharp": "npm:sharp@0.33.5",
+
"html-validate": "npm:html-validate@9.4.0"
},
"tasks": {
"lume": "echo \"import 'lume/cli.ts'\" | deno run --env-file=.env.dev -A -",
+70
deno.lock
···
"npm:dprint@*": "0.49.0",
"npm:estree-walker@3.0.3": "3.0.3",
"npm:highlight.js@11.11.1": "11.11.1",
+
"npm:html-validate@9.4.0": "9.4.0_ajv@8.17.1",
"npm:ico-endec@0.1.6": "0.1.6",
"npm:lightningcss-wasm@1.29.1": "1.29.1",
"npm:markdown-it-attrs@4.3.1": "4.3.1_markdown-it@14.1.0",
···
"tslib"
]
},
+
"@html-validate/stylish@4.2.0": {
+
"integrity": "sha512-Nl8HCv0hGRSLQ+n1OD4Hk3a+Urwk9HH0vQkAzzCarT4KlA7bRl+6xEiS5PZVwOmjtC7XiH/oNe3as9Fxcr2A1w==",
+
"dependencies": [
+
"kleur@4.1.5"
+
]
+
},
"@imagemagick/magick-wasm@0.0.31": {
"integrity": "sha512-QNivAUxSaItuiY8ziI/vRy6TtoecD7TOsD1LGZCG3wv8lfbdGbIj2QiJk0FlGkGwAVR966NlD3mkxPNvQrvq0w=="
},
···
"string.prototype.codepointat"
]
},
+
"@sidvind/better-ajv-errors@3.0.1_ajv@8.17.1": {
+
"integrity": "sha512-++1mEYIeozfnwWI9P1ECvOPoacy+CgDASrmGvXPMCcqgx0YUzB01vZ78uHdQ443V6sTY+e9MzHqmN9DOls02aw==",
+
"dependencies": [
+
"ajv",
+
"kleur@4.1.5"
+
]
+
},
"@tailwindcss/oxide-android-arm64@4.0.3": {
"integrity": "sha512-S8XOTQuMnpijZRlPm5HBzPJjZ28quB+40LSRHjRnQF6rRYKsvpr1qkY7dfwsetNdd+kMLOMDsvmuT8WnqqETvg=="
},
···
"@types/estree@1.0.6": {
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
+
"ajv@8.17.1": {
+
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+
"dependencies": [
+
"fast-deep-equal",
+
"fast-uri",
+
"json-schema-traverse",
+
"require-from-string"
+
]
+
},
"ansi-regex@5.0.1": {
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
···
"@types/estree"
]
},
+
"fast-deep-equal@3.1.3": {
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+
},
"fast-glob@3.3.1": {
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"dependencies": [
···
"merge2",
"micromatch@4.0.8"
]
+
},
+
"fast-uri@3.0.6": {
+
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="
},
"fastq@1.15.0": {
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
···
"highlight.js@11.11.1": {
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="
},
+
"html-validate@9.4.0_ajv@8.17.1": {
+
"integrity": "sha512-Io8vjeAf2JJJTHJDGvozVB6Vv/IZRkNRruSENg9lmLzE1gUcr77sIdtE2rzwLkBsCESDv5+nAAtk2TCer78jxA==",
+
"dependencies": [
+
"@html-validate/stylish",
+
"@sidvind/better-ajv-errors",
+
"ajv",
+
"glob@10.4.5",
+
"kleur@4.1.5",
+
"minimist",
+
"prompts",
+
"semver"
+
]
+
},
"ico-endec@0.1.6": {
"integrity": "sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ=="
},
···
},
"jsbi@4.3.0": {
"integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g=="
+
},
+
"json-schema-traverse@1.0.0": {
+
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+
},
+
"kleur@3.0.3": {
+
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
+
},
+
"kleur@4.1.5": {
+
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="
},
"lightningcss-wasm@1.29.1": {
"integrity": "sha512-yrcX5OfnB5NJY32h2kOCaUr/OUz/os51uy/dTqSi+Z5lkUgnI9XIzlFDjbru/DaMkJ7v08QKN14Yr75SvDnqjA==",
···
"brace-expansion@2.0.1"
},
+
"minimist@1.2.8": {
+
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
+
},
"minipass@7.1.2": {
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
},
···
"source-map-js"
},
+
"prompts@2.4.2": {
+
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+
"dependencies": [
+
"kleur@3.0.3",
+
"sisteransi"
+
]
+
},
"punycode.js@2.3.1": {
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="
},
···
},
"remove-markdown@0.6.0": {
"integrity": "sha512-B9g8yo5Zp1wXfZ77M1RLpqI7xrBBERkp7+3/Btm9N/uZV5xhXZjzIxDbCKz7CSj141lWDuCnQuH12DKLUv4Ghw=="
+
},
+
"require-from-string@2.0.2": {
+
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
},
"resolve@1.22.8": {
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
···
"dependencies": [
"is-arrayish"
+
},
+
"sisteransi@1.0.5": {
+
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
···
"https://deno.land/x/lume_init@v0.3.0/steps/update.ts": "ae09f416ab44b628f1d3f9d1e3394f4e661bbb3d915661a4585c79e4147fdf0a",
"https://deno.land/x/lume_init@v0.3.0/steps/utils.ts": "55fb173934c2f8db41cc0b0801783458f26f8c78ddb37fe67d2b38aba4513570",
"https://deno.land/x/lume_init@v0.3.0/upgrade.ts": "2a5ca0b4ae0db1f6771e6ec4cede420b6c267e882a38e5647bc7218fda862483",
+
"https://deno.land/x/lume_markdown_plugins@v0.8.0/title.ts": "03cf0c80d1454385bda883e3ebbfe3c0dd8512d887ae5095303e447bfa45a0b0",
+
"https://deno.land/x/lume_markdown_plugins@v0.8.0/title/mod.ts": "f77140fdce40c65d5422ffc071803d6922e736fa83209dc7ace40006bf908432",
"https://deno.land/x/lume_markdown_plugins@v0.8.0/toc.ts": "1fe2769056a022303b3871fc4b7be26b7738d44a31e5fd08debd527e9dc49ecc",
"https://deno.land/x/lume_markdown_plugins@v0.8.0/toc/anchors.ts": "8a4a1c6b2c63156622695ceba57fa7100a6e5f109c9a383a1dcaf755233c8184",
"https://deno.land/x/lume_markdown_plugins@v0.8.0/toc/mod.ts": "8c7aa6e1dcfabda4264503495a3875388108cd9a5a94b54853b45a8e8cba9f78",
···
"npm:@catppuccin/tailwindcss@~0.1.6",
"npm:@img/sharp-wasm32@0.33.5",
"npm:@nolebase/markdown-it-bi-directional-links@^2.14.0",
+
"npm:html-validate@9.4.0",
"npm:markdown-it-obsidian-callouts@~0.3.1",
"npm:sharp@0.33.5"
+42
plugins/validateHTML.ts
···
+
import { ConfigData, formatterFactory, HtmlValidate, Report, Reporter } from "html-validate";
+
import "lume/types.ts";
+
import { merge } from "lume/core/utils/object.ts";
+
import { log } from "lume/core/utils/log.ts";
+
+
export const defaults: ConfigData = {
+
extends: ["html-validate:recommended", "html-validate:document"],
+
rules: {
+
"doctype-style": "off",
+
"attr-quotes": "off",
+
"no-trailing-whitespace": "off",
+
"void-style": "warn",
+
"require-sri": ["error", { target: "crossorigin" }],
+
},
+
};
+
+
export default function (userOptions?: ConfigData) {
+
const options = merge(defaults, userOptions);
+
const htmlvalidate = new HtmlValidate(options);
+
const format = formatterFactory("text");
+
+
return (site: Lume.Site) => {
+
site.process([".html"], validatePages);
+
+
async function validatePages(pages: Lume.Page[]) {
+
var reports: Array<Promise<Report>> = [];
+
for (const page of pages) {
+
const report = await htmlvalidate.validateString(page.content as string, page.outputPath);
+
reports.push(report);
+
}
+
const merged: Report | Promise<Report> = Reporter.merge(reports);
+
+
if (merged.valid) {
+
log.info("[validateHTML] Validation successful!");
+
}
+
+
if (!merged.valid) {
+
log.error("[validateHTML]:\n" + format(merged.results));
+
}
+
}
+
};
+
}