Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
feat(render): dynamic head content (#213)
Browse files Browse the repository at this point in the history
* update the JSX render class to not explicitly inject HTML inside of the body tag, allowing for head meta tags and such to be injected and handled by the browser.

* fix last pull request for HTML render changes as it didn't do as I had expected initially.

* add new render test to check that empty style tag is not injected, slightly change render body assignment to not inject empty style tag if twind isn't utilised.

* update render tests to include 3 steps (1 with Twind styling, 1 without Twind styling to test for empty style tag injection, and 1 to test for meta tag injection in head.)

* move Cheetah app creation to the outside of the test to clean things up a bit.

* remove unnecessary console.log in render function, use </head> instead of slicing the HEAD_TAG constant. (changes requested by not-ivy)

* optimize the 'no styles' case in the render function (check for empty css from Twind straight away, and return the html as is without any changes if that is the case)

* feat(render): add new render test case for JSX without the html, head or body tags to ensure that the browser and document is still generated appropriately, remove the head manipulation in the render function as the browser will usually know where to put specific tags upon request.
  • Loading branch information
jaymanmdev authored Sep 4, 2023
1 parent 0184688 commit d70410f
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 27 deletions.
6 changes: 1 addition & 5 deletions render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import { Context } from './mod.ts'

export function render(c: Context, Component: VNode) {
const htmlString = renderToString(Component)

try {
const { html, css } = extract(htmlString)

c.res.body = `<style>${css}</style><body>${html}</body>`
c.res.body = `${css.length > 0 ? `<style>${css}</style>` : ''}${html}`
} catch (_err) {
if (c.dev) {
console.warn(
Expand All @@ -28,10 +26,8 @@ export function render(c: Context, Component: VNode) {
),
)
}

c.res.body = htmlString
}

c.res.header('content-type', 'text/html; charset=utf-8')
}

Expand Down
174 changes: 152 additions & 22 deletions test/render.test.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,170 @@
// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license.
/** @jsx h */
import { Fragment, VNode } from 'https://esm.sh/preact@10.17.1'
import cheetah, { h, Renderer } from '../mod.ts'
import { assert, assertEquals, DOMParser } from './deps.ts'
import { assert, assertEquals, DOMParser, z } from './deps.ts'

Deno.test('render', async () => {
const app = new cheetah()

const { render } = new Renderer()
const Document = ({ children }: { children: VNode }) => {
return (
<html>
<head>
<title>This is a document!</title>
</head>
<body>
{children}
</body>
</html>
)
}

function Styled() {
return (
const Styled = () => {
return (
<Document>
<h3 class='text-sm italic' id='styled'>
styled <code class='font-mono'>h3</code> component
</h3>
)
}
</Document>
)
}

app.get('/a', (c) => render(c, <Styled />))
const Unstyled = () => {
return (
<Document>
<h3 id='unstyled'>
unstyled <code>h3</code> component
</h3>
</Document>
)
}

const MetaTagsWithoutWrappers = () => {
return (
<>
<title>This is a document!</title>
<meta charSet='utf-8' />
<h1>Hello world!</h1>
</>
)
}

const a = await app.fetch(new Request('http://localhost/a'))
const app = new cheetah()

const tx = await a.text()
const { render } = new Renderer()

app.get('/render', {
query: z.object({
type: z.union([
z.literal('styled'),
z.literal('unstyled'),
z.literal('meta-tags-without-wrappers'),
]),
}),
}, (ctx) => {
const type = ctx.req.query.type
if (type === 'meta-tags-without-wrappers') {
return render(ctx, <MetaTagsWithoutWrappers />)
}
render(
ctx,
type === 'styled' ? <Styled /> : <Unstyled />,
)
})

Deno.test('render', async (test) => {
await test.step('Twind styles are applied to the resulting HTML correctly.', async () => {
const renderResponse = await app.fetch(
new Request('http://localhost/render?type=styled'),
)
const htmlText = await renderResponse.text()
const document = new DOMParser().parseFromString(
htmlText,
'text/html',
)
assert(document)
assert([...document.getElementsByTagName('style')].length === 1)
assertEquals(
document.getElementById('styled')?.innerText,
'styled h3 component',
)
assertEquals(
renderResponse.headers.get('content-type'),
'text/html; charset=utf-8',
)
})

const renderResponse = await app.fetch(
new Request('http://localhost/render?type=unstyled'),
)
const htmlText = await renderResponse.text()
const document = new DOMParser().parseFromString(
tx,
htmlText,
'text/html',
)

assert(document)
assert([...document.getElementsByTagName('style')].length)
assertEquals(
document.getElementById('styled')?.innerText,
'styled h3 component',
)
assertEquals(
a.headers.get('content-type'),
'text/html; charset=utf-8',
)
await test.step('No empty style tag is injected if no Twind styles are utilised.', () => {
assertEquals(
[...document.getElementsByTagName('style')].length,
0,
)
assertEquals(
document.getElementById('unstyled')?.innerText,
'unstyled h3 component',
)
assertEquals(
renderResponse.headers.get('content-type'),
'text/html; charset=utf-8',
)
})

await test.step('Head meta tags are able to be injected into the HTML output properly.', () => {
const headElementsInDocument = [...document.getElementsByTagName('head')]
assert(headElementsInDocument.length === 1)
const headElementInDocument = headElementsInDocument.at(0)
assert(headElementInDocument)
assert(
[...headElementInDocument.children].find((childNode) =>
childNode.tagName === 'TITLE' &&
childNode.textContent === 'This is a document!'
),
)
})

await test.step('Even without the essential html, head and body tags in the JSX input, the JSX input is injected correctly into the appropriate places.', async () => {
const renderResponse = await app.fetch(
new Request('http://localhost/render?type=meta-tags-without-wrappers'),
)
const htmlText = await renderResponse.text()
const document = new DOMParser().parseFromString(
htmlText,
'text/html',
)
assert(document)
const { documentElement } = document
assert(documentElement)
const headElementsInDocument = documentElement.getElementsByTagName('head')
const [headElement] = headElementsInDocument
assert(headElement)
const titleElementInDocument = headElement.getElementsByTagName('title').at(
0,
)
assert(
titleElementInDocument &&
titleElementInDocument.textContent === 'This is a document!',
)
const metaTagsInDocument = documentElement.getElementsByTagName('meta')
assert(
metaTagsInDocument.find((metaTag) =>
metaTag.getAttribute('charset') === 'utf-8'
),
)
const [bodyElement] = documentElement.getElementsByTagName('body')
assert(bodyElement)
const [headingElementInBody] = [...bodyElement.children]
assert(
headingElementInBody !== undefined &&
headingElementInBody.tagName === 'H1' &&
headingElementInBody.textContent === 'Hello world!',
)
})
})

0 comments on commit d70410f

Please sign in to comment.