diff --git a/.vscode/settings.json b/.vscode/settings.json
index aa1c94e..9ab4dcb 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,12 @@
{
"deno.enable": true,
- "deno.unstable": true
+ "deno.unstable": true,
+ "editor.defaultFormatter": "denoland.vscode-deno",
+ "[typescript]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "denoland.vscode-deno"
+ },
+ "editor.formatOnSave": true
}
diff --git a/render.tsx b/render.tsx
new file mode 100644
index 0000000..741a6c7
--- /dev/null
+++ b/render.tsx
@@ -0,0 +1,23 @@
+// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license.
+/** @jsx h */
+import { default as renderToString } from 'https://esm.sh/preact-render-to-string@6.1.0?deps=preact@10.16.0'
+import { h, VNode } from 'https://esm.sh/preact@10.17.0'
+import { extract } from 'https://esm.sh/@twind/core@1.1.3'
+
+import { Context } from './mod.ts'
+
+export function render(c: Context, Component: (() => h.JSX.Element) | VNode) {
+ const html$ = renderToString(
+ Component instanceof Function ? : Component,
+ )
+ try {
+ const { html, css } = extract(html$) // twind throws error when trying to extract if it is not installed
+ c.res.body = `
${html}`
+ } catch (e) {
+ console.warn('twind is not installed, styles might not be applied')
+ c.res.body = html$
+ }
+ c.res.header('content-type', 'text/html; charset=utf-8')
+}
+
+export { h } from 'https://esm.sh/preact@10.17.0'
diff --git a/test/deps.ts b/test/deps.ts
index 794e987..f642d71 100644
--- a/test/deps.ts
+++ b/test/deps.ts
@@ -1,3 +1,12 @@
-export { assertEquals } from 'https://deno.land/std@0.198.0/assert/assert_equals.ts'
-export { assertInstanceOf } from 'https://deno.land/std@0.198.0/assert/assert_instance_of.ts'
+export {
+ assert,
+ assertEquals,
+ assertInstanceOf,
+} from 'https://deno.land/std@0.198.0/assert/mod.ts'
export { z } from 'https://deno.land/x/zod@v3.21.4/mod.ts'
+export { defineConfig } from 'https://esm.sh/@twind/core@1.1.3'
+import presetAutoPrefix from 'https://esm.sh/@twind/preset-autoprefix@1.0.7'
+import presetTailwind from 'https://esm.sh/@twind/preset-tailwind@1.1.4'
+export { presetAutoPrefix, presetTailwind }
+export { DOMParser } from 'https://deno.land/x/deno_dom@v0.1.38/deno-dom-wasm.ts'
+export { install } from 'https://esm.sh/@twind/core@1.1.3'
diff --git a/test/render.test.tsx b/test/render.test.tsx
new file mode 100644
index 0000000..219e3c2
--- /dev/null
+++ b/test/render.test.tsx
@@ -0,0 +1,49 @@
+// Copyright 2023 Samuel Kopp. All rights reserved. Apache-2.0 license.
+/** @jsx h */
+
+import { h, render } from '../render.tsx'
+import cheetah from '../mod.ts'
+import {
+ assert,
+ assertEquals,
+ defineConfig,
+ DOMParser,
+ install,
+ presetAutoPrefix,
+ presetTailwind,
+} from './deps.ts'
+
+Deno.test('render', async () => {
+ const app = new cheetah()
+ install(defineConfig({
+ presets: [presetAutoPrefix(), presetTailwind()],
+ }))
+
+ function Styled() {
+ return (
+
+ styled h3
component
+
+ )
+ }
+
+ app.get('/a', (c) => render(c, Styled))
+
+ const a = await app.fetch(new Request('http://localhost/a'))
+
+ const document = new DOMParser().parseFromString(
+ await a.text(),
+ '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',
+ )
+})
diff --git a/x/jsx.tsx b/x/jsx.tsx
index d003f5d..a15da00 100644
--- a/x/jsx.tsx
+++ b/x/jsx.tsx
@@ -2,6 +2,7 @@
/** @jsx h */
import { default as renderToString } from 'https://esm.sh/preact-render-to-string@6.1.0?deps=preact@10.16.0'
import { h, VNode } from 'https://esm.sh/preact@10.17.0'
+
import { Context } from '../mod.ts'
export function jsx(c: Context, Component: (() => h.JSX.Element) | VNode) {
diff --git a/x/x.test.tsx b/x/x.test.tsx
index 7fed67c..3784a44 100644
--- a/x/x.test.tsx
+++ b/x/x.test.tsx
@@ -20,34 +20,36 @@ Deno.test('x', async (t) => {
assertEquals(await verify(token, cryptoKey) !== undefined, true)
})
- await t.step('jsx', async () => {
- const app = new cheetah()
-
- function Custom() {
- return hello world
- }
-
- app.get('/a', (c) => jsx(c, Custom))
- app.get('/b', (c) => jsx(c, ))
-
- const a = await app.fetch(new Request('http://localhost/a'))
- const b = await app.fetch(new Request('http://localhost/b'))
-
- assertEquals(
- await a.text(),
- 'hello world
',
- )
- assertEquals(
- a.headers.get('content-type'),
- 'text/html; charset=utf-8',
- )
- assertEquals(
- await b.text(),
- 'hello world
',
- )
- assertEquals(
- b.headers.get('content-type'),
- 'text/html; charset=utf-8',
- )
+ await t.step('jsx', async (t) => {
+ await t.step('plain', async () => {
+ const app = new cheetah()
+
+ function Custom() {
+ return hello world
+ }
+
+ app.get('/a', (c) => jsx(c, Custom))
+ app.get('/b', (c) => jsx(c, ))
+
+ const a = await app.fetch(new Request('http://localhost/a'))
+ const b = await app.fetch(new Request('http://localhost/b'))
+
+ assertEquals(
+ await a.text(),
+ 'hello world
',
+ )
+ assertEquals(
+ a.headers.get('content-type'),
+ 'text/html; charset=utf-8',
+ )
+ assertEquals(
+ await b.text(),
+ 'hello world
',
+ )
+ assertEquals(
+ b.headers.get('content-type'),
+ 'text/html; charset=utf-8',
+ )
+ })
})
})