mcollina / fastify-html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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>&copy; 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