proposal to replace the default html tag
gurgunday opened this issue · comments
Hey, just wanted to ask if you'd be down to integrating my html tag to fastify-html
For very short strings it is 10x faster, for long strings it can be more than 700x faster
It also supports html escaping by default – to indicate that an expression is safe to render raw, it must be prefixed with !
I think people who are using React & Co. just to send basic html to users are making it much harder than necessary
Random benchmark results (html2 is common-tags)
html1 with safe input x 29,247,213 ops/sec ±0.79% (98 runs sampled)
html1 with safe input x 22,540,186 ops/sec ±0.09% (91 runs sampled)
html1 with safe input x 32,791,098 ops/sec ±0.10% (96 runs sampled)
html1 with safe input x 10,819,679 ops/sec ±0.62% (99 runs sampled)
html1 with safe input x 32,523,957 ops/sec ±0.43% (98 runs sampled)
html2 with safe input x 41,971 ops/sec ±0.14% (98 runs sampled)
html2 with safe input x 2,225,232 ops/sec ±0.20% (99 runs sampled)
html2 with safe input x 3,117,999 ops/sec ±0.42% (96 runs sampled)
html2 with safe input x 1,874,689 ops/sec ±0.23% (94 runs sampled)
html2 with safe input x 3,090,945 ops/sec ±0.51% (97 runs sampled)
Benchmark
import benchmark from "benchmark";
import { html as html1 } from "./html.js";
import { html as html2 } from "common-tags";
// Define input data
const dangerousOutputWithHTMLEscapeChars = "<script>".repeat(1000);
const safeOutput = "This is safe content!".repeat(1000);
// Create benchmark suite
const suite = new benchmark.Suite();
// Add tests to the suite
suite
.add("html1 with safe input", () => {
html1`<div>!${safeOutput}</div>`;
})
.add("html1 with safe input", () => {
html1`<div>!${123321}</div>`;
})
.add("html1 with safe input", () => {
html1`<div>!${undefined}</div>`;
})
.add("html1 with safe input", () => {
html1`<div>!${[null, 123, undefined]}</div>`;
})
.add("html1 with safe input", () => {
html1`<div>!${""}</div>`;
})
.add("html2 with safe input", () => {
html2`<div>!${safeOutput}</div>`;
})
.add("html2 with safe input", () => {
html2`<div>!${123321}</div>`;
})
.add("html2 with safe input", () => {
html2`<div>!${undefined}</div>`;
})
.add("html2 with safe input", () => {
html2`<div>!${[null, 123, undefined]}</div>`;
})
.add("html2 with safe input", () => {
html2`<div>!${""}</div>`;
})
.on("cycle", function (event) {
console.log(String(event.target));
})
.on("complete", function () {
console.log("Fastest is " + this.filter("fastest").map("name"));
})
// Run the benchmark
.run({ async: true });
A complex example
Here's an example where the html
tag can completely replace React's rendering model while natively supporting Prettier formatting, Tailwind completions, and all the other goodies
// layout.js
const Layout = (it, View) => {
return html`<!doctype html>
<html
lang="${it.user ? "" : "en"}"
data-theme="${it.user?.data.theme || "blackHEYHEY"}"
class="mx-auto max-w-[1440px] overflow-auto scroll-smooth bg-cover bg-center md:bg-fixed"
>
<head>
!${Meta(it)}
<link rel="stylesheet" href="/p/assets/css/style.min.css" />
<link
rel="icon"
sizes="192x192"
href="/p/assets/img/lolo-192x192.png"
/>
<link
rel="apple-touch-icon"
sizes="192x192"
href="/p/assets/img/lolo-192x192.png"
/>
!${it.scripts?.map((script) => {
return html`
<script
fetchpriority="low"
type="module"
src="/p/assets/js/${script}"
></script>
`;
})}
<script defer src="/p/assets/misc/js/quicklink.min.js"></script>
!${it.pageBackground &&
it.user?.data.profileBG &&
it.user.data.profileBGLastUploadDate &&
it.user.data.isPro
? html`
<style>
html {
background-image: url("${process.env
.S3_CDN_URI}/userAssets/${it.user.id}/profileBG.webp");
}
body {
backdrop-filter: blur(
${it.user.data.profileBGBlur ?? "10"}px
);
-webkit-backdrop-filter: blur(
${it.user.data.profileBGBlur ?? "10"}px
);
}
</style>
`
: ""}
!${it.user?.data.theme === "custom"
? html`
<style>
[data-theme=custom] {
${it.user.getThemeCustomResult()}
}
</style>
`
: ""}
</head>
<body class="min-h-screen overflow-hidden">
<header>
<nav class="navbar">
<div class="navbar-start">
<a href="/" class="btn btn-ghost text-lg font-black capitalize">
HEYHEY
</a>
</div>
<div class="navbar-end">
!${it.sessionAccount
? html`
<a
href="/p/account/profile"
class="btn btn-square btn-ghost"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
class="fill-current"
>
<title>Settings Icon</title>
<path
d="m2.344 15.271 2 3.46a1 1 0 0 0 1.366.365l1.396-.806c.58.457 1.221.832 1.895 1.112V21a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-1.598a8.094 8.094 0 0 0 1.895-1.112l1.396.806c.477.275 1.091.11 1.366-.365l2-3.46a1.004 1.004 0 0 0-.365-1.366l-1.372-.793a7.683 7.683 0 0 0-.002-2.224l1.372-.793c.476-.275.641-.89.365-1.366l-2-3.46a1 1 0 0 0-1.366-.365l-1.396.806A8.034 8.034 0 0 0 15 4.598V3a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v1.598A8.094 8.094 0 0 0 7.105 5.71L5.71 4.904a.999.999 0 0 0-1.366.365l-2 3.46a1.004 1.004 0 0 0 .365 1.366l1.372.793a7.683 7.683 0 0 0 0 2.224l-1.372.793c-.476.275-.641.89-.365 1.366zM12 8c2.206 0 4 1.794 4 4s-1.794 4-4 4-4-1.794-4-4 1.794-4 4-4z"
></path>
</svg>
</a>
<a
href="/${it.sessionAccount.id}"
class="btn btn-square btn-ghost"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
class="fill-current"
>
<title>Home Icon</title>
<path
d="m21.743 12.331-9-10c-.379-.422-1.107-.422-1.486 0l-9 10a.998.998 0 0 0-.17 1.076c.16.361.518.593.913.593h2v7a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-4h4v4a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-7h2a.998.998 0 0 0 .743-1.669z"
></path>
</svg>
</a>
`
: html`
<a href="/p/login" class="btn btn-square btn-ghost">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current"
>
<title>Login Icon</title>
<path
d="M12 2C6.579 2 2 6.579 2 12s4.579 10 10 10 10-4.579 10-10S17.421 2 12 2zm0 5c1.727 0 3 1.272 3 3s-1.273 3-3 3c-1.726 0-3-1.272-3-3s1.274-3 3-3zm-5.106 9.772c.897-1.32 2.393-2.2 4.106-2.2h2c1.714 0 3.209.88 4.106 2.2C15.828 18.14 14.015 19 12 19s-3.828-.86-5.106-2.228z"
></path>
</svg>
</a>
`}
</div>
</nav>
</header>
!${View(it)}
!${it.user
? ""
: html`
<footer class="footer footer-center rounded p-16">
<div class="grid grid-flow-col gap-5">
<a href="mailto:hey@heyhey,to" class="link-hover link">
Contact
</a>
<a href="/p/privacy" class="link-hover link">Privacy</a>
<a href="/p/terms" class="link-hover link">Terms</a>
</div>
<div>
<p>© GOODMEN VENTURES, Inc.</p>
</div>
</footer>
`}
</body>
</html>`;
};
Let's do it!
Great!
There are some changes that I'll need to do since the package is currently ESM only and requires node 20 (because of the RegExp v
tag)
I just invited you as well, so feel free to accept it or not
We can bump this to node v20 and esm, no need to do cjs here