-
Notifications
You must be signed in to change notification settings - Fork 4
/
mod.tsx
102 lines (94 loc) · 5.27 KB
/
mod.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/** @jsx h */
import { h, type VNode } from "https://esm.sh/preact@10.7.2";
import {
renderToString,
} from "https://esm.sh/preact-render-to-string@5.2.0?deps=preact@10.7.2";
import {
type Preset,
UnoGenerator,
type UserConfig,
} from "https://esm.sh/@unocss/core@0.37.0";
import presetWind from "https://esm.sh/@unocss/preset-wind@0.37.0?bundle&no-check";
const resetCSS =
`/* reset */*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";scrollbar-gutter:stable}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}.dark{color-scheme:dark}.dark ::-moz-selection{background:#444}.dark ::selection{background:#444}div{display:flex;flex-direction:column}div[row=""]{display:flex;flex-direction:row}div[block=""]{display:block}`;
const publicUno = new UnoGenerator({
presets: [presetWind() as unknown as Preset],
});
const userUnos: Map<UserConfig, UnoGenerator> = new Map();
export interface HtmlOptions {
lang?: string;
title?: string;
meta?: Record<string, string>;
styles?: (string | { href: string; id?: string })[];
scripts?: (string | { src: string; type?: string; id?: string })[];
}
export interface Options extends HtmlOptions {
body: VNode;
unocss?: UserConfig;
status?: number;
headers?: HeadersInit;
}
export async function html(options: Options): Promise<Response> {
const { body, unocss, status, headers: headersInit, ...rest } = options;
const headers = new Headers(headersInit);
headers.append("Content-Type", "text/html; charset=utf-8");
let uno = publicUno;
if (unocss) {
if (!userUnos.has(unocss)) {
userUnos.set(unocss, new UnoGenerator(unocss));
}
uno = userUnos.get(unocss)!;
}
const bodyHtml = renderToString(body);
const { css } = await uno.generate(bodyHtml, { minify: true });
return new Response(
renderToString(
<Html
body={bodyHtml}
unocss={{ css, version: uno.version }}
{...rest}
/>,
),
{ status, headers },
);
}
interface HtmlProps extends HtmlOptions {
body: string;
unocss: { css: string; version: string };
}
function Html({ lang, title, meta, styles, scripts, body, unocss }: HtmlProps) {
return (
<html lang={lang ?? "en"}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{title && <title>{title}</title>}
{meta &&
Object.entries(meta).filter(([name, content]) => !!name && !!content)
.map(([name, content]) => <meta name={name} content={content} />)}
<style dangerouslySetInnerHTML={{ __html: resetCSS }} />
{unocss.css && (
<style
data-unocss={unocss.version}
dangerouslySetInnerHTML={{ __html: unocss.css }}
/>
)}
{styles && styles.map((style) => (
typeof style === "string"
? <style dangerouslySetInnerHTML={{ __html: style }} />
: <link rel="stylesheet" href={style.href} id={style.id} />
))}
{scripts && scripts.map((script) => (
typeof script === "string"
? <script dangerouslySetInnerHTML={{ __html: script }} />
: (
<script src={script.src} type={script.type} id={script.id}>
</script>
)
))}
</head>
<body dangerouslySetInnerHTML={{ __html: body }} />
</html>
);
}
export * from "https://esm.sh/preact@10.7.2";