Skip to content

Instantly share code, notes, and snippets.

@usualoma
Last active June 6, 2022 23:05
Show Gist options
  • Save usualoma/219211d989885d4177873e45932c9835 to your computer and use it in GitHub Desktop.
Save usualoma/219211d989885d4177873e45932c9835 to your computer and use it in GitHub Desktop.
/* @jsx jsx */
import { Hono } from '../hono'
import { jsx, render } from '../utils/jsx'
function Layout(props: any) {
return (
<html>
{props.children.map((c: any) => (
<div className='wrap'>{c}</div>
))}
</html>
)
}
describe('JSX', () => {
const app = new Hono()
app.get('/hello/:name', (c) => {
const { name } = c.req.param()
const data = '<>'
return c.html(
render(
<Layout>
<div attr={'a"b'}>
Hello {data} <span data-attr={'c"d'}>{name}!</span>
{['a', 'b'].map((c) => (
<span>{c}</span>
))}
</div>
</Layout>
)
)
})
it('request', async () => {
const res = await app.request('http://localhost/hello/hono')
expect(res.headers.get('Content-Type')).toMatch(/text\/html/)
expect(await res.text()).toBe(
'<html><div className="wrap"><div attr="a&quot;b">Hello &lt;&gt; <span data-attr="c&quot;d">hono!</span><span>a</span><span>b</span></div></div></html>'
)
})
})
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jsx.JSX {
interface IntrinsicElements {
[tagName: string]: Record<string, string>
}
}
}
class EscapedString {
private content: string
constructor(content: string) {
this.content = content
}
public toString() {
return this.content
}
}
function escape(str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
export function jsx(
tag: string | Function,
props: Record<string, any>,
...children: (string | EscapedString)[]
): EscapedString {
if (typeof tag === 'function') {
return tag.call(null, { ...props, children })
}
let attrs = ''
const propsKeys = Object.keys(props || {})
for (let i = 0, len = propsKeys.length; i < len; i++) {
attrs += ` ${propsKeys[i]}="${escape(props[propsKeys[i]])}"`
}
return new EscapedString(
`<${tag}${attrs}>${children
.flat()
.map((c) => (c instanceof EscapedString ? c.toString() : escape(c)))
.join('')}</${tag}>`
)
}
export function render(content: EscapedString): string {
return content.toString()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment