Rewrite without hyperscript

Fixes #5. Reduces bundle by 6kB.
This commit is contained in:
Florine W. Dekker 2021-03-26 02:58:50 +01:00
parent 55cce0c433
commit 0e4f973553
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
3 changed files with 68 additions and 51 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -24,7 +24,6 @@
"prepare": "grunt clean deploy" "prepare": "grunt clean deploy"
}, },
"dependencies": { "dependencies": {
"hyperscript": "^2.0.2",
"milligram": "^1.4.1" "milligram": "^1.4.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,4 +1,3 @@
import h from "hyperscript";
import "../css/normalize.css"; import "../css/normalize.css";
import "milligram/dist/milligram.min.css"; import "milligram/dist/milligram.min.css";
import "../css/common.css"; import "../css/common.css";
@ -6,6 +5,17 @@ import "../css/nav.css";
import "../css/overrides.css"; import "../css/overrides.css";
/**
* Converts the given string to an HTML element.
*
* @param string the string to convert to an HTML element
* @param query the type of element to return
* @returns {HTMLElement} the HTML element described by the given string
*/
const stringToHtml = function (string, query) {
return new DOMParser().parseFromString(string, "text/html").body.querySelector(query);
}
/** /**
* Alias for `document.querySelector`. * Alias for `document.querySelector`.
* *
@ -41,22 +51,28 @@ export const doAfterLoad = function (fun) {
* @returns {HTMLElement} a base navigation element that will eventually be filled with contents * @returns {HTMLElement} a base navigation element that will eventually be filled with contents
*/ */
export const nav = function (highlightPath = "") { export const nav = function (highlightPath = "") {
const base = h("ul", const base = stringToHtml(`
h("li", h("a", {href: "https://fwdekker.com/"}, <ul>
h("div.logo", h("img.logo", {src: "https://fwdekker.com/favicon.png"})), <li><a href="https://fwdekker.com/">
h("b", "FWDekker") <div class="logo"><img class="logo" src="https://fwdekker.com/favicon.png" alt="FWDekker" /></div>
)) <b>FWDekker</b>
); </a></li>
</ul>
`, "ul");
fetch("https://fwdekker.com/api/nav/") fetch("https://fwdekker.com/api/nav/")
.then(it => it.json()) .then(it => it.json())
.then(json => json.entries.forEach(entry => base.appendChild(unpackEntry(entry, "/", highlightPath)))) .then(json => {
json.entries.forEach(entry => base.appendChild(stringToHtml(unpackEntry(entry, "/", highlightPath), "li")))
})
.catch(e => { .catch(e => {
console.error("Failed to fetch navigation elements", e); console.error("Failed to fetch navigation elements", e);
return []; return [];
}); });
return h("nav.nav", base); const nav = stringToHtml(`<nav class="nav"></nav>`, "nav");
nav.appendChild(base);
return nav;
}; };
/** /**
@ -66,25 +82,23 @@ export const nav = function (highlightPath = "") {
* @param [path] {number} the current path traversed, found by joining the names of the entries with `/`s; always starts * @param [path] {number} the current path traversed, found by joining the names of the entries with `/`s; always starts
* and ends with a `/` * and ends with a `/`
* @param [highlightPath] {String} the path to highlight together with its parents * @param [highlightPath] {String} the path to highlight together with its parents
* @returns {HTMLElement} the navigation list entry as HTML, described by its children * @returns {string} the navigation list entry as HTML, described by its children
*/ */
const unpackEntry = function (entry, path = "/", highlightPath = "") { const unpackEntry = function (entry, path = "/", highlightPath = "") {
const shouldHighlight = highlightPath.startsWith(`${path + entry.name}/`); const shouldHighlight = highlightPath.startsWith(`${path + entry.name}/`);
if (entry.entries.length === 0) if (entry.entries.length === 0)
return h("li", return `<li><a href="${entry.link}" class="${shouldHighlight ? "currentPage" : ""}">${entry.name}</a></li>`;
h("a", {href: entry.link, innerHTML: entry.name}),
{className: shouldHighlight ? "currentPage" : ""}
);
const depth = path.split("/").length - 2; // -1 because count parts, then another -1 because of leading `/` const depth = path.split("/").length - 2; // -1 because count parts, then another -1 because of leading `/`
const arrow = depth === 0 ? "&#9662;" : "&#9656;"; const arrow = depth === 0 ? "&#9662;" : "&#9656;";
return h("li", return `
h("a", {href: entry.link, innerHTML: `${entry.name} ${arrow}`}), <li class="${shouldHighlight ? "currentPage" : ""}">
h("ul", entry.entries.map(it => unpackEntry(it, `${path + entry.name}/`, highlightPath))), <a href="${entry.link}">${entry.name} ${arrow}</a>
{className: shouldHighlight ? "currentPage" : ""} <ul>${entry.entries.map(it => unpackEntry(it, `${path + entry.name}/`, highlightPath)).join("")}</ul>
); </li>
`;
}; };
@ -97,14 +111,16 @@ const unpackEntry = function (entry, path = "/", highlightPath = "") {
*/ */
export const header = function ({title, description}) { export const header = function ({title, description}) {
if (title === undefined && description === undefined) if (title === undefined && description === undefined)
return h("header.header"); return stringToHtml(`<header class="header"></header>`, "header");
return h("header.header", return stringToHtml(`
h("section.container", <header class="header">
title !== undefined ? h("h1", {innerHTML: title}) : undefined, <section class="container">
description !== undefined ? h("p", h("em", {innerHTML: description})) : undefined ${(title !== undefined ? `<h1>${title}</h1>` : "")}
) ${(description !== undefined ? `<p><em>${description}</em></p>` : "")}
); </section>
</header>
`, "header");
}; };
@ -127,20 +143,22 @@ export const footer = function (
author, authorURL, license, licenseURL, vcs, vcsURL, version, author, authorURL, license, licenseURL, vcs, vcsURL, version,
privacyPolicyURL = undefined privacyPolicyURL = undefined
}) { }) {
return h("footer.footer", return stringToHtml(`
h("section.container", <footer class="footer">
footerLink("Made by ", author, authorURL, ". "), <section class="container">
footerLink("Licensed under the ", license, licenseURL, ". "), ${footerLink("Made by ", author, authorURL, ". ")}
footerLink("Source code available on ", vcs, vcsURL, ". "), ${footerLink("Licensed under the ", license, licenseURL, ". ")}
footerLink( ${footerLink("Source code available on ", vcs, vcsURL, ". ")}
"Consider reading the ", ${footerLink(
privacyPolicyURL === null ? undefined : "privacy policy", "Consider reading the ",
privacyPolicyURL === undefined ? "https://fwdekker.com/privacy/" : privacyPolicyURL, privacyPolicyURL === null ? undefined : "privacy policy",
". " privacyPolicyURL === undefined ? "https://fwdekker.com/privacy/" : privacyPolicyURL,
), ". "
h("div", version || "", {style: {"float": "right"}}) )}
) <div style="float: right;">${version || ""}</div>
); </section>
</footer>
`, "footer");
}; };
/** /**
@ -150,18 +168,18 @@ export const footer = function (
* @param text {string|undefined} the text to display, or `undefined` if the returned element should be empty * @param text {string|undefined} the text to display, or `undefined` if the returned element should be empty
* @param url {string|undefined} the URL to link the text to, or `undefined` if the text should not be a link * @param url {string|undefined} the URL to link the text to, or `undefined` if the text should not be a link
* @param suffix {string} the text to display after the text if the text is not undefined * @param suffix {string} the text to display after the text if the text is not undefined
* @returns {HTMLElement} a footer link element * @returns {string} a footer link element
*/ */
const footerLink = function (prefix, text, url, suffix) { const footerLink = function (prefix, text, url, suffix) {
if (text === undefined) return h("span"); if (text === undefined) return "";
return h("span", return `
h("span", prefix), <span>
url !== undefined ${prefix}
? h("a", text, {href: url}) ${url !== undefined ? `<a href="${url}">${text}</a>` : text}
: h("span", text), ${suffix}
h("span", suffix) </span>
); `;
}; };