Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to render multiple items using Array.map() + question about escaping HTML #14

Open
neg4n opened this issue Feb 12, 2023 · 10 comments
Open

Comments

@neg4n
Copy link

neg4n commented Feb 12, 2023

Hello 👋🏻

Thanks for creating this library and ultrahtml, they're both 🔥 however, I'm struggling to implement dynamic rendering of array of items (as it is commonly done in JSX). Let me present an example:

I'm using https://astro.build and I'm implementing dynamic open-graph images generation with static output of the image files. I use satori-html and @resvg/resvg-js with successfully created partial resemble of @vercel/og library. Everything goes flawlessly until my try to render tags from one of the articles on the blog.

html`${tags.map((tag) => `<span>${tag}</span>`).join("")}`
//     ^^^^^^ this will return escaped string

and in real world I'm getting something like this on my generated image
image

After some digging I found that all the strings from values injected by template are escaped by default

https://github.com/natemoo-re/ultrahtml/blob/2cb77629b075799c7da03742bf7c7dd7681d9166/test/html.test.ts#L9-L12

Is there a way of disabling the escape I'm missing? Or maybe it would be possible to add such way?

Cheers. Igor

@felixhaeberle
Copy link

I'm also facing this issue, has somebody already found a solution?

@nzoschke
Copy link

nzoschke commented May 7, 2023

Same issue here, using under astro and trying to turn an array into child elements.

ultrahtml shows that you can nest html tags

  it("nested", () => {
    const { value } = html`<h1>${html`<div></div>`}</h1>`;
    expect(value).toEqual(`<h1><div></div></h1>`);
  })

however that doesn't work with this wrapper, it turns it to an [object Object] string

const h = html`<h1>${html`<div></div>`}</h1>`;
console.log(h.props.children[0]);
{ type: 'h1', props: { children: '[object Object]' } }

@felixhaeberle
Copy link

@nzoschke Should we come up with a PR to fix this behavior?

I worked around it for now with writing my own sanitizing function. In my case, it messes up with the JS syntax comma and I have to "simply" filter that out.

@natemoo-re What do you think? How can we help?

@nzoschke
Copy link

nzoschke commented May 22, 2023

Definitely would be nice to fix. I did spend some time looking at this library and the test suite but it didn't click for me yet.

The workaround I have for a "list" right now is using a pre tag:

html`
<pre class="flex h-[21rem] flex-col">
  ${collection.playlists.map((p, i) => `${i.toString().padStart(2, "0")} ${p.title.replace("&", "and")}`).join("\n")}
</pre>
`

One more catch here is that & is escaped to &amp;, hence the and replacement.

@nzoschke
Copy link

nzoschke commented May 22, 2023

I took one more look at it... Here's a test that demonstrates the problem:

  it("works as a nested tagged template", async () => {
    const result = html`<div>Hello ${html`<b>world</b>`}</div>`;
    expect(result).toEqual(
      wrap({
        type: "div",
        props: {
          children: "Hello [object Object]",
        },
      })
    );
  });

Right now it looks like it assumes a single DocumentNode as a single root node, but the inner html is a second DocumentNode.

nodeMap.set(node, root);

But it's still not obvious to me how to handle this. Tree parsing is tricky!

@nzoschke
Copy link

I had a personal breakthrough.

I'm also using https://astro.build and there is no need for this shim given that it supports React and JSX / TSX out of the box.

I wasn't using React for anything else in my astro project, but so far it seems like its totally fine to add just for Satori.

https://docs.astro.build/en/guides/integrations-guide/react/

Sorry I couldn't help fix the library, but maybe this could help @neg4n with his issue.

And @natemoo-re, would you be interested if I contributed some docs about this for Astro somewhere?

@KyleTryon
Copy link

I ran into the same issue today. @nzoschke I'd love to see what you ended up doing, I would use JSX/TSX but astro doesn't support .tsx endpoints.

@nzoschke
Copy link

Astro supports .tsx components and ts endpoints.

So first add https://docs.astro.build/en/guides/integrations-guide/react/ then a TSX file in components/OG.tsx. I'm using the experimental tailwind tw attribute in satori.

declare module "react" {
  interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
    tw?: string;
  }
}

export default function OG() {
  return (
    <div tw="flex h-[24rem] w-[48rem]">
      Hello World
    </div>
  );
}

Then an SGV route in src/pages/sgv.ts

import OG from "@components/OG";
import satori from "satori";
import { Roboto } from "../fonts";

export default async function SVG() {
  return await satori(OG(), {
    width: 48 * 16,
    height: 24 * 16,
    fonts: Roboto,
  });
}

export const get: APIRoute = async function get({ request }) {
  return new Response(await SVG(), {
    status: 200,
    headers: {
      "Content-Type": "image/svg+xml",
    },
  });
};

And a PNG route at src/pages/png.ts

import { Resvg } from "@resvg/resvg-js";
import type { APIRoute } from "astro";
import SVG from "./svg";

export const get: APIRoute = async function get({ request }) {
  const resvg = new Resvg(await SVG(), {
    background: "rgba(255, 255, 255, 1)",
    fitTo: {
      mode: "width",
      value: 48 * 16 * 2,
    },
  });

  return new Response(resvg.render().asPng(), {
    status: 200,
    headers: {
      "Content-Type": "image/png",
    },
  });
};

@KyleTryon
Copy link

Thanks @nzoschke this was a huge help! In another project where I was using React already I gave this a shot and or worked fantastically. The PR for anyone who might be interested. CircleCI-Archived/Config.Tips#45

@lizyChy0329
Copy link

Only support string literals?

console.log(html`${domGenRef.value?.wrapper}`); // [object HTMLDivElement]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants