Skip to content

Instantly share code, notes, and snippets.

@reggi
Created May 11, 2023 20:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reggi/2a49b47d367a31ceb37280f11579825c to your computer and use it in GitHub Desktop.
Save reggi/2a49b47d367a31ceb37280f11579825c to your computer and use it in GitHub Desktop.
/** @jsx h */
/** @jsxFrag Fragment */
import { VNode, h, Fragment } from "https://esm.sh/preact@10.13.2";
import { renderToString } from "https://esm.sh/preact-render-to-string@6.0.3?deps=preact@10.13.2";
import { serve } from "https://deno.land/std@0.184.0/http/server.ts";
class Next {}
type Route = (req: Request) => Promise<Next | Response>
const routes = (...routes: (Route | Route[])[]) => async (req: Request): Promise<Response> => {
const r = routes.flat()
const value = await r.reduce((thread: Promise<Next | Response>, route) => {
return thread.then(currentValue => {
if (currentValue instanceof Response) return currentValue
return route(req)
})
}, Promise.resolve(new Next()))
if (value instanceof Next) {
return new Response("Not found", { status: 404 })
}
return value;
}
const Template = (p: { id: string, children: VNode<any> }) => {
return (
/** @ts-ignore */
<template id={p.id}>
{p.children}
{/** @ts-ignore */}
</template>
)
}
const Widget = (p: { children: VNode<any> }) => {
return <div>in widget --- { p.children }</div>
}
function escapeHtml(unsafe: string){
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
const HTMLComponent = (p: { id: string, children: VNode<any>, call?: boolean }) => {
return (
<>
<Template id={p.id}>{p.children}</Template>
<script dangerouslySetInnerHTML={{__html: defineComponentScript({ name: p.id })}}/>
{p.call && <p.id/>}
</>
)
}
const defineComponentScript = (p: { name: string }) => `
customElements.define(
"${p.name}",
class extends HTMLElement {
constructor() {
super();
let template = document.getElementById("${p.name}");
let templateContent = template.content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
}
}
);
`
const defineTemplateInScript = (p: { name: string, html: string}) => `
customElements.define(
"${p.name}",
class extends HTMLElement {
constructor() {
super();
let template = document.createElement('template');
template.innerHTML = \`${p.html}\`
let templateContent = template.content;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(templateContent.cloneNode(true));
}
}
);
`
const Main = (props: { user: { name: string, age: number, hometown: string}}) => {
return <div>this is an example component for {props.user.name} with an age of {props.user.age} and lives in {props.user.hometown}</div>
}
const users = {
'alice': {
name: 'alice',
age: 20,
hometown: 'nyc'
},
'thomas': {
name: 'thomas',
age: 30,
hometown: 'nyc'
},
'bob': {
name: 'bob',
age: 30,
hometown: 'sfo'
}
}
const MainHandler = async (req: Request): Promise<Next | Response> => {
console.log(req.url)
const patternString = '/main/:user/:format?'
const pattern = new URLPattern({ pathname: patternString })
const match = pattern.exec(req.url)
if (!match) return new Next()
const user = match.pathname.groups.user
const format = match.pathname.groups.format
if (!user) return new Next()
const foundUser = Object.values(users).find(v => v.name === user)
if (!foundUser) return new Response('user not found', { status: 404 })
const data = await Promise.resolve(foundUser)
const inner = <Main user={data}></Main>
// content-types
const isJSON = format?.includes('.json')
const isPlain = format?.includes('.plain')
// html handling
const isTemplate = format?.includes('.temp')
const isInner = format?.includes('.inner')
const isScriptComponent = format?.includes('.js')
const isHtmlComponent = format?.includes('.htmlwc')
const shouldCall = format?.includes('.call')
const shouldEscape = format?.includes('.escape')
const isIframeSrcDoc = format?.includes('.iframesrcdoc')
const name = `csv-${user}-list${isInner ? '-inner' : ''}`
let html = <Widget>{inner}</Widget>
html = isInner ? inner : html
html = isTemplate && !isHtmlComponent ? <Template id={name}>{html}</Template> : html
html = isHtmlComponent ? <HTMLComponent id={name} call={shouldCall}>{html}</HTMLComponent> : html
let type = "text/html"
type = isPlain ? 'text/plain' : type
type = isScriptComponent ? 'text/javascript' : type
if (isJSON) return Response.json(data)
const htmlString = renderToString(html)
if (isScriptComponent) return new Response(defineTemplateInScript({ name, html: htmlString}), { headers: { "content-type": `${type}; charset=utf-8` }})
if (isIframeSrcDoc) return new Response(`<iframe frameBorder="0" srcdoc="${escapeHtml(htmlString)}"/>`, { headers: { "content-type": `${type}; charset=utf-8` }, status: 200 })
if (shouldEscape) return new Response(escapeHtml(htmlString), { headers: { "content-type": `${type}; charset=utf-8` }})
return new Response(htmlString, { headers: { "content-type": `${type}; charset=utf-8` }})
}
console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await serve(routes(MainHandler));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment