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', + ) + }) }) })