preactjs / preact-render-to-string

📄 Universal rendering for Preact: render JSX and Preact components to HTML.
http://npm.im/preact-render-to-string
MIT License
653 stars 92 forks source link

`className` is not correctly coerced to strings for Function proxies #390

Closed sliftist closed 3 months ago

sliftist commented 3 months ago

Repro:

let classNameBuilder = new Proxy(() => {}, { get() { return () => "example-class" } })
let element = <div className={classNameBuilder} />;
// <div></div>
console.log(renderToString(element));

However, in the browser:

var element = document.createElement("div");
element.className = new Proxy(() => {}, { get() { return () => "example-class" } })
// <div class="example-class"></div>
console.log(element.outerHTML)

It looks like all attributes which are functions are ignored, when className should in fact be coerced to a string.

I only tested on Chrome, but I believe string coercion is the spec compliant behavior. The spec says that The className attribute must reflect the "class" content attribute (https://dom.spec.whatwg.org/#dom-element-classname), and it appears that in the process of doing this a string coercion is implied.

marvinhagemeister commented 3 months ago

We have a fast bail out during attribute rendering that checks if the value is a function and bails out.

https://github.com/preactjs/preact-render-to-string/blob/19ebe20a9fbbee793e10764225524d1464807cc5/src/index.js#L546

It feels like a very odd way to use className. I'm curious what the situation is where one would not want to pass strings as values.

sliftist commented 3 months ago

I use function proxies to build type-safe css for some personal projects.

It's worked well in the browser, and I only ran into problems when I started server-side rendering, due to the function attributes being ignored.

Thanks for the fast pull request! I made a similar change in a development fork, but I understand if you don't want to add the complexity into master.

By the way, the type-safe css library is here: https://www.npmjs.com/package/typesafecss, and it looks like this:

<div className={css.display("flex").gap(10)}>
    {items.map(item => <div>{item}</div>)}
</div>