Last active
November 12, 2025 22:29
-
-
Save hnordt/fd5ec2d89bd73873a9b8d0f4ced6609a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "compilerOptions": { | |
| "lib": ["dom", "deno.ns"], | |
| "jsx": "precompile", | |
| "jsxImportSource": "preact" | |
| }, | |
| "tasks": { | |
| "dev": "deno run --allow-net --watch server.tsx", | |
| }, | |
| "imports": { | |
| "linkedom": "npm:linkedom@^0.18.12", | |
| "preact-render-to-string": "npm:preact-render-to-string@^6.6.3", | |
| "preact": "npm:preact@^10.27.2", | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // We need this to convert our JSX into HTML at runtime. | |
| import { renderToStaticMarkup } from "preact-render-to-string"; | |
| // Needed because HTMLElement isn’t available in Deno yet. | |
| import { HTMLElement } from "linkedom"; | |
| // Enables type-safe usage of custom HTML elements in JSX. | |
| declare global { | |
| namespace preact.JSX { | |
| interface IntrinsicElements { | |
| "el-hello": { url: string }; | |
| } | |
| } | |
| } | |
| // Defined on the server for type-safety, but only sent as a script tag. | |
| // Per spec, Function.prototype.toString() returns source code, we use that here. | |
| class HelloElement extends HTMLElement { | |
| // Tell the DOM to watch these props so they can be changed via JS or devtools. | |
| static observedAttributes = ["url"]; | |
| // Type-safe helper for convenience. | |
| get props() { | |
| return this.getAttributeNames().reduce((acc, name) => ({ | |
| ...acc, | |
| [name]: this.getAttribute(name), | |
| }), {}) as preact.JSX.IntrinsicElements["el-hello"]; | |
| } | |
| // Same as componentDidMount. | |
| connectedCallback() { | |
| this.render(); | |
| } | |
| // Same as componentDidUpdate. | |
| attributeChangedCallback() { | |
| this.render(); | |
| } | |
| render() { | |
| // For simplicity, I’m just rendering a string here, but we could add | |
| // preact-render-to-string to the client and use it instead. | |
| this.innerHTML = `<div>Hello from ${new URL(this.props.url).pathname}!</div>`; | |
| } | |
| } | |
| function App(props: { url: string }) { | |
| return ( | |
| <html> | |
| <head> | |
| <style | |
| dangerouslySetInnerHTML={{ | |
| __html: `body { background-color: #fff; }`, | |
| }} | |
| /> | |
| <script | |
| type="module" | |
| dangerouslySetInnerHTML={{ | |
| __html: `customElements.define("el-hello", ${HelloElement});`, | |
| }} | |
| /> | |
| </head> | |
| <body> | |
| <el-hello url={props.url} /> | |
| </body> | |
| </html> | |
| ); | |
| } | |
| Deno.serve((req) => | |
| new Response(renderToStaticMarkup(<App url={req.url} />), { | |
| headers: { | |
| "Content-Type": "text/html; charset=utf-8", | |
| }, | |
| }) | |
| ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment