├── examples ├── yew-app │ ├── src │ │ ├── components.rs │ │ ├── routes │ │ │ ├── _404.rs │ │ │ ├── index.rs │ │ │ └── todos.rs │ │ ├── lib.rs │ │ ├── routes.rs │ │ └── app.rs │ ├── .gitignore │ ├── assets │ │ ├── yew.png │ │ ├── external-link.svg │ │ └── logo.svg │ ├── pkg │ │ └── yew_app_bg.wasm │ ├── main.ts │ ├── server.ts │ ├── index.html │ ├── Cargo.toml │ ├── README.md │ └── dev.ts ├── with-unocss │ ├── yew-app │ │ ├── src │ │ │ ├── components.rs │ │ │ ├── lib.rs │ │ │ ├── routes.rs │ │ │ ├── routes │ │ │ │ ├── _404.rs │ │ │ │ └── index.rs │ │ │ └── app.rs │ │ ├── .gitignore │ │ ├── assets │ │ │ ├── yew.png │ │ │ ├── external-link.svg │ │ │ └── logo.svg │ │ ├── pkg │ │ │ └── yew_app_bg.wasm │ │ ├── main.ts │ │ ├── unocss.config.ts │ │ ├── index.html │ │ ├── server.ts │ │ ├── README.md │ │ ├── Cargo.toml │ │ └── dev.ts │ ├── react-app │ │ ├── main.ts │ │ ├── README.md │ │ ├── unocss.config.ts │ │ ├── routes │ │ │ ├── _app.tsx │ │ │ ├── _export.ts │ │ │ ├── _404.tsx │ │ │ └── index.tsx │ │ ├── index.html │ │ ├── server.ts │ │ └── assets │ │ │ └── logo.svg │ └── leptos-app │ │ ├── pkg │ │ ├── client_bg.wasm │ │ └── server_bg.wasm │ │ ├── public │ │ └── favicon.ico │ │ ├── .gitignore │ │ ├── Makefile.toml │ │ ├── main.ts │ │ ├── unocss.config.ts │ │ ├── index.html │ │ ├── server.ts │ │ ├── src │ │ ├── routes.rs │ │ ├── routes │ │ │ └── index.rs │ │ └── lib.rs │ │ ├── LICENSE │ │ ├── dev.ts │ │ ├── README.md │ │ └── Cargo.toml ├── monaco-editor │ ├── server.ts │ ├── README.md │ ├── main.ts │ ├── index.html │ └── editor.ts ├── react-mdx-app │ ├── main.ts │ ├── README.md │ ├── routes │ │ ├── _app.tsx │ │ ├── _404.tsx │ │ ├── docs │ │ │ ├── get-started.mdx │ │ │ └── index.mdx │ │ ├── docs.tsx │ │ └── index.tsx │ ├── index.html │ ├── assets │ │ ├── external-link.svg │ │ └── logo.svg │ ├── components │ │ └── Heading.tsx │ ├── server.ts │ └── style │ │ ├── hljs.css │ │ ├── markdown.css │ │ └── app.css ├── react-suspense-ssr │ ├── main.ts │ ├── components │ │ ├── Spinner.tsx │ │ ├── Comments.tsx │ │ ├── Sidebar.tsx │ │ └── Post.tsx │ ├── routes │ │ ├── _export.ts │ │ └── index.tsx │ ├── server.ts │ ├── index.html │ ├── README.md │ └── style │ │ └── main.css ├── github-oauth-middleware │ ├── main.ts │ ├── README.md │ ├── routes │ │ ├── _export.ts │ │ └── index.tsx │ ├── index.html │ ├── server.ts │ └── middlewares │ │ └── oauth.ts ├── react-app │ ├── main.ts │ ├── README.md │ ├── routes │ │ ├── _app.tsx │ │ ├── _404.tsx │ │ ├── _export.ts │ │ ├── index.tsx │ │ └── todos.tsx │ ├── server.ts │ ├── index.html │ ├── assets │ │ ├── external-link.svg │ │ └── logo.svg │ └── components │ │ └── Header.tsx ├── leptos-app │ ├── pkg │ │ ├── client_bg.wasm │ │ └── server_bg.wasm │ ├── public │ │ └── favicon.ico │ ├── .gitignore │ ├── Makefile.toml │ ├── main.ts │ ├── server.ts │ ├── index.html │ ├── src │ │ ├── routes.rs │ │ ├── routes │ │ │ └── index.rs │ │ └── lib.rs │ ├── LICENSE │ ├── dev.ts │ ├── README.md │ └── Cargo.toml └── api-app │ ├── README.md │ ├── routes │ ├── ws.ts │ ├── index.ts │ ├── _export.ts │ └── users │ │ ├── index.ts │ │ └── $uid.ts │ └── server.ts ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── dev.ts ├── version.ts ├── framework ├── core │ ├── events.ts │ ├── nomodule.ts │ ├── redirect.ts │ ├── url_pattern.ts │ └── style.ts └── react │ ├── mod.ts │ ├── refresh.ts │ ├── client.ts │ ├── error.ts │ ├── plugin.ts │ ├── context.ts │ ├── portal.ts │ ├── head.ts │ └── router.ts ├── tests ├── server_media_type_test.ts ├── server_html_test.ts ├── shared_util_test.ts ├── server_helpers_test.ts ├── integration_api_app_test.ts └── integration_react_app_test.tsx ├── import_map.json ├── types.d.ts ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── deno.json ├── server ├── deps.ts ├── log.ts ├── mod.ts ├── media_type.ts ├── session.ts ├── context.ts ├── mock.ts ├── graph.ts └── cache.ts ├── plugins ├── unocss.ts └── mdx.ts ├── CONTRIBUTING.md ├── README.md └── shared └── util.ts /examples/yew-app/src/components.rs: -------------------------------------------------------------------------------- 1 | pub mod header; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | deno.lock 3 | examples/*/output 4 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/components.rs: -------------------------------------------------------------------------------- 1 | pub mod header; -------------------------------------------------------------------------------- /examples/yew-app/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/* 3 | !pkg/yew_app.js 4 | !pkg/yew_app_bg.wasm -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "denoland.vscode-deno" 4 | ] 5 | } -------------------------------------------------------------------------------- /examples/monaco-editor/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | 3 | serve({}); 4 | -------------------------------------------------------------------------------- /examples/react-mdx-app/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from "aleph/react"; 2 | 3 | bootstrap(); 4 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from "aleph/react"; 2 | 3 | bootstrap(); 4 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/* 3 | !pkg/yew_app.js 4 | !pkg/yew_app_bg.wasm -------------------------------------------------------------------------------- /dev.ts: -------------------------------------------------------------------------------- 1 | import dev from "./server/dev.ts"; 2 | 3 | if (import.meta.main) { 4 | dev(Deno.args); 5 | } 6 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from "aleph/react"; 2 | 3 | bootstrap(); 4 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from "aleph/react"; 2 | 3 | bootstrap(); 4 | -------------------------------------------------------------------------------- /examples/react-app/main.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { bootstrap } from "aleph/react"; 4 | 5 | bootstrap(); 6 | -------------------------------------------------------------------------------- /examples/yew-app/assets/yew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/yew-app/assets/yew.png -------------------------------------------------------------------------------- /examples/yew-app/pkg/yew_app_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/yew-app/pkg/yew_app_bg.wasm -------------------------------------------------------------------------------- /examples/leptos-app/pkg/client_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/leptos-app/pkg/client_bg.wasm -------------------------------------------------------------------------------- /examples/leptos-app/pkg/server_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/leptos-app/pkg/server_bg.wasm -------------------------------------------------------------------------------- /examples/leptos-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/leptos-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/assets/yew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/with-unocss/yew-app/assets/yew.png -------------------------------------------------------------------------------- /examples/leptos-app/.gitignore: -------------------------------------------------------------------------------- 1 | .leptos.kdl 2 | target/ 3 | pkg/* 4 | !pkg/client.js 5 | !pkg/client_bg.wasm 6 | !pkg/server.js 7 | !pkg/server_bg.wasm 8 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/pkg/client_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/with-unocss/leptos-app/pkg/client_bg.wasm -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/pkg/server_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/with-unocss/leptos-app/pkg/server_bg.wasm -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/with-unocss/leptos-app/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/pkg/yew_app_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alpha-Coder23/codeking/HEAD/examples/with-unocss/yew-app/pkg/yew_app_bg.wasm -------------------------------------------------------------------------------- /examples/leptos-app/Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "cargo" 3 | args = ["+nightly", "build-all-features"] 4 | install_crate = "cargo-all-features" 5 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/.gitignore: -------------------------------------------------------------------------------- 1 | .leptos.kdl 2 | target/ 3 | pkg/* 4 | !pkg/client.js 5 | !pkg/client_bg.wasm 6 | !pkg/server.js 7 | !pkg/server_bg.wasm 8 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "cargo" 3 | args = ["+nightly", "build-all-features"] 4 | install_crate = "cargo-all-features" 5 | -------------------------------------------------------------------------------- /examples/api-app/README.md: -------------------------------------------------------------------------------- 1 | # REST API App 2 | 3 | A REST API application powered by Aleph.js in Deno, deploy to [Deno Deploy](https://deno.com/deploy): 4 | 5 | https://aleph-api.deno.dev/ 6 | -------------------------------------------------------------------------------- /examples/yew-app/main.ts: -------------------------------------------------------------------------------- 1 | import init, { main } from "./pkg/yew_app.js"; 2 | 3 | // reload page on rebuild 4 | import.meta.hot?.decline(); 5 | 6 | // run app main 7 | init().then(main); 8 | -------------------------------------------------------------------------------- /examples/leptos-app/main.ts: -------------------------------------------------------------------------------- 1 | import init, { hydrate } from "./pkg/client.js"; 2 | 3 | // reload page on rebuild 4 | import.meta.hot?.decline(); 5 | 6 | // run app main 7 | init().then(hydrate); 8 | -------------------------------------------------------------------------------- /examples/react-app/README.md: -------------------------------------------------------------------------------- 1 | # React App 2 | 3 | A React demo application powered by Aleph.js in Deno, deploy to [Deno Deploy](https://deno.com/deploy): 4 | 5 | https://aleph-hello.deno.dev/ 6 | -------------------------------------------------------------------------------- /examples/react-mdx-app/README.md: -------------------------------------------------------------------------------- 1 | # React App 2 | 3 | A React demo application powered by Aleph.js in Deno, deploy to [Deno Deploy](https://deno.com/deploy): 4 | 5 | https://aleph-hello.deno.dev/ 6 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/main.ts: -------------------------------------------------------------------------------- 1 | import init, { main } from "./pkg/yew_app.js"; 2 | 3 | // reload page on rebuild 4 | import.meta.hot?.decline(); 5 | 6 | // run app main 7 | init().then(main); 8 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/README.md: -------------------------------------------------------------------------------- 1 | # React App 2 | 3 | A React demo application powered by Aleph.js in Deno, deploy to [Deno Deploy](https://deno.com/deploy): 4 | 5 | https://aleph-hello.deno.dev/ 6 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/main.ts: -------------------------------------------------------------------------------- 1 | import init, { hydrate } from "./pkg/client.js"; 2 | 3 | // reload page on rebuild 4 | import.meta.hot?.decline(); 5 | 6 | // run app main 7 | init().then(hydrate); 8 | -------------------------------------------------------------------------------- /version.ts: -------------------------------------------------------------------------------- 1 | /** `VERSION` managed by https://deno.land/x/publish */ 2 | export const VERSION = "1.0.0-beta.44"; 3 | 4 | /** The flag indicates that the version is canary version. */ 5 | export const isCanary = false; 6 | -------------------------------------------------------------------------------- /framework/core/events.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import mitt from "https://esm.sh/v126/mitt@3.0.0"; 4 | 5 | // shared event emitter for client(browser) 6 | export default mitt>>(); 7 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/README.md: -------------------------------------------------------------------------------- 1 | # Github OAuth 2 | 3 | A demo application using Github OAuth(middleware) by Aleph.js, deploy to [Deno Deploy](https://deno.com/deploy): 4 | 5 | https://aleph-github-oauth.deno.dev/ 6 | -------------------------------------------------------------------------------- /examples/monaco-editor/README.md: -------------------------------------------------------------------------------- 1 | # Monaco Editor 2 | 3 | A web IDE written in Aleph.js, powered by [Monaco Editor](https://microsoft.github.io/monaco-editor/), deploy to 4 | [Deno Deploy](https://deno.com/deploy): 5 | 6 | https://aleph-monaco-editor.deno.dev/ 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.config": "./deno.json", 5 | "[typescript][typescriptreact][json][markdown]": { 6 | "editor.defaultFormatter": "denoland.vscode-deno", 7 | "editor.formatOnSave": true 8 | } 9 | } -------------------------------------------------------------------------------- /examples/react-app/routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import Header from "../components/Header.tsx"; 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 |
7 | {children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import Header from "../components/Header.tsx"; 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 |
7 | {children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/unocss.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from "@unocss/core"; 2 | import presetUno from "@unocss/preset-uno"; 3 | 4 | // @ref https://github.com/unocss/unocss#configurations 5 | export default { 6 | presets: [presetUno()], 7 | }; 8 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/unocss.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from "@unocss/core"; 2 | import presetUno from "@unocss/preset-uno"; 3 | 4 | // @ref https://github.com/unocss/unocss#configurations 5 | export default { 6 | presets: [presetUno()], 7 | }; 8 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/unocss.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from "@unocss/core"; 2 | import presetUno from "@unocss/preset-uno"; 3 | 4 | // @ref https://github.com/unocss/unocss#configurations 5 | export default { 6 | presets: [presetUno()], 7 | }; 8 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/routes/_app.tsx: -------------------------------------------------------------------------------- 1 | import Header from "../components/Header.tsx"; 2 | 3 | export default function App({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 |
7 | {children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/yew-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import init, { ssr } from "./pkg/yew_app.js"; 3 | 4 | const wasmUrl = new URL("./pkg/yew_app_bg.wasm", import.meta.url); 5 | await init(await Deno.readFile(wasmUrl)); 6 | 7 | serve({ 8 | ssr: ({ url }) => ssr(url.href), 9 | }); 10 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | export default function Spinner({ active = true }) { 2 | return ( 3 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/routes/_export.ts: -------------------------------------------------------------------------------- 1 | // Exports router modules for serverless env that doesn't support the dynamic import. 2 | // This module will be updated automatically in development mode, do NOT edit it manually. 3 | 4 | import * as $0 from "./index.tsx"; 5 | 6 | export default { 7 | "/": $0, 8 | }; 9 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/routes/_export.ts: -------------------------------------------------------------------------------- 1 | // Exports router modules for serverless env that doesn't support the dynamic import. 2 | // This module will be updated automatically in development mode, do NOT edit it manually. 3 | 4 | import * as $0 from "./index.tsx"; 5 | 6 | export default { 7 | "/": $0, 8 | }; 9 | -------------------------------------------------------------------------------- /examples/leptos-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import init, { ssr } from "./pkg/server.js"; 3 | 4 | const wasmUrl = new URL("./pkg/server_bg.wasm", import.meta.url); 5 | await init(await Deno.readFile(wasmUrl)); 6 | 7 | serve({ 8 | ssr: ({ url }) => { 9 | return ssr(url.href); 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import denoDeploy from "aleph/plugins/deploy"; 3 | import react from "aleph/plugins/react"; 4 | import modules from "./routes/_export.ts"; 5 | 6 | serve({ 7 | plugins: [ 8 | denoDeploy({ modules }), 9 | react({ ssr: true }), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /framework/react/mod.ts: -------------------------------------------------------------------------------- 1 | export { bootstrap } from "./client.ts"; 2 | export { useData } from "./data.ts"; 3 | export { Head } from "./head.ts"; 4 | export { Link, type LinkProps, NavLink, type NavLinkProps } from "./link.ts"; 5 | export { usePortal } from "./portal.ts"; 6 | export { App, Router, type RouterProps, useRouter } from "./router.ts"; 7 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/monaco-editor/main.ts: -------------------------------------------------------------------------------- 1 | import { createEditor, createModel } from "./editor.ts"; 2 | 3 | const el = document.querySelector(".editor"); 4 | const editor = createEditor(el as HTMLElement); 5 | const model = createModel("mod.ts", `// Monaco Editor x Aleph.js (SPA mode) \n\nconsole.log("Hello, world!");\n`); 6 | 7 | el!.innerHTML = ""; 8 | editor.setModel(model); 9 | -------------------------------------------------------------------------------- /examples/leptos-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Leptos - Aleph.js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/react-app/routes/_404.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "aleph/react"; 2 | 3 | export default function E404() { 4 | return ( 5 |
6 |

7 | Ooooooops, nothing here! 8 |

9 |

10 | 11 | Go back to the homepage 12 | 13 |

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /examples/react-app/server.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { serve } from "aleph/server"; 4 | import react from "aleph/plugins/react"; 5 | import denoDeploy from "aleph/plugins/deploy"; 6 | import modules from "./routes/_export.ts"; 7 | 8 | serve({ 9 | plugins: [ 10 | denoDeploy({ moduleMain: import.meta.url, modules }), 11 | react({ ssr: true }), 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Yew - Aleph.js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/_404.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "aleph/react"; 2 | 3 | export default function E404() { 4 | return ( 5 |
6 |

7 | Ooooooops, nothing here! 8 |

9 |

10 | 11 | Go back to the homepage 12 | 13 |

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Leptos - Aleph.js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/api-app/routes/ws.ts: -------------------------------------------------------------------------------- 1 | export const GET = (req: Request) => { 2 | const { socket, response } = Deno.upgradeWebSocket(req); 3 | socket.onopen = () => { 4 | socket.send("hello"); 5 | console.log("socket opened"); 6 | }; 7 | socket.onmessage = (event) => { 8 | console.log("socket message", event); 9 | socket.send(event.data); 10 | }; 11 | return response; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/api-app/routes/index.ts: -------------------------------------------------------------------------------- 1 | // GET "/" 2 | export function GET(req: Request, ctx: Context) { 3 | const url = new URL(req.url); 4 | console.log("[middleware:foo]", ctx.foo); 5 | return Response.json({ 6 | "users_url": `${url.origin}/users`, 7 | "user_url": `${url.origin}/users/{user}`, 8 | "websocket_url": `${url.protocol === "https:" ? "wss" : "ws"}://${url.host}/ws`, 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/components/Comments.tsx: -------------------------------------------------------------------------------- 1 | import { useData } from "aleph/react"; 2 | 3 | export default function Comments() { 4 | const { data: { comments } } = useData<{ comments: string[] }>(); 5 | 6 | return ( 7 | <> 8 | {comments.map((comment, i) => ( 9 |

10 | {comment} 11 |

12 | ))} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/README.md: -------------------------------------------------------------------------------- 1 | # React Suspense SSR 2 | 3 | A demo for [New Suspense SSR Architecture in React 18](https://github.com/reactwg/react-18/discussions/37) with Aleph.js 4 | data fetching, deploy to [Deno Deploy](https://deno.com/deploy): 5 | 6 | https://aleph-suspense-ssr.deno.dev/ 7 | 8 | > This demo is artificially slowed down. Open routes/index.tsx to adjust how much different things are slowed down. 9 | -------------------------------------------------------------------------------- /examples/yew-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Yew - Aleph.js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/react-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/react-mdx-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import denoDeploy from "aleph/plugins/deploy"; 3 | import react from "aleph/plugins/react"; 4 | import unocss from "aleph/plugins/unocss"; 5 | import config from "./unocss.config.ts"; 6 | import modules from "./routes/_export.ts"; 7 | 8 | serve({ 9 | plugins: [ 10 | denoDeploy({ modules }), 11 | react({ ssr: true }), 12 | unocss(config), 13 | ], 14 | }); 15 | -------------------------------------------------------------------------------- /examples/yew-app/src/routes/_404.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::routes::Route; 5 | 6 | #[function_component] 7 | pub fn NotFound() -> Html { 8 | html! { 9 |
10 |

11 | {"Ooooooops, nothing here!"} 12 |

13 |

14 | to={Route::Home}>{"Go back to the homepage"}> 15 |

16 |
17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import unocss from "aleph/plugins/unocss"; 3 | import config from "./unocss.config.ts"; 4 | import init, { ssr } from "./pkg/server.js"; 5 | 6 | const wasmUrl = new URL("./pkg/server_bg.wasm", import.meta.url); 7 | await init(await Deno.readFile(wasmUrl)); 8 | 9 | serve({ 10 | plugins: [ 11 | unocss(/\.rs$/, config), 12 | ], 13 | ssr: ({ url }) => ssr(url.href), 14 | }); 15 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import unocss from "aleph/plugins/unocss"; 3 | import config from "./unocss.config.ts"; 4 | import init, { ssr } from "./pkg/yew_app.js"; 5 | 6 | const wasmUrl = new URL("./pkg/yew_app_bg.wasm", import.meta.url); 7 | await init(await Deno.readFile(wasmUrl)); 8 | 9 | serve({ 10 | plugins: [ 11 | unocss(/\.rs$/, config), 12 | ], 13 | ssr: ({ url }) => ssr(url.href), 14 | }); 15 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/docs/get-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get Started - Docs" 3 | author: ije 4 | --- 5 | 6 | import { Head } from "aleph/react" 7 | 8 | 9 | Get Started - Docs 10 | 11 | 12 | # Get Started 13 | 14 | Initialize a new project, you can pick a start template with `--template` flag, available templates: 15 | `[react, react-mdx, api]` 16 | 17 | ```bash 18 | deno run -A https://deno.land/x/aleph@1.0.0-beta.18/init.ts 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/react-app/routes/_export.ts: -------------------------------------------------------------------------------- 1 | // Exports router modules for serverless env that doesn't support the dynamic import. 2 | // This module will be updated automatically in development mode, do NOT edit it manually. 3 | 4 | import * as $0 from "./_404.tsx"; 5 | import * as $1 from "./_app.tsx"; 6 | import * as $2 from "./index.tsx"; 7 | import * as $3 from "./todos.tsx"; 8 | 9 | export default { 10 | "/_404": $0, 11 | "/_app": $1, 12 | "/": $2, 13 | "/todos": $3, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/routes/_export.ts: -------------------------------------------------------------------------------- 1 | // Exports router modules for serverless env that doesn't support the dynamic import. 2 | // This module will be updated automatically in development mode, do NOT edit it manually. 3 | 4 | import * as $0 from "./_404.tsx"; 5 | import * as $1 from "./_app.tsx"; 6 | import * as $2 from "./index.tsx"; 7 | import * as $3 from "./todos.tsx"; 8 | 9 | export default { 10 | "/_404": $0, 11 | "/_app": $1, 12 | "/": $2, 13 | "/todos": $3, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/api-app/routes/_export.ts: -------------------------------------------------------------------------------- 1 | // Exports router modules for serverless env that doesn't support the dynamic import. 2 | // This module will be updated automatically in development mode, do NOT edit it manually. 3 | 4 | import * as $0 from "./ws.ts"; 5 | import * as $1 from "./index.ts"; 6 | import * as $2 from "./users/index.ts"; 7 | import * as $3 from "./users/$uid.ts"; 8 | 9 | export default { 10 | "/ws": $0, 11 | "/": $1, 12 | "/users/index": $2, 13 | "/users/:uid": $3, 14 | }; 15 | -------------------------------------------------------------------------------- /examples/yew-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod components; 3 | mod routes; 4 | 5 | use wasm_bindgen::prelude::*; 6 | 7 | #[wasm_bindgen] 8 | pub fn main() { 9 | yew::Renderer::::new().hydrate(); 10 | } 11 | 12 | #[wasm_bindgen] 13 | pub async fn ssr(url: String) -> Result { 14 | let html = yew::ServerRenderer::::with_props(app::AppProps { ssr_url: Some(url) }) 15 | .render() 16 | .await; 17 | Ok(serde_wasm_bindgen::to_value(&html).unwrap()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | export default function Sidebar() { 2 | return ( 3 | <> 4 |

Archive

5 |
    6 |
  • May 2021
  • 7 |
  • April 2021
  • 8 |
  • March 2021
  • 9 |
  • February 2021
  • 10 |
  • January 2021
  • 11 |
  • December 2020
  • 12 |
  • November 2020
  • 13 |
  • October 2020
  • 14 |
  • September 2020
  • 15 |
16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod components; 3 | mod routes; 4 | 5 | use wasm_bindgen::prelude::*; 6 | 7 | #[wasm_bindgen] 8 | pub fn main() { 9 | yew::Renderer::::new().hydrate(); 10 | } 11 | 12 | #[wasm_bindgen] 13 | pub async fn ssr(url: String) -> Result { 14 | let html = yew::ServerRenderer::::with_props(app::AppProps { ssr_url: Some(url) }) 15 | .render() 16 | .await; 17 | Ok(serde_wasm_bindgen::to_value(&html).unwrap()) 18 | } 19 | -------------------------------------------------------------------------------- /examples/api-app/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import denoDeploy from "aleph/plugins/deploy"; 3 | import modules from "./routes/_export.ts"; 4 | 5 | declare global { 6 | interface Context { 7 | foo: string; 8 | } 9 | } 10 | 11 | serve({ 12 | plugins: [ 13 | denoDeploy({ modules }), 14 | ], 15 | middlewares: [ 16 | { 17 | name: "foo", 18 | fetch: (_req, ctx) => { 19 | ctx.foo = "bar"; 20 | return ctx.next(); 21 | }, 22 | }, 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/README.md: -------------------------------------------------------------------------------- 1 | # Yew App 2 | 3 | A [Yew](https://yew.rs/) SSR demo application powered by Aleph.js in Deno, deploy to 4 | [Deno Deploy](https://deno.com/deploy). 5 | 6 | 🚀 Both the CSR and SSR are using **[WebAssembly](https://webassembly.org/)** with great performance in modern browsers 7 | and serverless platform at edge. 8 | 9 | You will need [rust](https://www.rust-lang.org/tools/install) **1.56+** and 10 | [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/). 11 | 12 | https://aleph-yew.deno.dev/ 13 | -------------------------------------------------------------------------------- /examples/leptos-app/src/routes.rs: -------------------------------------------------------------------------------- 1 | use leptos::*; 2 | use leptos_meta::*; 3 | use leptos_router::*; 4 | 5 | pub mod index; 6 | use index::{Counter, CounterProps}; 7 | 8 | #[component] 9 | pub fn App(cx: Scope) -> impl IntoView { 10 | provide_meta_context(cx); 11 | view! { 12 | cx, 13 | 14 |
15 | 16 | 19 | }/> 20 | 21 |
22 |
23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/src/routes.rs: -------------------------------------------------------------------------------- 1 | use leptos::*; 2 | use leptos_meta::*; 3 | use leptos_router::*; 4 | 5 | pub mod index; 6 | use index::{Counter, CounterProps}; 7 | 8 | #[component] 9 | pub fn App(cx: Scope) -> impl IntoView { 10 | provide_meta_context(cx); 11 | view! { 12 | cx, 13 | 14 |
15 | 16 | 19 | }/> 20 | 21 |
22 |
23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/yew-app/src/routes.rs: -------------------------------------------------------------------------------- 1 | pub mod _404; 2 | pub mod index; 3 | pub mod todos; 4 | 5 | use yew::prelude::*; 6 | use yew_router::prelude::*; 7 | use index::Index; 8 | use todos::Todos; 9 | use _404::NotFound; 10 | 11 | #[derive(Clone, Routable, PartialEq)] 12 | pub enum Route { 13 | #[at("/")] 14 | Home, 15 | #[at("/todos")] 16 | Todos, 17 | #[at("/404")] 18 | #[not_found] 19 | NotFound, 20 | } 21 | 22 | pub fn switch(routes: Route) -> Html { 23 | match routes { 24 | Route::Home => html! { }, 25 | Route::Todos => html! { }, 26 | Route::NotFound => html! { }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/routes.rs: -------------------------------------------------------------------------------- 1 | pub mod _404; 2 | pub mod index; 3 | pub mod todos; 4 | 5 | use yew::prelude::*; 6 | use yew_router::prelude::*; 7 | use index::Index; 8 | use todos::Todos; 9 | use _404::NotFound; 10 | 11 | #[derive(Clone, Routable, PartialEq)] 12 | pub enum Route { 13 | #[at("/")] 14 | Home, 15 | #[at("/todos")] 16 | Todos, 17 | #[at("/404")] 18 | #[not_found] 19 | NotFound, 20 | } 21 | 22 | pub fn switch(routes: Route) -> Html { 23 | match routes { 24 | Route::Home => html! { }, 25 | Route::Todos => html! { }, 26 | Route::NotFound => html! { }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/routes/_404.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "aleph/react"; 2 | 3 | export default function E404() { 4 | return ( 5 |
11 |

12 | Ooooooops, nothing here! 13 |

14 |

15 | 19 | Go back to the homepage 20 | 21 |

22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /tests/server_media_type_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "std/testing/asserts.ts"; 2 | import { getContentType } from "../server/media_type.ts"; 3 | 4 | Deno.test("[unit] server/media_type.ts: getContentType", () => { 5 | assertEquals(getContentType("/mod.ts"), "application/typescript"); 6 | assertEquals(getContentType("/mod.tsx"), "text/tsx"); 7 | assertEquals(getContentType("/compression.gz"), "application/gzip"); 8 | assertEquals(getContentType("/compression.tar"), "application/tar"); 9 | assertEquals(getContentType("/compression.tar.gz"), "application/tar+gzip"); 10 | assertEquals(getContentType("/unknown"), "application/octet-stream"); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/components/Post.tsx: -------------------------------------------------------------------------------- 1 | export default function Post() { 2 | return ( 3 | <> 4 |

Hello world

5 |

6 | This demo is artificially slowed down. Open routes/index.tsx{" "} 7 | to adjust how much different things are slowed down. 8 |

9 |

10 | Notice how HTML for comments "streams in" before the JS (or React) has loaded on the page. 11 |

12 |

13 | Also notice that the JS for comments and sidebar has been code-split, but HTML for it is still included in the 14 | server output. 15 |

16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/routes/_404.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::routes::Route; 5 | 6 | #[function_component] 7 | pub fn NotFound() -> Html { 8 | html! { 9 |
13 |

14 | {"Ooooooops, nothing here!"} 15 |

16 |

17 | to={Route::Home} classes="text-gray-500 hover:underline"> 18 | {"Go back to the homepage"} 19 | > 20 |

21 |
22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/yew-app/assets/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /examples/react-app/assets/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /examples/react-mdx-app/assets/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /examples/leptos-app/src/routes/index.rs: -------------------------------------------------------------------------------- 1 | use leptos::*; 2 | 3 | /// A simple counter component. 4 | /// 5 | /// You can use doc comments like this to document your component. 6 | #[component] 7 | pub fn Counter( 8 | cx: Scope, 9 | ) -> impl IntoView { 10 | let (value, set_value) = create_signal(cx, 0); 11 | let step = 1; 12 | 13 | view! { cx, 14 |
15 | 16 | 17 | "Value: " {value} "!" 18 | 19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/assets/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /framework/react/refresh.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | // react-refresh 4 | // @link https://github.com/facebook/react/issues/16604#issuecomment-528663101 5 | 6 | import runtime from "https://esm.sh/v126/react-refresh@0.14.0/runtime"; 7 | 8 | let timer: number | null; 9 | const refresh = () => { 10 | if (timer !== null) { 11 | clearTimeout(timer); 12 | } 13 | timer = setTimeout(() => { 14 | runtime.performReactRefresh(); 15 | timer = null; 16 | }, 50); 17 | }; 18 | 19 | runtime.injectIntoGlobalHook(window); 20 | 21 | Object.assign(window, { 22 | $RefreshReg$: () => {}, 23 | $RefreshSig$: () => (type: unknown) => type, 24 | }); 25 | 26 | export { refresh as __REACT_REFRESH__, runtime as __REACT_REFRESH_RUNTIME__ }; 27 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/server.ts: -------------------------------------------------------------------------------- 1 | import { serve } from "aleph/server"; 2 | import denoDeploy from "aleph/plugins/deploy"; 3 | import react from "aleph/plugins/react"; 4 | import unocss from "aleph/plugins/unocss"; 5 | import presetUno from "@unocss/preset-uno"; 6 | import { GithubOauth } from "./middlewares/oauth.ts"; 7 | import modules from "./routes/_export.ts"; 8 | 9 | serve({ 10 | plugins: [ 11 | denoDeploy({ modules }), 12 | react({ ssr: true }), 13 | unocss({ 14 | presets: [presetUno()], 15 | }), 16 | ], 17 | middlewares: [ 18 | new GithubOauth({ 19 | clientId: Deno.env.get("GITHUB_OAUTH_CLIENT_ID"), 20 | clientSecret: Deno.env.get("GITHUB_OAUTH_CLIENT_SECRET"), 21 | }), 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /examples/react-mdx-app/components/Heading.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from "react"; 2 | import { createElement } from "react"; 3 | 4 | // The props `id` is generated by `rehype-slug` plugin 5 | export const Heading: FC> = ({ id, level, children, ...rest }) => { 6 | return createElement( 7 | `h${level}`, 8 | { ...rest, id }, 9 | <> 10 | § 11 | {children} 12 | , 13 | ); 14 | }; 15 | 16 | export const components: Record>> = {}; 17 | 18 | for (let i = 0; i < 6; i++) { 19 | components[`h${i + 1}`] = (props) => { 20 | return ; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { useData } from "aleph/react"; 2 | import type { GithubUser } from "../middlewares/oauth.ts"; 3 | 4 | export const data = (_req: Request, ctx: Context<{ user: GithubUser }>) => { 5 | return Response.json({ user: ctx.user }); 6 | }; 7 | 8 | export default function Index() { 9 | const { data: { user } } = useData<{ user: GithubUser }>(); 10 | return ( 11 |
12 |
13 | 14 | {user.name} 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /tests/server_html_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "std/testing/asserts.ts"; 2 | import { parseHtmlLinks } from "../server/html.ts"; 3 | 4 | Deno.test("[unit] lib/html.ts: parseHtmlLinks", async () => { 5 | const html = ` 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | `; 23 | const links = await parseHtmlLinks(html); 24 | assertEquals(links, ["./assets/logo.svg", "./style/app.css", "./main.tsx"]); 25 | }); 26 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "std/": "https://deno.land/std@0.180.0/", 4 | "aleph/": "./", 5 | "aleph/server": "./server/mod.ts", 6 | "aleph/dev": "./server/dev.ts", 7 | "aleph/plugins/deploy": "./plugins/deploy.ts", 8 | "aleph/plugins/unocss": "./plugins/unocss.ts", 9 | "aleph/plugins/mdx": "./plugins/mdx.ts", 10 | "aleph/react": "./framework/react/mod.ts", 11 | "aleph/plugins/react": "./framework/react/plugin.ts", 12 | "@unocss/core": "https://esm.sh/v126/@unocss/core@0.50.6", 13 | "@unocss/preset-uno": "https://esm.sh/v126/@unocss/preset-uno@0.50.6", 14 | "react": "https://esm.sh/v126/react@18.2.0", 15 | "react-dom": "https://esm.sh/v126/react-dom@18.2.0", 16 | "react-dom/": "https://esm.sh/v126/react-dom@18.2.0/", 17 | "@mdx-js/react": "https://esm.sh/v126/@mdx-js/react@2.3.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | type CTX = import("./server/types.ts").Context; 2 | // deno-lint-ignore no-empty-interface 3 | declare interface Context extends CTX {} 4 | 5 | /** The Middleare for Aleph server. */ 6 | declare interface Middleware { 7 | /** The middleware name. */ 8 | readonly name?: string; 9 | /** The middleware fetch method. */ 10 | fetch(request: Request, context: Context): Promise | Response; 11 | } 12 | 13 | declare interface ImportMeta { 14 | /** Aleph.js HMR `hot` API. */ 15 | readonly hot?: { 16 | readonly data: Record; 17 | accept>(callback?: (module: T) => void): void; 18 | decline(): void; 19 | dispose: (callback: (data: Record) => void) => void; 20 | invalidate(): void; 21 | watchFile(filename: string, callback: () => void): () => void; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /examples/react-mdx-app/server.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { serve } from "aleph/server"; 4 | import denoDeploy from "aleph/plugins/deploy"; 5 | import react from "aleph/plugins/react"; 6 | import mdx from "aleph/plugins/mdx"; 7 | import modules from "./routes/_export.ts"; 8 | 9 | // check https://mdxjs.com/docs/extending-mdx 10 | import remarkFrontmatter from "https://esm.sh/v126/remark-frontmatter@4.0.1"; 11 | import remarkGFM from "https://esm.sh/v126/remark-gfm@3.0.1"; 12 | import rehypeHighlight from "https://esm.sh/v126/rehype-highlight@5.0.2"; 13 | import rehypeSlug from "https://esm.sh/v126/rehype-slug@5.0.1"; 14 | 15 | serve({ 16 | plugins: [ 17 | denoDeploy({ modules }), 18 | mdx({ 19 | remarkPlugins: [remarkFrontmatter, remarkGFM], 20 | rehypePlugins: [rehypeHighlight, rehypeSlug], 21 | providerImportSource: "@mdx-js/react", 22 | }), 23 | react({ ssr: true }), 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /examples/yew-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-app" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | # this is the development version of Yew 11 | yew = { git = "https://github.com/yewstack/yew.git", features = ["ssr", "hydration"] } 12 | yew-router = { git = "https://github.com/yewstack/yew.git" } 13 | wasm-bindgen = { version = "0.2.83", features = ["strict-macro"] } 14 | wasm-bindgen-futures = "0.4.33" 15 | serde-wasm-bindgen = "0.4.3" 16 | web-sys = "0.3.60" 17 | url = "2.2.2" 18 | 19 | [profile.release] 20 | # less code to include into binary 21 | panic = 'abort' 22 | # optimization over all codebase ( better optimization, slower build ) 23 | codegen-units = 1 24 | # optimization for size ( more aggressive ) 25 | opt-level = 'z' 26 | # optimization for size 27 | # opt-level = 's' 28 | # link time optimization using using whole-program analysis 29 | lto = true 30 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yew-app" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | # this is the development version of Yew 11 | yew = { git = "https://github.com/yewstack/yew.git", features = ["ssr", "hydration"] } 12 | yew-router = { git = "https://github.com/yewstack/yew.git" } 13 | wasm-bindgen = { version = "0.2.83", features = ["strict-macro"] } 14 | wasm-bindgen-futures = "0.4.33" 15 | serde-wasm-bindgen = "0.4.3" 16 | web-sys = "0.3.60" 17 | url = "2.2.2" 18 | 19 | [profile.release] 20 | # less code to include into binary 21 | panic = 'abort' 22 | # optimization over all codebase ( better optimization, slower build ) 23 | codegen-units = 1 24 | # optimization for size ( more aggressive ) 25 | opt-level = 'z' 26 | # optimization for size 27 | # opt-level = 's' 28 | # link time optimization using using whole-program analysis 29 | lto = true 30 | -------------------------------------------------------------------------------- /framework/react/client.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from "react"; 2 | import { createRoot, hydrateRoot } from "react-dom/client"; 3 | import { App, RouterProps } from "./mod.ts"; 4 | import { createCSRContext } from "../core/router.ts"; 5 | 6 | export type RenderOptions = { 7 | root?: string | HTMLElement | null; 8 | createPortal?: RouterProps["createPortal"]; 9 | }; 10 | 11 | export async function bootstrap(options: RenderOptions = {}) { 12 | const { root = "#root", createPortal } = options; 13 | const rootEl = typeof root === "string" ? document.querySelector(root) : root; 14 | if (!rootEl) { 15 | throw new Error(`No element found for selector "${root}"`); 16 | } 17 | const csrContext = await createCSRContext(); 18 | const el = createElement(App, { csrContext, createPortal }); 19 | if (document.head.querySelector("script#ssr-data")) { 20 | hydrateRoot(rootEl, el); 21 | } else { 22 | createRoot(rootEl).render(el); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/src/routes/index.rs: -------------------------------------------------------------------------------- 1 | use leptos::*; 2 | 3 | /// A simple counter component. 4 | /// 5 | /// You can use doc comments like this to document your component. 6 | #[component] 7 | pub fn Counter( 8 | cx: Scope, 9 | ) -> impl IntoView { 10 | let (value, set_value) = create_signal(cx, 0); 11 | let step = 1; 12 | 13 | view! { cx, 14 |
15 | 16 | 17 | "Value: " {value} "!" 18 | 19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Aleph.js CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | format: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup deno 19 | uses: denoland/setup-deno@main 20 | with: 21 | deno-version: v1.x 22 | 23 | - name: Deno fmt check 24 | run: deno fmt --check 25 | 26 | - name: Deno lint 27 | run: deno lint 28 | 29 | test: 30 | name: Test 31 | runs-on: ${{ matrix.os }} 32 | 33 | strategy: 34 | matrix: 35 | os: [macOS-latest, windows-latest, ubuntu-latest] 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | 41 | - name: Setup deno 42 | uses: denoland/setup-deno@main 43 | with: 44 | deno-version: v1.x 45 | 46 | - name: Deno test 47 | run: deno test -A 48 | -------------------------------------------------------------------------------- /examples/monaco-editor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 34 | 35 | 36 | 37 |
38 |
loading...
39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/yew-app/README.md: -------------------------------------------------------------------------------- 1 | # Yew App 2 | 3 | This is a demo application powered by Aleph.js in Deno using [Yew](https://yew.rs/) SSR. 4 | 5 | - 🚀 Both the client-side rendering (CSR) and server-side rendering (SSR) use 6 | **[WebAssembly](https://webassembly.org/)**, delivering great performance on modern browsers and serverless platforms 7 | at the edge. 8 | - 🦀 To use this application, you will need [rust](https://www.rust-lang.org/tools/install) version **1.56+** and 9 | [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/). 10 | - 🦕 This application can be deployed with [Deno Deploy](https://deno.com/deploy) at https://aleph-yew.deno.dev/ 11 | 12 | ## Running the Example Locally 13 | 14 | ```bash 15 | # Run the example app in development mode 16 | deno run -A examples/yew-app/dev.ts 17 | 18 | # Run the example app in production mode 19 | deno run -A examples/yew-app/server.ts 20 | ``` 21 | 22 | ## Using as a Template 23 | 24 | ```bash 25 | deno run -A -r https://alephjs.org/init.ts --template=yew 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/dev.ts: -------------------------------------------------------------------------------- 1 | import { fromFileUrl, join } from "std/path/mod.ts"; 2 | import dev, { createWatchFsEmitter } from "aleph/dev"; 3 | 4 | const emitter = createWatchFsEmitter(); 5 | emitter.on("modify", ({ specifier }) => { 6 | if (specifier.endsWith(".rs")) { 7 | // rebuild the yew app then restart the dev server 8 | start(); 9 | } 10 | }); 11 | 12 | let buildProc: Deno.Process | null = null; 13 | 14 | // build the yew app then start the dev server 15 | async function start() { 16 | const cwd = fromFileUrl(new URL(".", import.meta.url)); 17 | if (buildProc) { 18 | buildProc.kill("SIGTERM"); 19 | buildProc.close(); 20 | } 21 | buildProc = Deno.run({ 22 | cmd: ["wasm-pack", "build", "--target", "web"], 23 | stdout: "inherit", 24 | stderr: "inherit", 25 | cwd, 26 | }); 27 | try { 28 | await buildProc.status(); 29 | buildProc.close(); 30 | await Deno.remove(`${cwd}/pkg/.gitignore`); 31 | dev(join(cwd, "server.ts")); 32 | } finally { 33 | buildProc = null; 34 | } 35 | } 36 | 37 | start(); 38 | -------------------------------------------------------------------------------- /examples/yew-app/dev.ts: -------------------------------------------------------------------------------- 1 | import { fromFileUrl, join } from "std/path/mod.ts"; 2 | import dev, { createWatchFsEmitter } from "aleph/dev"; 3 | 4 | const emitter = createWatchFsEmitter(); 5 | emitter.on("modify", ({ specifier }) => { 6 | if (specifier.endsWith(".rs")) { 7 | // rebuild the yew app then restart the dev server 8 | start(); 9 | } 10 | }); 11 | 12 | let buildProc: Deno.Process | null = null; 13 | 14 | // build the yew app then start the dev server 15 | async function start() { 16 | const cwd = fromFileUrl(new URL(".", import.meta.url)); 17 | if (buildProc) { 18 | buildProc.kill("SIGTERM"); 19 | buildProc.close(); 20 | } 21 | buildProc = Deno.run({ 22 | cmd: ["wasm-pack", "build", "--target", "web"], 23 | stdout: "inherit", 24 | stderr: "inherit", 25 | cwd, 26 | }); 27 | try { 28 | await buildProc.status(); 29 | buildProc.close(); 30 | await Deno.remove(`${cwd}/pkg/.gitignore`); 31 | // start aleph dev server 32 | dev(join(cwd, "server.ts")); 33 | } finally { 34 | buildProc = null; 35 | } 36 | } 37 | 38 | start(); 39 | -------------------------------------------------------------------------------- /examples/leptos-app/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Greg Johnston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2022 The Aleph.js authors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/api-app/routes/users/index.ts: -------------------------------------------------------------------------------- 1 | export type User = { 2 | uid: number; 3 | name: string; 4 | createdAt: string; 5 | }; 6 | 7 | export const users: User[] = [ 8 | { uid: 1, name: "john doe", createdAt: "2020-01-01T00:00:00.000Z" }, 9 | { uid: 2, name: "mike johnson", createdAt: "2020-01-02T00:00:00.000Z" }, 10 | { uid: 3, name: "mary jane", createdAt: "2020-01-03T00:00:00.000Z" }, 11 | { uid: 4, name: "larry wall", createdAt: "2020-01-04T00:00:00.000Z" }, 12 | ]; 13 | 14 | // GET "/users" 15 | export function GET(req: Request) { 16 | const url = new URL(req.url); 17 | return Response.json(users.map((user) => ({ ...user, url: `${url.origin}/users/${user.uid}` }))); 18 | } 19 | 20 | // POST "/users" 21 | export async function POST(req: Request) { 22 | const data = await req.formData(); 23 | const name = data.get("name"); 24 | if (typeof name !== "string" || name.length === 0) { 25 | return Response.json({ error: { message: "invalid name", code: "invalidName" } }, { status: 400 }); 26 | } 27 | const user: User = { uid: users.length + 1, name, createdAt: new Date().toISOString() }; 28 | users.push(user); 29 | return Response.json(user); 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Greg Johnston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/yew-app/src/app.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use url::Url; 3 | use yew::prelude::*; 4 | use yew_router::history::{AnyHistory, History, MemoryHistory}; 5 | use yew_router::prelude::*; 6 | 7 | use crate::components::header::Header; 8 | use crate::routes::{switch, Route}; 9 | 10 | #[derive(Properties, PartialEq, Default)] 11 | pub struct AppProps { 12 | pub ssr_url: Option, 13 | } 14 | 15 | #[function_component] 16 | pub fn App(props: &AppProps) -> Html { 17 | if let Some(url) = &props.ssr_url { 18 | let history = AnyHistory::from(MemoryHistory::new()); 19 | let url = Url::parse(url).unwrap(); 20 | let mut queries: HashMap = HashMap::new(); 21 | for (key, value) in url.query_pairs() { 22 | queries.insert(key.into(), value.into()); 23 | } 24 | history.push_with_query(url.path(), queries).unwrap(); 25 | html! { 26 | 27 |
28 | render={switch} /> 29 | 30 | } 31 | } else { 32 | html! { 33 | 34 |
35 | render={switch} /> 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "dom.extras", 7 | "deno.ns" 8 | ], 9 | "types": [ 10 | "./types.d.ts" 11 | ], 12 | "jsx": "react-jsx", 13 | "jsxImportSource": "https://esm.sh/v126/react@18.2.0" 14 | }, 15 | "importMap": "./import_map.json", 16 | "fmt": { 17 | "options": { 18 | "lineWidth": 120 19 | }, 20 | "files": { 21 | "exclude": [ 22 | ".vscode", 23 | "LICENSE", 24 | "CODE_OF_CONDUCT.md", 25 | "examples/yew-app/pkg", 26 | "examples/yew-app/target", 27 | "examples/with-unocss/yew-app/pkg", 28 | "examples/with-unocss/yew-app/target", 29 | "examples/leptos-app/pkg", 30 | "examples/leptos-app/target", 31 | "examples/with-unocss/leptos-app/pkg", 32 | "examples/with-unocss/leptos-app/target" 33 | ] 34 | } 35 | }, 36 | "lint": { 37 | "files": { 38 | "exclude": [ 39 | "examples/yew-app/pkg", 40 | "examples/with-unocss/yew-app/pkg", 41 | "examples/leptos-app/pkg", 42 | "examples/with-unocss/leptos-app/pkg" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/app.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use url::Url; 3 | use yew::prelude::*; 4 | use yew_router::history::{AnyHistory, History, MemoryHistory}; 5 | use yew_router::prelude::*; 6 | 7 | use crate::components::header::Header; 8 | use crate::routes::{switch, Route}; 9 | 10 | #[derive(Properties, PartialEq, Default)] 11 | pub struct AppProps { 12 | pub ssr_url: Option, 13 | } 14 | 15 | #[function_component] 16 | pub fn App(props: &AppProps) -> Html { 17 | if let Some(url) = &props.ssr_url { 18 | let history = AnyHistory::from(MemoryHistory::new()); 19 | let url = Url::parse(url).unwrap(); 20 | let mut queries: HashMap = HashMap::new(); 21 | for (key, value) in url.query_pairs() { 22 | queries.insert(key.into(), value.into()); 23 | } 24 | history.push_with_query(url.path(), queries).unwrap(); 25 | html! { 26 | 27 |
28 | render={switch} /> 29 | 30 | } 31 | } else { 32 | html! { 33 | 34 |
35 | render={switch} /> 36 | 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/react-app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/yew-app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/react-mdx-app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/docs.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import { MDXProvider } from "@mdx-js/react"; 3 | import { Head, NavLink } from "aleph/react"; 4 | import { components } from "../components/Heading.tsx"; 5 | 6 | const nav = [ 7 | ["About", "/docs"], 8 | ["Get Started", "/docs/get-started"], 9 | ]; 10 | 11 | export default function Docs(props: PropsWithChildren) { 12 | return ( 13 | <> 14 | 15 | 16 | 17 |
18 | 32 |
33 | 34 | {props.children} 35 | 36 |
37 |
38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /server/deps.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | // deno std 4 | export { concat as concatBytes } from "https://deno.land/std@0.192.0/bytes/mod.ts"; 5 | export { encode as btoa } from "https://deno.land/std@0.192.0/encoding/base64.ts"; 6 | export * as colors from "https://deno.land/std@0.192.0/fmt/colors.ts"; 7 | export { ensureDir } from "https://deno.land/std@0.192.0/fs/ensure_dir.ts"; 8 | export { serve, serveTls } from "https://deno.land/std@0.192.0/http/server.ts"; 9 | export * as path from "https://deno.land/std@0.192.0/path/mod.ts"; 10 | export * as jsonc from "https://deno.land/std@0.192.0/jsonc/mod.ts"; 11 | export { parse as parseCliArgs } from "https://deno.land/std@0.192.0/flags/mod.ts"; 12 | 13 | // third-party 14 | // @deno-types="https://deno.land/x/esbuild@v0.17.12/mod.d.ts" 15 | export * as esbuild from "https://deno.land/x/esbuild@v0.17.12/mod.js"; 16 | export * from "https://deno.land/x/aleph_compiler@0.9.3/mod.ts"; 17 | export * from "https://deno.land/x/aleph_compiler@0.9.3/types.ts"; 18 | export { default as initLolHtml, HTMLRewriter } from "https://deno.land/x/lol_html@0.0.6/mod.ts"; 19 | export { default as lolHtmlWasm } from "https://deno.land/x/lol_html@0.0.6/wasm.js"; 20 | 21 | // npm 22 | export { default as mitt, type Emitter } from "https://esm.sh/mitt@3.0.0?pin=v110"; 23 | -------------------------------------------------------------------------------- /examples/leptos-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | cfg_if! { 3 | if #[cfg(feature = "hydrate")] { 4 | use wasm_bindgen::prelude::wasm_bindgen; 5 | use leptos::*; 6 | pub mod routes; 7 | use routes::{App, AppProps}; 8 | 9 | #[wasm_bindgen] 10 | pub fn hydrate() { 11 | console_error_panic_hook::set_once(); 12 | _ = console_log::init_with_level(log::Level::Debug); 13 | 14 | mount_to_body(|cx| { 15 | view! { cx, } 16 | }) 17 | } 18 | } 19 | 20 | else if #[cfg(feature = "ssr")] { 21 | use wasm_bindgen::prelude::wasm_bindgen; 22 | use leptos::*; 23 | use leptos_router::{ServerIntegration, RouterIntegrationContext}; 24 | pub mod routes; 25 | use routes::{App, AppProps}; 26 | 27 | #[wasm_bindgen] 28 | pub fn ssr(url: String) -> String { 29 | let history = ServerIntegration { path: url }; 30 | let router_integration = RouterIntegrationContext::new(history); 31 | render_to_string(move |cx| { 32 | provide_context::(cx, router_integration); 33 | view! { cx, } 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/style/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: system-ui, sans-serif; 3 | } 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | nav { 10 | padding: 20px; 11 | } 12 | 13 | .sidebar { 14 | padding: 10px; 15 | height: 500px; 16 | float: left; 17 | width: 30%; 18 | } 19 | 20 | .post { 21 | padding: 20px; 22 | float: left; 23 | width: 60%; 24 | } 25 | 26 | h1, h2 { 27 | padding: 0; 28 | } 29 | 30 | ul, li { 31 | margin: 0; 32 | } 33 | 34 | .post p { 35 | font-size: larger; 36 | font-family: Georgia, serif; 37 | } 38 | 39 | .comments { 40 | margin-top: 40px; 41 | } 42 | 43 | .comment { 44 | border: 2px solid #aaa; 45 | border-radius: 4px; 46 | padding: 20px; 47 | } 48 | 49 | /* https://codepen.io/mandelid/pen/vwKoe */ 50 | .spinner { 51 | display: inline-block; 52 | transition: opacity linear 0.1s; 53 | width: 20px; 54 | height: 20px; 55 | border: 3px solid rgba(80, 80, 80, 0.5); 56 | border-radius: 50%; 57 | border-top-color: #fff; 58 | animation: spin 1s ease-in-out infinite; 59 | opacity: 0; 60 | } 61 | .spinner--active { 62 | opacity: 1; 63 | } 64 | 65 | @keyframes spin { 66 | to { 67 | transform: rotate(360deg); 68 | } 69 | } 70 | @keyframes spin { 71 | to { 72 | transform: rotate(360deg); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | cfg_if! { 3 | if #[cfg(feature = "hydrate")] { 4 | use wasm_bindgen::prelude::wasm_bindgen; 5 | use leptos::*; 6 | pub mod routes; 7 | use routes::{App, AppProps}; 8 | 9 | #[wasm_bindgen] 10 | pub fn hydrate() { 11 | console_error_panic_hook::set_once(); 12 | _ = console_log::init_with_level(log::Level::Debug); 13 | 14 | mount_to_body(|cx| { 15 | view! { cx, } 16 | }) 17 | } 18 | } 19 | 20 | else if #[cfg(feature = "ssr")] { 21 | use wasm_bindgen::prelude::wasm_bindgen; 22 | use leptos::*; 23 | use leptos_router::{ServerIntegration, RouterIntegrationContext}; 24 | pub mod routes; 25 | use routes::{App, AppProps}; 26 | 27 | #[wasm_bindgen] 28 | pub fn ssr(url: String) -> String { 29 | let history = ServerIntegration { path: url }; 30 | let router_integration = RouterIntegrationContext::new(history); 31 | render_to_string(move |cx| { 32 | provide_context::(cx, router_integration); 33 | view! { cx, } 34 | }) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/yew-app/src/routes/index.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::routes::Route; 5 | 6 | #[function_component] 7 | pub fn Index() -> Html { 8 | html! { 9 |
10 | 14 |

{"The Fullstack Framework in Deno."}

15 |

16 | {"Aleph.js"} 17 | {" gives you the best developer experience for building web applications"} 18 |
19 | {"with modern toolings."} {"."} 20 |

21 | 32 | 37 |
38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/api-app/routes/users/$uid.ts: -------------------------------------------------------------------------------- 1 | import { users } from "./index.ts"; 2 | 3 | // GET "/users/:uid" 4 | export function GET(_req: Request, ctx: Context) { 5 | const user = users.find((u) => String(u.uid) === ctx.params.uid); 6 | if (user) { 7 | return Response.json(user); 8 | } 9 | return Response.json({ error: { message: "user not found", code: "userNotFound" } }, { status: 404 }); 10 | } 11 | 12 | // PATCH "/users/:uid" 13 | export async function PATCH(req: Request, ctx: Context) { 14 | const user = users.find((u) => String(u.uid) === ctx.params.uid); 15 | if (user) { 16 | const data = await req.formData(); 17 | const name = data.get("name"); 18 | if (typeof name !== "string" || name.length === 0) { 19 | return Response.json({ error: { message: "invalid name", code: "invalidName" } }, { status: 400 }); 20 | } 21 | user.name = name; 22 | return Response.json(user); 23 | } 24 | return Response.json({ error: { message: "user not found", code: "userNotFound" } }, { status: 404 }); 25 | } 26 | 27 | // DELETE "/users/:uid" 28 | export function DELETE(_req: Request, ctx: Context) { 29 | const index = users.findIndex((u) => String(u.uid) === ctx.params.uid); 30 | if (index) { 31 | return Response.json(users.splice(index, 1)[0]); 32 | } 33 | return Response.json({ error: { message: "user not found", code: "userNotFound" } }, { status: 404 }); 34 | } 35 | -------------------------------------------------------------------------------- /examples/react-app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Link } from "aleph/react"; 2 | 3 | const externalLinks = [ 4 | ["Get Started", "https://alephjs.org/docs/get-started"], 5 | ["Docs", "https://alephjs.org/docs"], 6 | ["Github", "https://github.com/alephjs/aleph.js"], 7 | ]; 8 | 9 | export default function Index() { 10 | return ( 11 |
12 | 13 | Aleph.js 14 | 15 | 16 |

17 | 18 |

19 |

20 | The Fullstack Framework in Deno. 21 |

22 |

23 | Aleph.js gives you the best developer experience for building web applications
{" "} 24 | with modern toolings. 25 |

26 |
27 | {externalLinks.map(([text, href]) => ( 28 | 33 | {text} 34 | 35 | ))} 36 |
37 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Link } from "aleph/react"; 2 | 3 | const externalLinks = [ 4 | ["Get Started", "https://alephjs.org/docs/get-started"], 5 | ["Docs", "https://alephjs.org/docs"], 6 | ["Github", "https://github.com/alephjs/aleph.js"], 7 | ]; 8 | 9 | export default function Index() { 10 | return ( 11 |
12 | 13 | Aleph.js 14 | 15 | 16 |

17 | 18 |

19 |

20 | The Fullstack Framework in Deno. 21 |

22 |

23 | Aleph.js gives you the best developer experience for building web applications
{" "} 24 | with modern toolings. 25 |

26 |
27 | {externalLinks.map(([text, href]) => ( 28 | 33 | {text} 34 | 35 | ))} 36 |
37 | 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /plugins/unocss.ts: -------------------------------------------------------------------------------- 1 | import { createGenerator, type UserConfig } from "@unocss/core"; 2 | import type { AtomicCSSEngine, Plugin } from "../server/types.ts"; 3 | 4 | export type UnoConfig = UserConfig & { test?: RegExp; resetCSS?: boolean }; 5 | 6 | export function UnoCSS(config?: UnoConfig): AtomicCSSEngine { 7 | if (!Array.isArray(config?.presets)) { 8 | throw new Error("UnoCSS: `presets` must be an array."); 9 | } 10 | const generator = createGenerator(config); 11 | if (config?.test) { 12 | Reflect.set(generator, "test", config?.test); 13 | } 14 | if (config?.test instanceof RegExp) { 15 | Reflect.set(generator, "test", config.test); 16 | } 17 | if (config?.resetCSS !== false) { 18 | Reflect.set( 19 | generator, 20 | "resetCSS", 21 | `https://esm.sh/@unocss/reset@${generator.version}/tailwind.css`, 22 | ); 23 | } 24 | Reflect.set(generator, "name", "UnoCSS"); 25 | return generator; 26 | } 27 | 28 | export default function UnoCSSPlugin(config: UnoConfig): Plugin; 29 | export default function UnoCSSPlugin(test: RegExp, config: UnoConfig): Plugin; 30 | export default function UnoCSSPlugin(testOrConfig: RegExp | UnoConfig, config?: UnoConfig): Plugin { 31 | return { 32 | name: "unocss", 33 | setup(aleph) { 34 | const isRegexp = testOrConfig instanceof RegExp; 35 | config = isRegexp ? config ?? {} : testOrConfig; 36 | if (isRegexp) { 37 | config.test = testOrConfig; 38 | } 39 | aleph.atomicCSS = UnoCSS(config); 40 | }, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /examples/react-suspense-ssr/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { Head } from "aleph/react"; 3 | import Spinner from "../components/Spinner.tsx"; 4 | import Sidebar from "../components/Sidebar.tsx"; 5 | import Post from "../components/Post.tsx"; 6 | import Comments from "../components/Comments.tsx"; 7 | 8 | // This demo is artificially slowed down. 9 | // Please update `delay` in ms to adjust how much different things are slowed down. 10 | const delay = 3000; 11 | 12 | export const data = { 13 | defer: true, 14 | fetch: async () => { 15 | await new Promise((resolve) => setTimeout(resolve, delay)); 16 | return Response.json({ 17 | comments: [ 18 | "Wait, it doesn't wait for React to load?", 19 | "How does this even work?", 20 | "I like marshmallows", 21 | ], 22 | }); 23 | }, 24 | }; 25 | 26 | export default function Index() { 27 | return ( 28 |
29 | 30 | React 18 Suspense SSR 31 | 32 | 37 |
38 | }> 39 | 40 | 41 |
42 |

Comments

43 | }> 44 | 45 | 46 |
47 |

Thanks for reading!

48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /server/log.ts: -------------------------------------------------------------------------------- 1 | import { colors } from "./deps.ts"; 2 | 3 | export type LevelName = "debug" | "info" | "warn" | "error"; 4 | 5 | export enum Level { 6 | Debug = 0, 7 | Info = 1, 8 | Warn = 2, 9 | Error = 3, 10 | } 11 | 12 | export class Logger { 13 | #level: Level = Level.Info; 14 | 15 | get level(): Level { 16 | return this.#level; 17 | } 18 | 19 | setLevel(level: LevelName): void { 20 | switch (level) { 21 | case "debug": 22 | this.#level = Level.Debug; 23 | break; 24 | case "info": 25 | this.#level = Level.Info; 26 | break; 27 | case "warn": 28 | this.#level = Level.Warn; 29 | break; 30 | case "error": 31 | this.#level = Level.Error; 32 | break; 33 | } 34 | } 35 | 36 | debug(...args: unknown[]): void { 37 | if (this.#level <= Level.Debug) { 38 | console.debug(colors.dim("DEBUG"), ...args); 39 | } 40 | } 41 | 42 | info(...args: unknown[]): void { 43 | if (this.#level <= Level.Info) { 44 | console.log(colors.green("INFO"), ...args); 45 | } 46 | } 47 | 48 | warn(...args: unknown[]): void { 49 | if (this.#level <= Level.Warn) { 50 | console.warn(colors.yellow("WARN"), ...args); 51 | } 52 | } 53 | 54 | error(...args: unknown[]): void { 55 | if (this.#level <= Level.Error) { 56 | console.error(colors.red("ERROR"), ...args); 57 | } 58 | } 59 | 60 | fatal(...args: unknown[]): never { 61 | console.error(colors.red("FATAL"), ...args); 62 | return Deno.exit(1); 63 | } 64 | } 65 | 66 | export default new Logger(); 67 | -------------------------------------------------------------------------------- /framework/react/error.ts: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from "react"; 2 | import { Component, createElement } from "react"; 3 | 4 | type ErrorBoundaryProps = PropsWithChildren<{ Handler: FC<{ error: Error }> }>; 5 | 6 | export class ErrorBoundary extends Component { 7 | constructor(props: ErrorBoundaryProps) { 8 | super(props); 9 | this.state = { error: null }; 10 | } 11 | 12 | static getDerivedStateFromError(error: Error) { 13 | return { error }; 14 | } 15 | 16 | render() { 17 | if (this.state.error instanceof Error) { 18 | return createElement(this.props.Handler, { error: this.state.error }); 19 | } 20 | 21 | return this.props.children; 22 | } 23 | } 24 | 25 | export function Err({ 26 | error: { status, message }, 27 | fullscreen, 28 | }: { 29 | error: { status?: number; message: string }; 30 | fullscreen?: boolean; 31 | }) { 32 | return createElement( 33 | "div", 34 | { 35 | style: fullscreen 36 | ? { 37 | display: "flex", 38 | alignItems: "center", 39 | justifyContent: "center", 40 | width: "100vw", 41 | height: "100vh", 42 | fontSize: 18, 43 | } 44 | : { 45 | margin: "0", 46 | padding: "1.5rem 2rem", 47 | color: "red", 48 | fontSize: 18, 49 | }, 50 | }, 51 | status && createElement("strong", { style: { fontWeight: "600" } }, status), 52 | status && createElement("small", { style: { opacity: 0.5, padding: "0 6px" } }, "-"), 53 | message, 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /framework/core/nomodule.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // deno-lint-ignore-file 3 | 4 | (function (document) { 5 | var containerEl = document.createElement("div"); 6 | var hEl = document.createElement("h2"); 7 | var pEl = document.createElement("p"); 8 | var contarinStyle = { 9 | position: "fixed", 10 | top: "0", 11 | left: "0", 12 | zIndex: "999", 13 | width: "100%", 14 | padding: "30px 0", 15 | margin: "0", 16 | backgroundColor: "#fff9cc", 17 | textAlign: "center", 18 | borderBottom: "1px solid #eee", 19 | boxShadow: "0 1px 5px rgba(0,0,0,0.1)", 20 | }; 21 | var hStyle = { 22 | padding: "0", 23 | margin: "0", 24 | lineHeight: "1.2", 25 | fontSize: "24px", 26 | fontWeight: "700", 27 | color: "#000", 28 | }; 29 | var pStyle = { 30 | padding: "6px 0 0 0", 31 | margin: "0", 32 | lineHeight: "1.2", 33 | fontSize: "15px", 34 | color: "#454545", 35 | }; 36 | for (var key in contarinStyle) { 37 | containerEl.style[key] = contarinStyle[key]; 38 | } 39 | for (var key in hStyle) { 40 | hEl.style[key] = hStyle[key]; 41 | } 42 | for (var key in pStyle) { 43 | pEl.style[key] = pStyle[key]; 44 | } 45 | // todo: i18n 46 | // todo: add browser info 47 | hEl.innerText = "Your browser is out of date!"; 48 | pEl.innerHTML = 49 | 'This site requires ES module, please upgrade your browser.'; 50 | containerEl.appendChild(hEl); 51 | containerEl.appendChild(pEl); 52 | document.body.appendChild(containerEl); 53 | })(window.document); 54 | -------------------------------------------------------------------------------- /framework/react/plugin.ts: -------------------------------------------------------------------------------- 1 | import { createElement } from "react"; 2 | import { renderToReadableStream } from "react-dom/server"; 3 | import type { Plugin, SSRContext, SSROptions } from "../../server/types.ts"; 4 | import { isPlainObject, pick } from "../../shared/util.ts"; 5 | import { App } from "./router.ts"; 6 | 7 | /** The `suspenseMarker` to mark the susponse rendering is starting. */ 8 | const suspenseMarker = `data:text/javascript;/** suspense marker **/`; 9 | 10 | export const render = (ctx: SSRContext): Promise => { 11 | if (ctx.modules.length === 0 || ctx.modules.at(-1)?.url.pathname === "/_404") { 12 | ctx.setStatus(404); 13 | } 14 | // support suspense rendering in server-side 15 | ctx.setSuspenseMarker("script", (el) => { 16 | if (el.getAttribute("src") === suspenseMarker) { 17 | el.remove(); 18 | return true; 19 | } 20 | return false; 21 | }); 22 | return renderToReadableStream( 23 | createElement(App, { ssrContext: ctx }), 24 | { 25 | ...pick(ctx, "signal", "nonce"), 26 | bootstrapScripts: [suspenseMarker], 27 | }, 28 | ); 29 | }; 30 | 31 | export type PluginOptions = { 32 | ssr?: boolean | SSROptions; 33 | }; 34 | 35 | export default function ReactPlugin(options?: PluginOptions): Plugin { 36 | return { 37 | name: "react", 38 | setup(aleph) { 39 | if (Deno.args.includes("--dev")) { 40 | // Enable react refresh 41 | Deno.env.set("REACT_REFRESH", "true"); 42 | } 43 | if (options?.ssr) { 44 | aleph.ssr = { 45 | ...(isPlainObject(options.ssr) ? options.ssr : {}), 46 | render, 47 | }; 48 | } 49 | }, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /plugins/mdx.ts: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { compile, type CompileOptions } from "https://esm.sh/v126/@mdx-js/mdx@2.3.0"; 4 | import type { ModuleLoader, ModuleLoaderEnv, ModuleLoaderOutput, Plugin } from "../server/types.ts"; 5 | 6 | export class MDXLoader implements ModuleLoader { 7 | #options: CompileOptions; 8 | 9 | constructor(options?: CompileOptions) { 10 | this.#options = options ?? {}; 11 | } 12 | 13 | test(path: string): boolean { 14 | const exts = this.#options?.mdxExtensions ?? ["mdx"]; 15 | return exts.some((ext) => path.endsWith(`.${ext}`)); 16 | } 17 | 18 | async load( 19 | specifier: string, 20 | content: string, 21 | env: ModuleLoaderEnv, 22 | ): Promise { 23 | const ret = await compile( 24 | { path: specifier, value: content }, 25 | { 26 | jsxImportSource: this.#options.jsxImportSource ?? 27 | env.jsxConfig?.jsxImportSource, 28 | ...this.#options, 29 | providerImportSource: this.#options.providerImportSource 30 | ? env.importMap?.imports[ 31 | this.#options.providerImportSource 32 | ] ?? this.#options.providerImportSource 33 | : undefined, 34 | development: env.isDev, 35 | }, 36 | ); 37 | return { 38 | code: ret.toString(), 39 | lang: "js", 40 | }; 41 | } 42 | } 43 | 44 | export default function MdxPlugin(options?: CompileOptions): Plugin { 45 | return { 46 | name: "mdx", 47 | setup(aleph) { 48 | const exts = options?.mdxExtensions ?? ["mdx"]; 49 | aleph.loaders = [new MDXLoader(options), ...(aleph.loaders ?? [])]; 50 | aleph.router = { 51 | ...aleph.router, 52 | exts: [...exts, ...(aleph.router?.exts ?? [])], 53 | }; 54 | }, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /framework/react/context.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode, ReactPortal } from "react"; 2 | import { createContext } from "react"; 3 | 4 | export type HttpMethod = "get" | "post" | "put" | "patch" | "delete"; 5 | 6 | export type UpdateStrategy = "none" | "replace" | { 7 | optimisticUpdate?: (data: T) => T; 8 | onFailure?: (error: Error) => void; 9 | replace?: boolean; 10 | }; 11 | 12 | export type Mutation = { 13 | [key in "post" | "put" | "patch" | "delete"]: ( 14 | data?: unknown, 15 | updateStrategy?: UpdateStrategy, 16 | ) => Promise; 17 | }; 18 | 19 | export type RouterContextProps = { 20 | url: URL; 21 | params: Record; 22 | e404?: boolean; 23 | ssrHeadCollection?: string[]; 24 | createPortal?: (children: ReactNode, container: Element, key?: null | string) => ReactPortal; 25 | }; 26 | 27 | /** Context for the router. */ 28 | export const RouterContext = createContext({ 29 | url: new URL("http://localhost/"), 30 | params: {}, 31 | }); 32 | RouterContext.displayName = "RouterContext"; 33 | 34 | export type DataContextProps = { 35 | deferedData?: { current?: T }; 36 | data: T; 37 | isMutating: HttpMethod | boolean; 38 | mutation: Mutation; 39 | reload: (signal?: AbortSignal) => Promise; 40 | }; 41 | 42 | /** Context for the router data. */ 43 | export const DataContext = createContext({ 44 | data: undefined, 45 | isMutating: false, 46 | mutation: { 47 | post: () => Promise.resolve(new Response(null)), 48 | put: () => Promise.resolve(new Response(null)), 49 | patch: () => Promise.resolve(new Response(null)), 50 | delete: () => Promise.resolve(new Response(null)), 51 | }, 52 | reload: () => Promise.resolve(undefined), 53 | }); 54 | DataContext.displayName = "DataContext"; 55 | -------------------------------------------------------------------------------- /examples/react-mdx-app/style/hljs.css: -------------------------------------------------------------------------------- 1 | /* copied from https://esm.sh/highlightjs@9.16.2/styles/github.css */ 2 | 3 | .hljs { 4 | display: block; 5 | overflow-x: auto; 6 | padding: .5em; 7 | color: #333; 8 | background: #f8f8f8 9 | } 10 | 11 | .hljs-comment, 12 | .hljs-quote { 13 | color: #998; 14 | font-style: italic 15 | } 16 | 17 | .hljs-keyword, 18 | .hljs-selector-tag, 19 | .hljs-subst { 20 | color: #111; 21 | font-weight: 500 22 | } 23 | 24 | .hljs-literal, 25 | .hljs-number, 26 | .hljs-template-variable, 27 | .hljs-variable { 28 | color: teal 29 | } 30 | 31 | .hljs-doctag, 32 | .hljs-string { 33 | color: var(--theme-color) 34 | } 35 | 36 | .hljs-section, 37 | .hljs-selector-id, 38 | .hljs-title { 39 | color: var(--theme-color); 40 | font-weight: 500 41 | } 42 | 43 | .hljs-subst { 44 | font-weight: 400 45 | } 46 | 47 | .hljs-class .hljs-title, 48 | .hljs-type { 49 | color: #458; 50 | font-weight: 500 51 | } 52 | 53 | .hljs-attribute, 54 | .hljs-name, 55 | .hljs-tag { 56 | color: #999; 57 | font-weight: 400 58 | } 59 | 60 | .hljs-tag .hljs-name { 61 | color: teal; 62 | font-weight: 500 63 | } 64 | 65 | .hljs-tag .hljs-attr { 66 | color: #454545; 67 | font-weight: 500 68 | } 69 | 70 | .hljs-link, 71 | .hljs-regexp { 72 | color: #926711 73 | } 74 | 75 | .hljs-bullet, 76 | .hljs-symbol { 77 | color: var(--theme-color) 78 | } 79 | 80 | .hljs-built_in, 81 | .hljs-builtin-name { 82 | color: teal 83 | } 84 | 85 | .hljs-meta { 86 | color: #999; 87 | font-weight: 500 88 | } 89 | 90 | .hljs-deletion { 91 | background: #fdd 92 | } 93 | 94 | .hljs-addition { 95 | background: #dfd 96 | } 97 | 98 | .hljs-emphasis { 99 | font-style: italic 100 | } 101 | 102 | .hljs-strong { 103 | font-weight: 500 104 | } 105 | -------------------------------------------------------------------------------- /examples/leptos-app/dev.ts: -------------------------------------------------------------------------------- 1 | import { fromFileUrl, join } from "std/path/mod.ts"; 2 | import dev, { createWatchFsEmitter } from "aleph/dev"; 3 | 4 | const emitter = createWatchFsEmitter(); 5 | emitter.on("modify", ({ specifier }) => { 6 | if (specifier.endsWith(".rs")) { 7 | // rebuild the leptos app then restart the dev server 8 | start(); 9 | } 10 | }); 11 | 12 | let buildClientProc: Deno.Process | null = null; 13 | let buildServerProc: Deno.Process | null = null; 14 | 15 | // build the leptos app then start the dev server 16 | async function start() { 17 | const cwd = fromFileUrl(new URL(".", import.meta.url)); 18 | if (buildServerProc) { 19 | buildServerProc.kill("SIGTERM"); 20 | buildServerProc.close(); 21 | } 22 | if (buildClientProc) { 23 | buildClientProc.kill("SIGTERM"); 24 | buildClientProc.close(); 25 | } 26 | buildServerProc = Deno.run({ 27 | cmd: [ 28 | "wasm-pack", 29 | "build", 30 | "--target", 31 | "web", 32 | "--out-name", 33 | "server", 34 | "--features", 35 | "ssr", 36 | ], 37 | stdout: "inherit", 38 | stderr: "inherit", 39 | cwd, 40 | }); 41 | buildClientProc = Deno.run({ 42 | cmd: [ 43 | "wasm-pack", 44 | "build", 45 | "--target", 46 | "web", 47 | "--out-name", 48 | "client", 49 | "--features", 50 | "hydrate", 51 | ], 52 | stdout: "inherit", 53 | stderr: "inherit", 54 | cwd, 55 | }); 56 | try { 57 | await buildServerProc.status(); 58 | await buildClientProc.status(); 59 | buildServerProc.close(); 60 | buildClientProc.close(); 61 | await Deno.remove(`${cwd}/pkg/.gitignore`); 62 | // start aleph dev server 63 | dev(join(cwd, "server.ts")); 64 | } finally { 65 | buildServerProc = null; 66 | buildClientProc = null; 67 | } 68 | } 69 | 70 | start(); 71 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/dev.ts: -------------------------------------------------------------------------------- 1 | import { fromFileUrl, join } from "std/path/mod.ts"; 2 | import dev, { createWatchFsEmitter } from "aleph/dev"; 3 | 4 | const emitter = createWatchFsEmitter(); 5 | emitter.on("modify", ({ specifier }) => { 6 | if (specifier.endsWith(".rs")) { 7 | // rebuild the leptos app then restart the dev server 8 | start(); 9 | } 10 | }); 11 | 12 | let buildClientProc: Deno.Process | null = null; 13 | let buildServerProc: Deno.Process | null = null; 14 | 15 | // build the leptos app then start the dev server 16 | async function start() { 17 | const cwd = fromFileUrl(new URL(".", import.meta.url)); 18 | if (buildServerProc) { 19 | buildServerProc.kill("SIGTERM"); 20 | buildServerProc.close(); 21 | } 22 | if (buildClientProc) { 23 | buildClientProc.kill("SIGTERM"); 24 | buildClientProc.close(); 25 | } 26 | buildServerProc = Deno.run({ 27 | cmd: [ 28 | "wasm-pack", 29 | "build", 30 | "--target", 31 | "web", 32 | "--out-name", 33 | "server", 34 | "--features", 35 | "ssr", 36 | ], 37 | stdout: "inherit", 38 | stderr: "inherit", 39 | cwd, 40 | }); 41 | buildClientProc = Deno.run({ 42 | cmd: [ 43 | "wasm-pack", 44 | "build", 45 | "--target", 46 | "web", 47 | "--out-name", 48 | "client", 49 | "--features", 50 | "hydrate", 51 | ], 52 | stdout: "inherit", 53 | stderr: "inherit", 54 | cwd, 55 | }); 56 | try { 57 | await buildServerProc.status(); 58 | await buildClientProc.status(); 59 | buildServerProc.close(); 60 | buildClientProc.close(); 61 | await Deno.remove(`${cwd}/pkg/.gitignore`); 62 | // start aleph dev server 63 | dev(join(cwd, "server.ts")); 64 | } finally { 65 | buildServerProc = null; 66 | buildClientProc = null; 67 | } 68 | } 69 | 70 | start(); 71 | -------------------------------------------------------------------------------- /examples/monaco-editor/editor.ts: -------------------------------------------------------------------------------- 1 | import { editor, Uri } from "https://esm.sh/monaco-editor@0.36.1"; 2 | import "https://esm.sh/monaco-editor@0.36.1?css"; 3 | 4 | // deno-lint-ignore ban-ts-comment 5 | // @ts-ignore 6 | self.MonacoEnvironment = { 7 | async getWorker(_: unknown, label: string) { 8 | if (label === "typescript" || label === "javascript") { 9 | const { default: tsWorker } = await import( 10 | "https://esm.sh/monaco-editor@0.36.1/esm/vs/language/typescript/ts.worker?worker" 11 | ); 12 | return tsWorker(); 13 | } 14 | const { default: editorWorker } = await import( 15 | "https://esm.sh/monaco-editor@0.36.1/esm/vs/editor/editor.worker?worker" 16 | ); 17 | return editorWorker(); 18 | }, 19 | }; 20 | 21 | export function createModel(name: string, source: string) { 22 | const lang = getLanguage(name); 23 | if (!lang) { 24 | return null; 25 | } 26 | 27 | const uri = Uri.parse(`file:///src/${name}`); 28 | const model = editor.createModel(source, lang, uri); 29 | return model; 30 | } 31 | 32 | export function createEditor(container: HTMLElement, readOnly?: boolean) { 33 | return editor.create(container, { 34 | readOnly, 35 | automaticLayout: true, 36 | contextmenu: true, 37 | fontSize: 14, 38 | lineHeight: 18, 39 | lineNumbersMinChars: 2, 40 | minimap: { enabled: false }, 41 | scrollBeyondLastLine: false, 42 | smoothScrolling: true, 43 | scrollbar: { 44 | useShadows: false, 45 | verticalScrollbarSize: 10, 46 | horizontalScrollbarSize: 10, 47 | }, 48 | overviewRulerLanes: 0, 49 | }); 50 | } 51 | 52 | function getLanguage(name: string) { 53 | switch (name.slice(name.lastIndexOf(".") + 1).toLowerCase()) { 54 | case "ts": 55 | case "tsx": 56 | return "typescript"; 57 | case "js": 58 | case "jsx": 59 | return "javascript"; 60 | } 61 | return null; 62 | } 63 | -------------------------------------------------------------------------------- /examples/react-mdx-app/routes/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "About - Docs" 3 | author: ije 4 | --- 5 | 6 | import { Head } from "aleph/react" 7 | 8 | 9 | About - Docs 10 | 11 | 12 | # About 13 | 14 | **Aleph.js** (or **Aleph** or **א** or **阿莱夫**, ˈɑːlɛf) is a 15 | fullstack framework in [Deno]. Inspired by [Next.js], [Remix] and [Vite]. 16 | 17 | > The name is taken from the book [_The Aleph_] by **Jorge Luis Borges**. 18 | 19 | Aleph.js is modern framework that doesn't need **webpack** or other bundler 20 | since it uses the [ES Module] syntax during development. Every module only needs 21 | to be compiled once, when a module changes, Aleph.js just needs to re-compile 22 | that single module. There is no time wasted _re-bundling_ everytime a change is 23 | made. This, along with Hot Module Replacement (**HMR**) and **Fast Refresh**, 24 | leads to instant updates in the browser. 25 | 26 | Aleph.js uses modern tools to build your app. It transpiles code using [swc] in 27 | WASM with high performance, and bundles modules with [esbuild] at optimization 28 | time extremely fast. 29 | 30 | Aleph.js works on top of **Deno**, a _simple_, _modern_ and _secure_ runtime for 31 | JavaScript and TypeScript. All dependencies are imported using URLs, and managed 32 | by Deno cache system. No `package.json` and `node_modules` directory needed. 33 | 34 | [_The Aleph_]: http://phinnweb.org/links/literature/borges/aleph.html 35 | [ES Module]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules 36 | [deno]: https://deno.land 37 | [next.js]: https://nextjs.org 38 | [Remix]: https://remix.run 39 | [Vite]: https://vitejs.dev 40 | [swc]: https://swc.rs 41 | [esbuild]: https://github.com/evanw/esbuild 42 | 43 | ```js 44 | import React from 'https://esm.sh/react' 45 | import Logo from '../components/logo.tsx' 46 | 47 | export default function Home() { 48 | return ( 49 |
50 | 51 |

Hello World!

52 |
53 | ) 54 | } 55 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Aleph.js 2 | 3 | Welcome, and thank you for taking time in contributing to Aleph.js! You can contribute to Aleph.js in different ways: 4 | 5 | - Submit new features 6 | - Report and fix bugs 7 | - Review code 8 | - Improve our [documentation](https://github.com/alephjs/alephjs.org) 9 | 10 | ## Development Setup 11 | 12 | You will need [Deno](https://deno.land/) 1.20+. 13 | 14 | 1. Fork this repository to your own GitHub account. 15 | 2. Clone the repository to your local device. 16 | 3. Create a new branch `git checkout -b BRANCH_NAME`. 17 | 4. Change code then run the examples. 18 | 5. [Push your branch to Github after all tests passed.](#Testing) 19 | 6. Make a [pull request](https://github.com/alephjs/aleph.js/pulls). 20 | 7. Merge to master branch by our maintainers. 21 | 22 | ### Run The Examples 23 | 24 | ```bash 25 | # Run the example app in development mode 26 | deno run -A dev.ts examples/$APP/server.ts 27 | 28 | # Run the example app in production mode 29 | deno run -A examples/$APP/server.ts 30 | 31 | # Optimize the application (bundling, ssg, etc.) 32 | deno run -A examples/$APP/server.ts --build 33 | ``` 34 | 35 | **Examples**: https://github.com/alephjs/aleph.js/tree/main/examples 36 | 37 | ## Testing 38 | 39 | You can run all the testings by the following command: 40 | 41 | ```bash 42 | deno test -A 43 | ``` 44 | 45 | ## Project Structure 46 | 47 | - **/examples** examples to get started 48 | - **/framework** 49 | - **core** framework framework core 50 | - **react** framework framework for React 51 | - **/server** server core of Aleph.js 52 | - **/shared** shared libraries 53 | - **/tests** unit/integration testings 54 | 55 | ## Code Style We Followed 56 | 57 | - Double quote for string 58 | - Ends with semicolon 59 | - 2 spaces indent 60 | - Types everything 61 | - Order the imports 62 | - Remove unused code 63 | - Format code before commit 64 | 65 | ```bash 66 | deno fmt **/*.{ts,tsx} 67 | ``` 68 | 69 | ## Code of Conduct 70 | 71 | All contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Aleph.js: The Full-stack Framework in Deno.](.github/poster.svg)](https://alephjs.org) 2 | 3 |

4 | Chat 5 | Twitter 6 |

7 | 8 | > ⚠️ Not yet 1.0. Many things are subject to change. Documentation is lacking in many places. Try it out and give us 9 | > feedback! 10 | 11 | Some demo apps deployed to [Deno Deploy](https://deno.com/deploy) with the new architecture: 12 | 13 | - React App: https://aleph-hello.deno.dev/ 14 | - REST API: https://aleph-api.deno.dev/ 15 | - React 18 Suspense SSR: https://aleph-suspense-ssr.deno.dev/ 16 | - UnoCSS(tailwind): https://aleph-unocss.deno.dev/ 17 | - Monaco Editor: https://aleph-monaco-editor.deno.dev/ 18 | - Yew SSR: https://aleph-yew.deno.dev/ 19 | - Github OAuth Middleware: https://aleph-github-oauth.deno.dev/ 20 | 21 | > **Source code**: https://github.com/alephjs/aleph.js/tree/main/examples 22 | 23 | ## Real-world Apps 24 | 25 | - Deno Deploy: https://dash.deno.com 26 | - Meet Me: https://meet-me.deno.dev ([source](https://github.com/denoland/meet-me)) 27 | 28 | ## Get started 29 | 30 | Initialize a new project, you can pick a start template with `--template` flag, available templates: 31 | `[react, react-mdx, api, yew]` 32 | 33 | ```bash 34 | deno run -A -r https://alephjs.org/init.ts 35 | ``` 36 | 37 | after `init`, you can run the app with deno tasks: 38 | 39 | ```bash 40 | # go to the app root created by the `init` 41 | cd APPDIR 42 | 43 | # run the app in devlopment mode 44 | deno task dev 45 | 46 | # run the app in production mode 47 | deno task start 48 | ``` 49 | 50 | ## Documentation 51 | 52 | > The new docs site is working in progress: https://aleph.deno.dev 53 | > ([PR](https://github.com/alephjs/alephjs.org/pull/58)). You can join the Aleph.js 54 | > [Discord](https://discord.com/invite/pWGdS7sAqD) to get helps. 55 | -------------------------------------------------------------------------------- /framework/react/portal.ts: -------------------------------------------------------------------------------- 1 | import type { ReactNode, ReactPortal } from "react"; 2 | import { useCallback, useContext, useEffect, useState } from "react"; 3 | import { RouterContext } from "./context.ts"; 4 | 5 | /** 6 | * The `usePortal` hook to create a portal node. 7 | * Please ensure to pass the `React.createPortal` in `Router` props. 8 | * 9 | * ```jsx 10 | * function Modal() { 11 | * const portal = usePortal({ type: "dialog", preventScroll: true }); 12 | * return portal(

Hello portal!

); 13 | * } 14 | * ``` 15 | */ 16 | export function usePortal( 17 | props?: { key?: string | null; className?: string; lockScroll?: boolean; type?: "div" | "dialog" }, 18 | ): (children: ReactNode) => ReactPortal | null { 19 | const { className, lockScroll, type, key } = props || {}; 20 | const { createPortal } = useContext(RouterContext); 21 | const [portalRoot, setPortalRoot] = useState(null); 22 | 23 | useEffect(() => { 24 | const { body } = document; 25 | const portalRoot = document.createElement(type ?? "div"); 26 | if (key) { 27 | portalRoot.id = key; 28 | } 29 | if (className) { 30 | portalRoot.className = className; 31 | } 32 | if (lockScroll) { 33 | body.style.overflow = "hidden"; 34 | } 35 | body.appendChild(portalRoot); 36 | 37 | if (type) { 38 | Object.assign(portalRoot.style, { 39 | width: "100vw", 40 | height: "100vh", 41 | backgroundColor: "transparent", 42 | }); 43 | /* @ts-ignore */ 44 | portalRoot.showModal?.(); 45 | } 46 | 47 | setPortalRoot(portalRoot); 48 | 49 | return () => { 50 | body.removeChild(portalRoot); 51 | if (lockScroll) { 52 | body.style.overflow = ""; 53 | } 54 | setPortalRoot(null); 55 | }; 56 | }, [key, className, lockScroll, type]); 57 | 58 | return useCallback( 59 | (chlidren: ReactNode) => { 60 | if (!portalRoot) { 61 | return null; 62 | } 63 | if (!createPortal) { 64 | throw new Error("Please ensure to pass the `React.createPortal` in `Router` props"); 65 | } 66 | return createPortal(chlidren, portalRoot, key); 67 | }, 68 | [portalRoot, createPortal, key], 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /tests/shared_util_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "std/testing/asserts.ts"; 2 | import { cleanPath, isLikelyHttpURL, prettyBytes, splitBy, trimPrefix, trimSuffix } from "../shared/util.ts"; 3 | 4 | Deno.test("[unit] shared/util.ts", async (t) => { 5 | await t.step("isLikelyHttpURL", () => { 6 | assertEquals(isLikelyHttpURL("https://deno.land"), true); 7 | assertEquals(isLikelyHttpURL("http://deno.land"), true); 8 | assertEquals(isLikelyHttpURL("//deno.land"), false); 9 | assertEquals(isLikelyHttpURL("file:///deno.land"), false); 10 | assertEquals(isLikelyHttpURL("www.deno.land"), false); 11 | }); 12 | 13 | await t.step("trimPrefix", () => { 14 | assertEquals(trimPrefix("foobar", "foo"), "bar"); 15 | assertEquals(trimPrefix("foobar", "baz"), "foobar"); 16 | assertEquals(trimSuffix("foobar", "bar"), "foo"); 17 | assertEquals(trimSuffix("foobar", "baz"), "foobar"); 18 | }); 19 | 20 | await t.step("splitBy", () => { 21 | // test `splitBy` 22 | assertEquals(splitBy("/app.tsx", "."), ["/app", "tsx"]); 23 | assertEquals(splitBy("foo.bar.", "."), ["foo", "bar."]); 24 | assertEquals(splitBy("foobar.", "."), ["foobar", ""]); 25 | assertEquals(splitBy(".foobar.", "."), ["", "foobar."]); 26 | assertEquals(splitBy("foobar", "."), ["foobar", ""]); 27 | }); 28 | 29 | await t.step("prettyBytes", () => { 30 | assertEquals(prettyBytes(1000), "1000B"); 31 | assertEquals(prettyBytes(1024), "1KB"); 32 | assertEquals(prettyBytes(2048), "2KB"); 33 | assertEquals(prettyBytes(3000), "2.93KB"); 34 | assertEquals(prettyBytes(1024 ** 2), "1MB"); 35 | assertEquals(prettyBytes(1024 ** 3), "1GB"); 36 | assertEquals(prettyBytes(1024 ** 4), "1TB"); 37 | assertEquals(prettyBytes(1024 ** 5), "1PB"); 38 | }); 39 | 40 | await t.step("cleanPath", () => { 41 | assertEquals(cleanPath("./"), "/"); 42 | assertEquals(cleanPath("./a/./b/./c/."), "/a/b/c"); 43 | assertEquals(cleanPath("../"), "/"); 44 | assertEquals(cleanPath("../a/b/c"), "/a/b/c"); 45 | assertEquals(cleanPath("/a/../b/c"), "/b/c"); 46 | assertEquals(cleanPath("/a/b/../c"), "/a/c"); 47 | assertEquals(cleanPath("\\a\\b\\c"), "/a/b/c"); 48 | assertEquals(cleanPath("\\a\\b\\.\\..\\c"), "/a/c"); 49 | assertEquals(cleanPath("//a//b//c//"), "/a/b/c"); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /examples/github-oauth-middleware/middlewares/oauth.ts: -------------------------------------------------------------------------------- 1 | export type GithubUser = { 2 | id: number; 3 | login: string; 4 | avatar_url: string; 5 | name: string; 6 | }; 7 | 8 | export type GithubOauthConfig = { 9 | clientId?: string; 10 | clientSecret?: string; 11 | }; 12 | 13 | export class GithubOauth implements Middleware { 14 | readonly name: string = "github-oauth"; 15 | 16 | #config: GithubOauthConfig; 17 | 18 | constructor(config: GithubOauthConfig) { 19 | this.#config = config; 20 | } 21 | 22 | async fetch(req: Request, ctx: Context) { 23 | const { pathname, searchParams } = new URL(req.url); 24 | const session = await ctx.getSession<{ user: GithubUser }>(); 25 | 26 | if (pathname === "/logout") { 27 | await session.end(); 28 | return session.redirect("/"); 29 | } 30 | 31 | if (!session.store?.user) { 32 | const code = searchParams.get("code"); 33 | if (!code) { 34 | const loginUrl = 35 | `https://github.com/login/oauth/authorize?client_id=${this.#config.clientId}&scope=read:user+user:email`; 36 | return new Response("Not logged in", { 37 | status: 302, 38 | headers: { Location: loginUrl }, 39 | }); 40 | } 41 | 42 | const ret: { access_token: string; error?: string } = await fetch( 43 | "https://github.com/login/oauth/access_token", 44 | { 45 | method: "POST", 46 | body: JSON.stringify({ 47 | client_id: this.#config.clientId, 48 | client_secret: this.#config.clientSecret, 49 | state: searchParams.get("state") || undefined, 50 | code, 51 | }), 52 | headers: { 53 | "Content-Type": "application/json", 54 | "Accept": "application/json", 55 | }, 56 | }, 57 | ).then((res) => res.json()); 58 | if (ret.error) { 59 | return new Response(ret.error, { status: 500 }); 60 | } 61 | 62 | const user: GithubUser = await fetch("https://api.github.com/user", { 63 | headers: { 64 | "Authorization": `token ${ret.access_token}`, 65 | "Accept": "application/json", 66 | }, 67 | }).then((res) => res.json()); 68 | 69 | await session.update({ user }); 70 | return session.redirect("/"); 71 | } 72 | 73 | ctx.user = session.store.user; 74 | return ctx.next(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /framework/core/redirect.ts: -------------------------------------------------------------------------------- 1 | import { isFilledString } from "../../shared/util.ts"; 2 | import events from "./events.ts"; 3 | import { matchRoutes, type Router } from "./router.ts"; 4 | 5 | let router: Router | null = null; 6 | let preRedirect: URL | null = null; 7 | 8 | const onrouter = (e: Record) => { 9 | events.off("router", onrouter); 10 | if (preRedirect) { 11 | events.emit("popstate", { type: "popstate", url: preRedirect }); 12 | preRedirect = null; 13 | } 14 | router = e.router as Router; 15 | }; 16 | events.on("router", onrouter); 17 | 18 | export function redirect(href: string, replace?: boolean) { 19 | const { history, location } = globalThis; 20 | if (!isFilledString(href) || !history || !location) { 21 | return; 22 | } 23 | 24 | if (href.startsWith("file://") || href.startsWith("mailto:") || href.startsWith("data:")) { 25 | location.href = href; 26 | return; 27 | } 28 | 29 | const url = new URL(href, location.href); 30 | if (url.href === location.href) { 31 | return; 32 | } 33 | if (url.host !== location.host) { 34 | location.href = href; 35 | return; 36 | } 37 | 38 | if (replace) { 39 | history.replaceState(null, "", url); 40 | } else { 41 | history.pushState(null, "", url); 42 | } 43 | 44 | if (router) { 45 | if (!Reflect.has(globalThis, "navigation")) { 46 | events.emit("popstate", { type: "popstate", url }); 47 | } 48 | } else { 49 | preRedirect = url; 50 | } 51 | } 52 | 53 | const prefetched = new Set(); 54 | 55 | /** prefetch module using `` */ 56 | export const prefetchModule = (url: URL) => { 57 | if (prefetched.has(url.href)) { 58 | return; 59 | } 60 | prefetched.add(url.href); 61 | if (!router) { 62 | throw new Error("router is not ready."); 63 | } 64 | const deploymentId = window.document.body.getAttribute("data-deployment-id"); 65 | const q = deploymentId ? `?v=${deploymentId}` : ""; 66 | const matches = matchRoutes(url, router); 67 | matches.map(([_, meta]) => { 68 | if (!document.querySelector(`link[data-module-id="${meta.filename}"]`)) { 69 | const link = document.createElement("link"); 70 | link.setAttribute("rel", "modulepreload"); 71 | link.setAttribute("href", meta.filename.slice(1) + q); 72 | link.setAttribute("data-module-id", meta.filename); 73 | document.head.appendChild(link); 74 | } 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /examples/leptos-app/README.md: -------------------------------------------------------------------------------- 1 | # Leptos Counter Isomorphic Example 2 | 3 | This example demonstrates how to use a function isomorphically, to run a server side function from the browser and 4 | receive a result. 5 | 6 | ## Client Side Rendering 7 | 8 | For this example the server must store the counter state since it can be modified by many users. This means it is not 9 | possible to produce a working CSR-only version as a non-static server is required. 10 | 11 | ## Server Side Rendering with cargo-leptos 12 | 13 | cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides 14 | automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about 15 | it [here](https://github.com/akesson/cargo-leptos) 16 | 17 | 1. Install cargo-leptos 18 | 19 | ```bash 20 | cargo install --locked cargo-leptos 21 | ``` 22 | 23 | 2. Build the site in watch mode, recompiling on file changes 24 | 25 | ```bash 26 | cargo leptos watch 27 | ``` 28 | 29 | Open browser on [http://localhost:3000/](http://localhost:3000/) 30 | 31 | 3. When ready to deploy, run 32 | 33 | ```bash 34 | cargo leptos build --release 35 | ``` 36 | 37 | ## Server Side Rendering without cargo-leptos 38 | 39 | To run it as a server side app with hydration, you'll need to have wasm-pack installed. 40 | 41 | 0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. For examples with CSS you also want to 42 | change the path of the `` component in the root component to point towards the CSS file in the root. 43 | This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are 44 | no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want 45 | to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings 46 | instead of cargo-leptos. If you do so, your file/folder names cannot include dashes. 47 | 1. Install wasm-pack 48 | 49 | ```bash 50 | cargo install wasm-pack 51 | ``` 52 | 53 | 2. Build the Webassembly used to hydrate the HTML from the server 54 | 55 | ```bash 56 | wasm-pack build --target=web --debug --no-default-features --features=hydrate 57 | ``` 58 | 59 | 3. Run the server to serve the Webassembly, JS, and HTML 60 | 61 | ```bash 62 | cargo run --no-default-features --features=ssr 63 | ``` 64 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/README.md: -------------------------------------------------------------------------------- 1 | # Leptos Counter Isomorphic Example 2 | 3 | This example demonstrates how to use a function isomorphically, to run a server side function from the browser and 4 | receive a result. 5 | 6 | ## Client Side Rendering 7 | 8 | For this example the server must store the counter state since it can be modified by many users. This means it is not 9 | possible to produce a working CSR-only version as a non-static server is required. 10 | 11 | ## Server Side Rendering with cargo-leptos 12 | 13 | cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides 14 | automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about 15 | it [here](https://github.com/akesson/cargo-leptos) 16 | 17 | 1. Install cargo-leptos 18 | 19 | ```bash 20 | cargo install --locked cargo-leptos 21 | ``` 22 | 23 | 2. Build the site in watch mode, recompiling on file changes 24 | 25 | ```bash 26 | cargo leptos watch 27 | ``` 28 | 29 | Open browser on [http://localhost:3000/](http://localhost:3000/) 30 | 31 | 3. When ready to deploy, run 32 | 33 | ```bash 34 | cargo leptos build --release 35 | ``` 36 | 37 | ## Server Side Rendering without cargo-leptos 38 | 39 | To run it as a server side app with hydration, you'll need to have wasm-pack installed. 40 | 41 | 0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. For examples with CSS you also want to 42 | change the path of the `` component in the root component to point towards the CSS file in the root. 43 | This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are 44 | no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want 45 | to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings 46 | instead of cargo-leptos. If you do so, your file/folder names cannot include dashes. 47 | 1. Install wasm-pack 48 | 49 | ```bash 50 | cargo install wasm-pack 51 | ``` 52 | 53 | 2. Build the Webassembly used to hydrate the HTML from the server 54 | 55 | ```bash 56 | wasm-pack build --target=web --debug --no-default-features --features=hydrate 57 | ``` 58 | 59 | 3. Run the server to serve the Webassembly, JS, and HTML 60 | 61 | ```bash 62 | cargo run --no-default-features --features=ssr 63 | ``` 64 | -------------------------------------------------------------------------------- /tests/server_helpers_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "std/testing/asserts.ts"; 2 | import { MagicString, restoreUrl, toLocalPath } from "../server/helpers.ts"; 3 | import { parseDeps } from "../server/deps.ts"; 4 | 5 | Deno.test("server/helper.ts", async (t) => { 6 | await t.step("toLocalPath", () => { 7 | assertEquals(toLocalPath("https://foo.com/lib@0.1.0?action"), "/-/foo.com/lib@0.1.0?action"); 8 | assertEquals(toLocalPath("https://deno.land/x/aleph@0.1.0/"), "/-/deno.land/x/aleph@0.1.0"); 9 | assertEquals(toLocalPath("http://foo.com/bar?lang=us-en"), "/-/http_foo.com/bar?lang=us-en"); 10 | assertEquals(toLocalPath("http://foo.com:8080/bar"), "/-/http_foo.com_8080/bar"); 11 | assertEquals(toLocalPath("file://foo/bar/"), "file://foo/bar/"); 12 | assertEquals(toLocalPath("/foo/bar/"), "/foo/bar/"); 13 | }); 14 | 15 | await t.step("restoreUrl", () => { 16 | assertEquals(restoreUrl("/-/foo.com/lib@0.1.0?action"), "https://foo.com/lib@0.1.0?action"); 17 | assertEquals(restoreUrl("/-/deno.land/x/aleph@0.1.0"), "https://deno.land/x/aleph@0.1.0"); 18 | assertEquals(restoreUrl("/-/http_foo.com/bar?lang=us-en"), "http://foo.com/bar?lang=us-en"); 19 | assertEquals(restoreUrl("/-/http_foo.com_8080/bar"), "http://foo.com:8080/bar"); 20 | }); 21 | 22 | await t.step("MagicString", async () => { 23 | const code = `// Deno 🦕 App (应用) 24 | import React from "htts://esm.sh/react"; 25 | import foo from "./foo.js"; 26 | import { bar } from './bar.js'; 27 | const baz = await import('./baz.js'); 28 | const worker = new Worker('./worker.js', { type: 'module' }); 29 | `; 30 | const overwritedCode = `// Deno 🦕 App (应用) 31 | import React from "htts://esm.sh/react?dev"; 32 | import foo from "./foo.js?v=123"; 33 | import { bar } from "./bar.js?v=123"; 34 | const baz = await import("./baz.js?v=123"); 35 | const worker = new Worker("./worker.js?v=123", { type: 'module' }); 36 | `; 37 | const deps = await parseDeps("./app.js", code); 38 | const m = new MagicString(code); 39 | for (const dep of deps) { 40 | if (dep.loc) { 41 | let url = dep.specifier; 42 | if (url.startsWith("htts://esm.sh/")) { 43 | url += "?dev"; 44 | } else { 45 | url += "?v=123"; 46 | } 47 | m.overwrite(dep.loc.start - 1, dep.loc.end - 1, `"${url}"`); 48 | } 49 | } 50 | assertEquals(m.toString(), overwritedCode); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tests/integration_api_app_test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "std/testing/asserts.ts"; 2 | import { mockFormData, MockServer } from "aleph/server/mock.ts"; 3 | 4 | Deno.test("[integration] examples/api-app", async (t) => { 5 | const api = new MockServer({ 6 | appDir: "./examples/api-app", 7 | router: { 8 | glob: "./routes/**/*.ts", 9 | }, 10 | origin: "https://api.example.com", 11 | }); 12 | 13 | await t.step("GET /", async () => { 14 | const res = await api.fetch("/"); 15 | assertEquals(res.status, 200); 16 | assertEquals((await res.json()).users_url, "https://api.example.com/users"); 17 | }); 18 | 19 | await t.step("GET /users", async () => { 20 | const res = await api.fetch("/users"); 21 | assertEquals(res.status, 200); 22 | assertEquals((await res.json()).length, 4); 23 | }); 24 | 25 | await t.step("POST /users", async () => { 26 | const res = await api.fetch("/users", { method: "POST", body: mockFormData({ "name": "saul" }) }); 27 | const ret = await res.json(); 28 | assertEquals(res.status, 200); 29 | assertEquals(ret.uid, 5); 30 | assertEquals(ret.name, "saul"); 31 | 32 | const res2 = await api.fetch("/users"); 33 | const ret2 = await res2.json(); 34 | assertEquals(res2.status, 200); 35 | assertEquals(ret2.length, 5); 36 | assertEquals(ret2.at(-1).uid, 5); 37 | assertEquals(ret2.at(-1).name, "saul"); 38 | }); 39 | 40 | await t.step("PATCH /users/5", async () => { 41 | const res = await api.fetch("/users/5", { method: "PATCH", body: mockFormData({ "name": "saul goodman" }) }); 42 | const ret = await res.json(); 43 | assertEquals(res.status, 200); 44 | assertEquals(ret.uid, 5); 45 | assertEquals(ret.name, "saul goodman"); 46 | }); 47 | 48 | await t.step("GET /users/5", async () => { 49 | const res = await api.fetch("/users/5"); 50 | const ret = await res.json(); 51 | assertEquals(res.status, 200); 52 | assertEquals(ret.uid, 5); 53 | assertEquals(ret.name, "saul goodman"); 54 | }); 55 | 56 | await t.step("DELETE /users/5", async () => { 57 | const res = await api.fetch("/users/5", { method: "DELETE" }); 58 | const ret = await res.json(); 59 | assertEquals(res.status, 200); 60 | assertEquals(ret.uid, 5); 61 | assertEquals(ret.name, "saul goodman"); 62 | }); 63 | 64 | await t.step("GET /users/5", async () => { 65 | const res = await api.fetch("/users/5"); 66 | assertEquals(res.status, 404); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /server/mod.ts: -------------------------------------------------------------------------------- 1 | import { serve as stdServe, serveTls } from "./deps.ts"; 2 | import { createHandler } from "./handler.ts"; 3 | import log, { type LevelName } from "./log.ts"; 4 | import { build } from "./build.ts"; 5 | import { watch } from "./dev.ts"; 6 | import { getAppDir } from "./helpers.ts"; 7 | import type { AlephConfig, ServeInit } from "./types.ts"; 8 | export type { Context, Middleware } from "./types.ts"; 9 | 10 | /** The options for Aleph.js server. */ 11 | export type ServeOptions = AlephConfig & Omit; 12 | 13 | /** Start the Aleph.js server. */ 14 | export async function serve(options?: ServeOptions): Promise { 15 | const isDev = Deno.args.includes("--dev"); 16 | const { hostname, port, signal, onListen: _onListen, plugins, ...config } = options ?? {}; 17 | 18 | // use plugins 19 | if (plugins) { 20 | for (const plugin of plugins) { 21 | try { 22 | await plugin.setup(config, { isDev }); 23 | log.debug(`plugin ${plugin.name ?? "Unnamed"} setup`); 24 | } catch (err) { 25 | log.fatal(`[plugin:${plugin.name}] ${err.message}`); 26 | } 27 | } 28 | } 29 | 30 | // inject the config to global 31 | Reflect.set(globalThis, "__ALEPH_CONFIG", config); 32 | 33 | const handler = createHandler(config); 34 | 35 | // build the app for production 36 | if (Deno.args.includes("--build")) { 37 | return build(handler); 38 | } 39 | 40 | // watch file changes in development mode 41 | if (isDev) { 42 | watch(getAppDir(), config.router?.onChange); 43 | } 44 | 45 | const { tls } = config; 46 | const onListen = (arg: { port: number; hostname: string }) => { 47 | const origin = `${tls ? "https" : "http"}://${hostname ?? "localhost"}:${arg.port}`; 48 | Reflect.set(globalThis, "__ALEPH_SERVER_ORIGIN", origin); 49 | log.info(`Server ready on ${origin}`); 50 | _onListen?.(arg); 51 | }; 52 | const serveOptions = { hostname, port, signal, onListen }; 53 | const flagPort = Number(Deno.args.join(" ").match(/--port=(\d+)/)?.[1]); 54 | if (flagPort) { 55 | serveOptions.port = flagPort; 56 | } 57 | if (tls) { 58 | return serveTls(handler, { ...tls, ...serveOptions }); 59 | } 60 | return stdServe(handler, serveOptions); 61 | } 62 | 63 | /** Set the log level. */ 64 | export function setLogLevel(level: LevelName) { 65 | log.setLevel(level); 66 | } 67 | 68 | // set log level to debug when debug aleph.js itself. 69 | if (import.meta.url.startsWith("file:")) { 70 | log.setLevel("debug"); 71 | } 72 | -------------------------------------------------------------------------------- /examples/leptos-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leptos-app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | broadcaster = "1" 11 | console_log = "0.2" 12 | console_error_panic_hook = "0.1" 13 | serde = { version = "1", features = ["derive"] } 14 | futures = "0.3" 15 | cfg-if = "1" 16 | lazy_static = "1" 17 | leptos = { version = "0.1.3", default-features = false, features = [ 18 | "serde", 19 | ] } 20 | leptos_meta = { version = "0.1.3", default-features = false } 21 | leptos_router = { version = "0.1.3", default-features = false } 22 | log = "0.4" 23 | simple_logger = "4.0.0" 24 | gloo-net = { git = "https://github.com/rustwasm/gloo" } 25 | 26 | [features] 27 | default = [] 28 | hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] 29 | ssr = [ 30 | "leptos/ssr", 31 | "leptos_meta/ssr", 32 | "leptos_router/ssr", 33 | ] 34 | stable = ["leptos/stable", "leptos_router/stable"] 35 | 36 | [package.metadata.cargo-all-features] 37 | denylist = ["stable"] 38 | skip_feature_sets = [["ssr", "hydrate"]] 39 | 40 | [package.metadata.leptos] 41 | # The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. 42 | # When NOT using cargo-leptos this must be updated to "." or the counters will not work. The above warning still applies if you do switch to cargo-leptos later. 43 | site-root = "target/site" 44 | # The site-root relative folder where all compiled output (JS, WASM and CSS) is written 45 | # Defaults to pkg 46 | site-pkg-dir = "pkg" 47 | # The browserlist query used for optimizing the CSS. 48 | browserquery = "defaults" 49 | # Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head 50 | watch = false 51 | # The environment Leptos will run in, usually either "DEV" or "PROD" 52 | env = "DEV" 53 | # The features to use when compiling the bin target 54 | # 55 | # Optional. Can be over-ridden with the command line parameter --bin-features 56 | bin-features = ["hydrate", "ssr"] 57 | 58 | # If the --no-default-features flag should be used when compiling the bin target 59 | # 60 | # Optional. Defaults to false. 61 | bin-default-features = false 62 | 63 | # The features to use when compiling the lib target 64 | # 65 | # Optional. Can be over-ridden with the command line parameter --lib-features 66 | lib-features = ["hydrate", "ssr"] 67 | 68 | # If the --no-default-features flag should be used when compiling the lib target 69 | # 70 | # Optional. Defaults to false. 71 | lib-default-features = false 72 | -------------------------------------------------------------------------------- /examples/with-unocss/leptos-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "leptos-app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | broadcaster = "1" 11 | console_log = "0.2" 12 | console_error_panic_hook = "0.1" 13 | serde = { version = "1", features = ["derive"] } 14 | futures = "0.3" 15 | cfg-if = "1" 16 | lazy_static = "1" 17 | leptos = { version = "0.1.3", default-features = false, features = [ 18 | "serde", 19 | ] } 20 | leptos_meta = { version = "0.1.3", default-features = false } 21 | leptos_router = { version = "0.1.3", default-features = false } 22 | log = "0.4" 23 | simple_logger = "4.0.0" 24 | gloo-net = { git = "https://github.com/rustwasm/gloo" } 25 | 26 | [features] 27 | default = [] 28 | hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] 29 | ssr = [ 30 | "leptos/ssr", 31 | "leptos_meta/ssr", 32 | "leptos_router/ssr", 33 | ] 34 | stable = ["leptos/stable", "leptos_router/stable"] 35 | 36 | [package.metadata.cargo-all-features] 37 | denylist = ["stable"] 38 | skip_feature_sets = [["ssr", "hydrate"]] 39 | 40 | [package.metadata.leptos] 41 | # The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. 42 | # When NOT using cargo-leptos this must be updated to "." or the counters will not work. The above warning still applies if you do switch to cargo-leptos later. 43 | site-root = "target/site" 44 | # The site-root relative folder where all compiled output (JS, WASM and CSS) is written 45 | # Defaults to pkg 46 | site-pkg-dir = "pkg" 47 | # The browserlist query used for optimizing the CSS. 48 | browserquery = "defaults" 49 | # Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head 50 | watch = false 51 | # The environment Leptos will run in, usually either "DEV" or "PROD" 52 | env = "DEV" 53 | # The features to use when compiling the bin target 54 | # 55 | # Optional. Can be over-ridden with the command line parameter --bin-features 56 | bin-features = ["hydrate", "ssr"] 57 | 58 | # If the --no-default-features flag should be used when compiling the bin target 59 | # 60 | # Optional. Defaults to false. 61 | bin-default-features = false 62 | 63 | # The features to use when compiling the lib target 64 | # 65 | # Optional. Can be over-ridden with the command line parameter --lib-features 66 | lib-features = ["hydrate", "ssr"] 67 | 68 | # If the --no-default-features flag should be used when compiling the lib target 69 | # 70 | # Optional. Defaults to false. 71 | lib-default-features = false 72 | -------------------------------------------------------------------------------- /examples/with-unocss/react-app/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { Head, Link } from "aleph/react"; 2 | 3 | const externalLinks = [ 4 | ["Get Started", "https://alephjs.org/docs/get-started"], 5 | ["Docs", "https://alephjs.org/docs"], 6 | ["Github", "https://github.com/alephjs/aleph.js"], 7 | ]; 8 | 9 | export default function Index() { 10 | return ( 11 |
17 | 18 | Aleph.js 19 | 20 | 21 |

22 | 23 |

24 |

25 | The Fullstack Framework in Deno. 26 |

27 |

28 | Aleph.js gives you the best developer experience for building web applications
{" "} 29 | with modern toolings. 30 |

31 |
32 | {externalLinks.map(([text, href]) => ( 33 | 39 | {text} 40 | 41 | 45 | 46 | 47 | ))} 48 |
49 | 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /server/media_type.ts: -------------------------------------------------------------------------------- 1 | import { splitBy, trimPrefix } from "../shared/util.ts"; 2 | 3 | // MIME types for web 4 | const mimeTypes: Record = { 5 | // application 6 | "application/javascript": ["js", "mjs"], 7 | "application/typescript": ["ts", "mts"], 8 | "application/wasm": ["wasm"], 9 | "application/json": ["json", "map"], 10 | "application/jsonc": ["jsonc"], 11 | "application/json5": ["json5"], 12 | "application/pdf": ["pdf"], 13 | "application/xml": ["xml", "plist", "tmLanguage", "tmTheme"], 14 | "application/zip": ["zip"], 15 | "application/gzip": ["gz"], 16 | "application/tar": ["tar"], 17 | "application/tar+gzip": ["tar.gz", "tgz"], 18 | // text 19 | "text/html": ["html", "htm"], 20 | "text/markdown": ["md", "markdown"], 21 | "text/mdx": ["mdx"], 22 | "text/jsx": ["jsx"], 23 | "text/tsx": ["tsx"], 24 | "text/vue": ["vue"], 25 | "text/svelte": ["svelte"], 26 | "text/css": ["css"], 27 | "text/less": ["less"], 28 | "text/sass": ["sass", "scss"], 29 | "text/stylus": ["stylus", "styl"], 30 | "text/csv": ["csv"], 31 | "text/yaml": ["yaml", "yml"], 32 | "text/plain": ["txt", "glsl"], 33 | // font 34 | "font/ttf": ["ttf"], 35 | "font/otf": ["otf"], 36 | "font/woff": ["woff"], 37 | "font/woff2": ["woff2"], 38 | "font/collection": ["ttc"], 39 | // image 40 | "image/jpeg": ["jpg", "jpeg"], 41 | "image/png": ["png"], 42 | "image/apng": ["apng"], 43 | "image/gif": ["gif"], 44 | "image/webp": ["webp"], 45 | "image/avif": ["avif"], 46 | "image/svg+xml": ["svg", "svgz"], 47 | "image/x-icon": ["ico"], 48 | // audio 49 | "audio/mp4": ["m4a"], 50 | "audio/mpeg": ["mp3", "m3a"], 51 | "audio/ogg": ["ogg", "oga"], 52 | "audio/wav": ["wav"], 53 | "audio/webm": ["weba"], 54 | // video 55 | "video/mp4": ["mp4", "m4v"], 56 | "video/ogg": ["ogv"], 57 | "video/webm": ["webm"], 58 | "video/x-matroska": ["mkv"], 59 | // shader 60 | "x-shader/x-fragment": ["frag"], 61 | "x-shader/x-vertex": ["vert"], 62 | }; 63 | 64 | const typesMap = Object.entries(mimeTypes).reduce((map, [contentType, exts]) => { 65 | exts.forEach((ext) => map.set(ext, contentType)); 66 | return map; 67 | }, new Map()); 68 | 69 | /** register a new type */ 70 | export function registerType(ext: string, contentType: string) { 71 | typesMap.set(trimPrefix(ext, "."), contentType); 72 | } 73 | 74 | /** get the content type by file name */ 75 | export function getContentType(filename: string): string { 76 | let [prefix, ext] = splitBy(filename, ".", true); 77 | if (ext === "gz" && prefix.endsWith(".tar")) { 78 | ext = "tar.gz"; 79 | } 80 | return typesMap.get(ext) ?? "application/octet-stream"; 81 | } 82 | -------------------------------------------------------------------------------- /examples/with-unocss/yew-app/src/routes/index.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | use yew_router::prelude::*; 3 | 4 | use crate::routes::Route; 5 | 6 | #[function_component] 7 | pub fn Index() -> Html { 8 | let icon = html! { 9 | 14 | 18 | 19 | }; 20 | 21 | html! { 22 |
26 |

27 | 28 | 29 |

30 |

{"The Fullstack Framework in Deno."}

31 |

32 | {"Aleph.js"} 33 | {" gives you the best developer experience for building web applications"} 34 |
35 | {"with modern toolings."} 36 | {"."} 37 |

38 | 64 | 72 |
73 | } 74 | } 75 | -------------------------------------------------------------------------------- /server/session.ts: -------------------------------------------------------------------------------- 1 | import { cookieHeader } from "./helpers.ts"; 2 | import type { Session, SessionOptions, SessionStorage } from "./types.ts"; 3 | 4 | export class MemorySessionStorage implements SessionStorage { 5 | #store: Map, number]> = new Map(); 6 | 7 | get(sid: string): Promise | undefined> { 8 | const [data, expires] = this.#store.get(sid) ?? [undefined, 0]; 9 | if (expires > 0 && Date.now() > expires) { 10 | this.#store.delete(sid); 11 | return Promise.resolve(undefined); 12 | } 13 | return Promise.resolve(data); 14 | } 15 | 16 | set(sid: string, data: Record, expires: number): Promise { 17 | this.#store.set(sid, [data, expires]); 18 | return Promise.resolve(); 19 | } 20 | 21 | delete(sid: string): Promise { 22 | this.#store.delete(sid); 23 | return Promise.resolve(); 24 | } 25 | } 26 | 27 | const defaultSessionStorage = new MemorySessionStorage(); 28 | 29 | export class SessionImpl> implements Session { 30 | #id: string; 31 | #options: SessionOptions; 32 | #store: StoreType | undefined; 33 | #storage: SessionStorage; 34 | 35 | constructor(id: string, options: SessionOptions = {}) { 36 | this.#id = id; 37 | this.#options = options; 38 | this.#storage = options.storage ?? defaultSessionStorage; 39 | } 40 | 41 | get id(): string { 42 | return this.#id; 43 | } 44 | 45 | get store(): StoreType | undefined { 46 | return this.#store; 47 | } 48 | 49 | async init(): Promise { 50 | this.#store = (await this.#storage.get(this.#id)) as StoreType | undefined; 51 | } 52 | 53 | async update( 54 | store: StoreType | ((prev: StoreType | undefined) => StoreType), 55 | ): Promise { 56 | if (typeof store !== "object" && typeof store !== "function") { 57 | throw new Error("store must be a valid object or a function"); 58 | } 59 | 60 | let nextStore: StoreType | undefined; 61 | if (typeof store === "function") { 62 | nextStore = store(this.#store); 63 | } else { 64 | nextStore = store; 65 | } 66 | 67 | // save the new store 68 | await this.#storage.set(this.#id, nextStore, Date.now() + 1000 * (this.#options.maxAge ?? 1800)); 69 | this.#store = nextStore; 70 | } 71 | 72 | async end(): Promise { 73 | if (!this.#store) { 74 | await this.#storage.delete(this.#id); 75 | } 76 | this.#store = undefined; 77 | } 78 | 79 | redirect(url: string | URL): Response { 80 | const cookie = cookieHeader( 81 | this.#options.cookie?.name ?? "session", 82 | this.#id, 83 | { 84 | ...this.#options.cookie, 85 | expires: new Date(this.#store === undefined ? 0 : Date.now() + 1000 * (this.#options.maxAge ?? 1800)), 86 | }, 87 | ); 88 | return new Response("", { 89 | status: 302, 90 | headers: { "Set-Cookie": cookie, "Location": url.toString() }, 91 | }); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/react-mdx-app/style/markdown.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --theme-color: #d63369; 3 | } 4 | 5 | .markdown-body { 6 | overflow: hidden; 7 | color: #333; 8 | padding-left: 30px; 9 | 10 | & a { 11 | color: var(--theme-color); 12 | 13 | &:hover { 14 | text-decoration: underline; 15 | } 16 | } 17 | 18 | & strong { 19 | font-weight: 600; 20 | color: #111; 21 | } 22 | 23 | & em { 24 | font-style: italic; 25 | } 26 | 27 | & h1, 28 | & h2, 29 | & h3, 30 | & h4 { 31 | line-height: 1.2; 32 | font-weight: 500; 33 | color: #111; 34 | position: relative; 35 | 36 | & .anchor { 37 | position: absolute; 38 | left: -24px; 39 | top: 0.1em; 40 | padding-right: 24px; 41 | display: none; 42 | font-size: 80%; 43 | color: #999; 44 | 45 | &:hover { 46 | color: #333; 47 | } 48 | } 49 | &:hover .anchor { 50 | display: inline-block; 51 | text-decoration: none; 52 | } 53 | } 54 | 55 | & h1 { 56 | margin-top: 3.2rem; 57 | font-size: 2.4rem; 58 | font-weight: 700; 59 | } 60 | 61 | & h1:first-child { 62 | margin-top: 0; 63 | } 64 | 65 | & h2 { 66 | margin-top: 2.8rem; 67 | font-size: 1.8rem; 68 | } 69 | 70 | & h3 { 71 | margin-top: 2.4rem; 72 | font-size: 1.5rem; 73 | } 74 | 75 | & h4 { 76 | margin-top: 2.0rem; 77 | font-size: 1.27rem; 78 | } 79 | 80 | &>p { 81 | margin-top: 1.2rem; 82 | line-height: 1.5; 83 | } 84 | 85 | & ul { 86 | padding-left: 1.2rem; 87 | margin-top: 0.9rem; 88 | list-style: none; 89 | } 90 | 91 | & ul li:before { 92 | position: absolute; 93 | margin-left: -1rem; 94 | color: #aaa; 95 | content: '-'; 96 | } 97 | 98 | & ol { 99 | margin-top: 0.9rem; 100 | list-style: decimal inside; 101 | } 102 | 103 | & ul, 104 | & ol { 105 | & li+li { 106 | margin-top: 0.5rem; 107 | } 108 | 109 | & li p { 110 | display: inline; 111 | } 112 | } 113 | 114 | & pre { 115 | box-sizing: border-box; 116 | overflow-x: auto; 117 | width: 100%; 118 | margin-top: 1.5rem; 119 | border-radius: 6px; 120 | line-height: 1.5; 121 | color: #333; 122 | background-color: #f6f6f6; 123 | white-space: pre; 124 | overflow-x: auto; 125 | -webkit-overflow-scrolling: touch; 126 | 127 | &>code { 128 | display: block; 129 | padding: 1rem 1.2rem; 130 | font-size: 0.9rem; 131 | } 132 | } 133 | 134 | & :not(pre)>code { 135 | display: inline; 136 | white-space: pre-wrap; 137 | font-size: 90%; 138 | color: var(--theme-color); 139 | 140 | &::before, 141 | &::after { 142 | color: currentColor; 143 | content: '`' 144 | } 145 | } 146 | 147 | & blockquote { 148 | color: #666; 149 | padding: 0.3rem 1.2rem; 150 | margin: 1.2rem 0; 151 | border-left: 2px solid #ccc; 152 | 153 | & p { 154 | margin: 0; 155 | } 156 | 157 | & a { 158 | box-shadow: none; 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /examples/yew-app/src/routes/todos.rs: -------------------------------------------------------------------------------- 1 | use web_sys::HtmlInputElement; 2 | use yew::prelude::*; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | struct Todo { 6 | id: usize, 7 | completed: bool, 8 | title: String, 9 | } 10 | 11 | #[derive(PartialEq, Properties, Clone)] 12 | struct EntryProps { 13 | pub todo: Todo, 14 | pub ontoggle: Callback, 15 | pub onremove: Callback, 16 | } 17 | 18 | #[function_component] 19 | fn Entry(props: &EntryProps) -> Html { 20 | let id = props.todo.id; 21 | let lablel_class = if props.todo.completed { 22 | "completed" 23 | } else { 24 | "" 25 | }; 26 | let ontoggle = { 27 | let ontoggle = props.ontoggle.clone(); 28 | move |_| ontoggle.emit(id) 29 | }; 30 | let onremove = { 31 | let onremove = props.onremove.clone(); 32 | move |_| onremove.emit(id) 33 | }; 34 | 35 | html! { 36 |
  • 37 | 38 | 39 | 40 |
  • 41 | } 42 | } 43 | 44 | #[function_component] 45 | pub fn Todos() -> Html { 46 | let todos = use_state(|| Vec::::new()); 47 | let all_todos = use_memo(|todos| todos.len(), todos.clone()); 48 | let completed_todos = use_memo(|todos| todos.iter().filter(|t| t.completed).count(), todos.clone()); 49 | let input_node_ref = use_node_ref(); 50 | 51 | let onadd = { 52 | let todos = todos.clone(); 53 | let input_node_ref = input_node_ref.clone(); 54 | Callback::from(move |e: FocusEvent| { 55 | e.prevent_default(); 56 | let input = input_node_ref.cast::().unwrap(); 57 | let mut v = todos.to_vec(); 58 | v.push(Todo { 59 | id: todos.to_vec().len() + 1, 60 | completed: false, 61 | title: input.value().trim().to_string(), 62 | }); 63 | input.set_value(""); 64 | todos.set(v) 65 | }) 66 | }; 67 | 68 | let ontoggle = { 69 | let todos = todos.clone(); 70 | Callback::from(move |id: usize| { 71 | todos.set( 72 | todos 73 | .to_vec() 74 | .into_iter() 75 | .map(|t| { 76 | if t.id == id { 77 | Todo { 78 | id: t.id, 79 | completed: !t.completed, 80 | title: t.title, 81 | } 82 | } else { 83 | t 84 | } 85 | }) 86 | .collect(), 87 | ) 88 | }) 89 | }; 90 | 91 | let onremove = { 92 | let todos = todos.clone(); 93 | Callback::from(move |id: usize| todos.set(todos.to_vec().into_iter().filter(|t| t.id != id).collect())) 94 | }; 95 | 96 | html! { 97 |
    98 |

    99 | {"Todos"} 100 | if *all_todos > 0 { 101 | {completed_todos}{"/"}{all_todos} 102 | } 103 |

    104 |
      105 | { for todos.iter().map(|todo| html! { 106 | 111 | }) } 112 |
    113 |
    114 | 122 |
    123 |
    124 | } 125 | } 126 | -------------------------------------------------------------------------------- /server/context.ts: -------------------------------------------------------------------------------- 1 | import { computeHash, hmacSign, splitBy } from "../shared/util.ts"; 2 | import { SessionImpl } from "./session.ts"; 3 | import type { ConnInfo, Context, HTMLRewriterHandlers, Session, SessionOptions } from "./types.ts"; 4 | 5 | export const NEXT = Symbol(); 6 | export const CUSTOM_HTML_REWRITER = Symbol(); 7 | 8 | export type ContextInit = { 9 | req: Request; 10 | connInfo: ConnInfo; 11 | sessionOptions?: SessionOptions; 12 | }; 13 | 14 | /** create a context object */ 15 | export function createContext(next: () => Promise | Response, init: ContextInit): Context { 16 | let cookies: Map | null = null; 17 | let session: Session> | null = null; 18 | const customHtmlRewriter: [string, HTMLRewriterHandlers][] = []; 19 | const extension = {}; 20 | const ctx: Context = { 21 | connInfo: init.connInfo, 22 | params: {}, 23 | cookies: { 24 | get(name: string) { 25 | if (!cookies) { 26 | cookies = new Map(); 27 | const cookieHeader = init.req.headers.get("Cookie"); 28 | if (cookieHeader) { 29 | for (const cookie of cookieHeader.split(";")) { 30 | const [key, value] = splitBy(cookie, "="); 31 | cookies.set(key.trim(), value); 32 | } 33 | } 34 | } 35 | return cookies.get(name); 36 | }, 37 | }, 38 | // deno-lint-ignore ban-ts-comment 39 | // @ts-ignore 40 | async getSession(): Promise>> { 41 | if (session) { 42 | return session; 43 | } 44 | 45 | const { sessionOptions } = init; 46 | const cookieName = sessionOptions?.cookie?.name ?? "session"; 47 | const secret = sessionOptions?.secret ?? "-"; 48 | let sid = ctx.cookies.get(cookieName); 49 | let skipInit = false; 50 | if (sid) { 51 | const [rid, signature] = splitBy(sid, "."); 52 | if (!signature || signature !== await hmacSign(rid, secret, "SHA-256")) { 53 | sid = undefined; 54 | } 55 | } 56 | if (!sid) { 57 | const rid = await computeHash("SHA-1", crypto.randomUUID()); 58 | sid = rid + "." + hmacSign(rid, secret); 59 | skipInit = true; 60 | } 61 | 62 | const sessionImpl = new SessionImpl>(sid, sessionOptions); 63 | session = sessionImpl; 64 | if (!skipInit) { 65 | await sessionImpl.init(); 66 | } 67 | return session; 68 | }, 69 | htmlRewriter: { 70 | on: (selector: string, handlers: HTMLRewriterHandlers) => { 71 | customHtmlRewriter.push([selector, handlers]); 72 | }, 73 | }, 74 | next, 75 | }; 76 | return new Proxy(Object.create({}), { 77 | get(_target, prop) { 78 | if (prop === CUSTOM_HTML_REWRITER) { 79 | return customHtmlRewriter; 80 | } 81 | if (Reflect.has(ctx, prop)) { 82 | return Reflect.get(ctx, prop); 83 | } 84 | return Reflect.get(extension, prop); 85 | }, 86 | set(_target, prop, value) { 87 | if (prop === NEXT) { 88 | Reflect.set(ctx, "next", value); 89 | } 90 | if (!Reflect.has(ctx, prop)) { 91 | return Reflect.set(extension, prop, value); 92 | } 93 | return false; 94 | }, 95 | deleteProperty(_target, prop) { 96 | if (!Reflect.has(ctx, prop)) { 97 | return Reflect.deleteProperty(extension, prop); 98 | } 99 | return false; 100 | }, 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /framework/core/url_pattern.ts: -------------------------------------------------------------------------------- 1 | import { isFilledString, splitPath } from "../../shared/util.ts"; 2 | 3 | export type URLPatternInput = { 4 | host?: string; 5 | pathname: string; 6 | }; 7 | 8 | export type URLPatternResult = { 9 | host: { input: string; groups: Record }; 10 | pathname: { input: string; groups: Record }; 11 | }; 12 | 13 | /** 14 | * A class uses the `URLPattern` class to parse and match URLs if the browser supports it, 15 | * or fallback to use the `execPathname` function. 16 | */ 17 | export class URLPatternCompat { 18 | pattern: Record; 19 | 20 | static execPathname( 21 | patternPathname: string, 22 | pathname: string, 23 | ): null | Pick { 24 | const patternSegments = splitPath(patternPathname); 25 | const segments = splitPath(pathname); 26 | const depth = Math.max(patternSegments.length, segments.length); 27 | const groups: Record = {}; 28 | 29 | for (let i = 0; i < depth; i++) { 30 | const patternSegment = patternSegments[i]; 31 | const segment = segments[i]; 32 | 33 | if (segment === undefined || patternSegment === undefined) { 34 | return null; 35 | } 36 | 37 | if (patternSegment.startsWith(":") && patternSegment.length > 1) { 38 | if (patternSegment.endsWith("+") && patternSegment.length > 2 && i === patternSegments.length - 1) { 39 | groups[patternSegment.slice(1, -1)] = segments.slice(i).map(decodeURIComponent).join("/"); 40 | break; 41 | } 42 | groups[patternSegment.slice(1)] = decodeURIComponent(segment); 43 | } else if (patternSegment !== segment) { 44 | return null; 45 | } 46 | } 47 | 48 | return { 49 | pathname: { 50 | input: pathname, 51 | groups, 52 | }, 53 | }; 54 | } 55 | 56 | constructor(pattern: URLPatternInput) { 57 | if ("URLPattern" in globalThis) { 58 | this.pattern = new URLPattern(pattern) as unknown as Record; 59 | } else { 60 | this.pattern = pattern; 61 | } 62 | } 63 | 64 | test(input: { host: string; pathname: string }): boolean { 65 | const { pattern } = this; 66 | if (typeof pattern.test === "function") { 67 | return pattern.test(input); 68 | } 69 | if (isFilledString(pattern.host) && pattern.host !== input.host) { 70 | return false; 71 | } 72 | if (isFilledString(pattern.pathname)) { 73 | return URLPatternCompat.execPathname(pattern.pathname, input.pathname) !== null; 74 | } 75 | return false; 76 | } 77 | 78 | exec(input: { host: string; pathname: string }): URLPatternResult | null { 79 | const { pattern } = this; 80 | if (typeof pattern.exec === "function") { 81 | return pattern.exec(input); 82 | } 83 | if (isFilledString(pattern.host) && pattern.host !== input.host) { 84 | return null; 85 | } 86 | if (isFilledString(pattern.pathname)) { 87 | const ret = URLPatternCompat.execPathname(pattern.pathname, input.pathname); 88 | if (ret) { 89 | return { 90 | ...ret, 91 | host: { 92 | input: input.host, 93 | groups: {}, 94 | }, 95 | }; 96 | } 97 | } 98 | return null; 99 | } 100 | } 101 | 102 | export function createStaticURLPatternResult(host: string, pathname: string): URLPatternResult { 103 | return { 104 | host: { input: host, groups: {} }, 105 | pathname: { input: pathname, groups: {} }, 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /shared/util.ts: -------------------------------------------------------------------------------- 1 | export const utf8Enc = new TextEncoder(); 2 | export const utf8Dec = new TextDecoder(); 3 | 4 | export function assertString(s: unknown): asserts s is string { 5 | if (typeof s !== "string") { 6 | throw new Error("assert string failed"); 7 | } 8 | } 9 | 10 | export function isFilledString(a: unknown): a is string { 11 | return typeof a === "string" && a.length > 0; 12 | } 13 | 14 | // deno-lint-ignore no-explicit-any 15 | export function isFilledArray(a: unknown): a is Array { 16 | return Array.isArray(a) && a.length > 0; 17 | } 18 | 19 | // remove duplicate items from an array 20 | export function unique(arr: T[]): T[] { 21 | return [...new Set(arr)]; 22 | } 23 | 24 | export function isPlainObject>(a: unknown): a is T { 25 | return a !== null && typeof a === "object" && Object.getPrototypeOf(a) === Object.prototype; 26 | } 27 | 28 | export function isLikelyHttpURL(s: string): boolean { 29 | const p = s.slice(0, 8).toLowerCase(); 30 | return p === "https://" || p.slice(0, 7) === "http://"; 31 | } 32 | 33 | export function trimPrefix(s: string, prefix: string): string { 34 | if (prefix !== "" && s.startsWith(prefix)) { 35 | return s.slice(prefix.length); 36 | } 37 | return s; 38 | } 39 | 40 | export function trimSuffix(s: string, suffix: string): string { 41 | if (suffix !== "" && s.endsWith(suffix)) { 42 | return s.slice(0, -suffix.length); 43 | } 44 | return s; 45 | } 46 | 47 | export function pick, K extends keyof T>(obj: T, ...keys: K[]): Pick { 48 | const result: Record = {}; 49 | for (const key of keys) { 50 | if (key in obj) { 51 | result[key as string] = obj[key]; 52 | } 53 | } 54 | return result as Pick; 55 | } 56 | 57 | export function splitBy(s: string, searchString: string, fromLast = false): [prefix: string, suffix: string] { 58 | const i = fromLast ? s.lastIndexOf(searchString) : s.indexOf(searchString); 59 | if (i >= 0) { 60 | return [s.slice(0, i), s.slice(i + searchString.length)]; 61 | } 62 | return [s, ""]; 63 | } 64 | 65 | export function toHex(buffer: ArrayBuffer) { 66 | const bytes = new Uint8Array(buffer); 67 | return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join(""); 68 | } 69 | 70 | export async function hmacSign(data: string, secret: string, hash = "SHA-256") { 71 | const key = await crypto.subtle.importKey( 72 | "raw", 73 | utf8Enc.encode(secret), 74 | { name: "HMAC", hash: { name: hash } }, 75 | false, 76 | ["sign", "verify"], 77 | ); 78 | const signature = await crypto.subtle.sign("HMAC", key, utf8Enc.encode(data)); 79 | return toHex(signature); 80 | } 81 | 82 | export async function computeHash(algorithm: AlgorithmIdentifier, data: string | Uint8Array): Promise { 83 | return await crypto.subtle.digest( 84 | algorithm, 85 | typeof data === "string" ? utf8Enc.encode(data) : data, 86 | ).then((sum) => toHex(sum)); 87 | } 88 | 89 | export function prettyBytes(bytes: number) { 90 | const units = ["", "K", "M", "G", "T", "P", "E"]; 91 | const exp = Math.floor(Math.log(bytes) / Math.log(1024)); 92 | return `${Math.round(bytes * 100 / Math.pow(1024, exp)) / 100}${units[exp]}B`; 93 | } 94 | 95 | export function splitPath(path: string): string[] { 96 | return path 97 | .split(/[\/\\]+/g) 98 | .filter((p) => p !== "" && p !== ".") 99 | .reduce((slice, p) => { 100 | if (p === "..") { 101 | slice.pop(); 102 | } else { 103 | slice.push(p); 104 | } 105 | return slice; 106 | }, [] as Array); 107 | } 108 | 109 | export function cleanPath(path: string): string { 110 | return "/" + splitPath(path).join("/"); 111 | } 112 | -------------------------------------------------------------------------------- /framework/react/head.ts: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from "react"; 2 | import { Children, createElement, Fragment, isValidElement, useContext, useEffect, useMemo } from "react"; 3 | import { isFilledArray, isFilledString } from "../../shared/util.ts"; 4 | import { RouterContext } from "./context.ts"; 5 | 6 | export const Head: FC<{ children?: ReactNode }> = (props) => { 7 | const { ssrHeadCollection } = useContext(RouterContext); 8 | const [els, forwardNodes] = useMemo(() => parse(props.children), [ 9 | props.children, 10 | ]); 11 | 12 | if (ssrHeadCollection) { 13 | els.forEach(({ type, props }) => { 14 | const { children, ...rest } = props; 15 | if (type === "title") { 16 | if (isFilledString(children)) { 17 | ssrHeadCollection.push(`${children}`); 18 | } else if (isFilledArray(children)) { 19 | ssrHeadCollection.push(`${children.join("")}`); 20 | } 21 | } else { 22 | const attrs = Object.entries(rest).map(([key, value]) => ` ${key}=${JSON.stringify(value)}`) 23 | .join(""); 24 | if (isFilledString(children)) { 25 | ssrHeadCollection.push(`<${type}${attrs} ssr>${children}`); 26 | } else if (isFilledArray(children)) { 27 | ssrHeadCollection.push( 28 | `<${type}${attrs} ssr>${children.join("")}`, 29 | ); 30 | } else { 31 | ssrHeadCollection.push(`<${type}${attrs} ssr>`); 32 | } 33 | } 34 | }); 35 | } 36 | 37 | useEffect(() => { 38 | const { document } = window; 39 | const { head } = document; 40 | const insertedEls: Array = []; 41 | 42 | if (els.length > 0) { 43 | els.forEach(({ type, props }) => { 44 | const el = document.createElement(type); 45 | Object.keys(props).forEach((key) => { 46 | const value = props[key]; 47 | if (key === "children") { 48 | if (isFilledString(value)) { 49 | el.innerText = value; 50 | } else if (isFilledArray(value)) { 51 | el.innerText = value.join(""); 52 | } 53 | } else { 54 | el.setAttribute(key, String(value || "")); 55 | } 56 | }); 57 | head.appendChild(el); 58 | insertedEls.push(el); 59 | }); 60 | } 61 | 62 | // remove ssr head elements 63 | Array.from(head.children).forEach((el: Element) => { 64 | if (el.hasAttribute("ssr")) { 65 | head.removeChild(el); 66 | } 67 | }); 68 | 69 | return () => { 70 | insertedEls.forEach((el) => head.removeChild(el)); 71 | }; 72 | }, [els]); 73 | 74 | return createElement(Fragment, null, ...forwardNodes); 75 | }; 76 | 77 | function parse( 78 | node: ReactNode, 79 | ): [{ type: string; props: Record }[], ReactNode[]] { 80 | const els: { type: string; props: Record }[] = []; 81 | const forwardNodes: ReactNode[] = []; 82 | const walk = (node: ReactNode) => { 83 | Children.forEach(node, (child) => { 84 | if (!isValidElement(child)) { 85 | return; 86 | } 87 | 88 | const { type, props } = child; 89 | switch (type) { 90 | case Fragment: 91 | walk(props.children); 92 | break; 93 | 94 | // ingore `script` and `no-script` tag 95 | 96 | case "base": 97 | case "title": 98 | case "meta": 99 | case "link": 100 | case "style": 101 | // remove the children prop of base/meta/link elements 102 | if (["base", "meta", "link"].includes(type) && "children" in props) { 103 | const { children: _, ...rest } = props; 104 | els.push({ type, props: rest }); 105 | } else { 106 | els.push({ type, props }); 107 | } 108 | break; 109 | } 110 | }); 111 | }; 112 | 113 | walk(node); 114 | return [els, forwardNodes]; 115 | } 116 | -------------------------------------------------------------------------------- /tests/integration_react_app_test.tsx: -------------------------------------------------------------------------------- 1 | import { assertEquals, assertStringIncludes } from "std/testing/asserts.ts"; 2 | import { MockServer } from "aleph/server/mock.ts"; 3 | import { render } from "aleph/framework/react/plugin.ts"; 4 | 5 | Deno.test("[integration] examples/react-app", async (t) => { 6 | const api = new MockServer({ 7 | appDir: "./examples/react-app", 8 | router: { 9 | glob: "./routes/**/*.{tsx,ts}", 10 | }, 11 | ssr: { render }, 12 | }); 13 | 14 | await t.step("GET /", async () => { 15 | const res = await api.fetch("/"); 16 | const html = await res.text(); 17 | assertEquals(res.status, 200); 18 | assertEquals(res.headers.get("Content-Type"), "text/html; charset=utf-8"); 19 | assertStringIncludes(html, `Aleph.js`); 21 | assertStringIncludes(html, ``); 22 | assertStringIncludes(html, `The Fullstack Framework in Deno.`); 23 | assertStringIncludes(html, ` href="/todos" `); 24 | assertStringIncludes(html, `>Todos App Demo`); 25 | assertStringIncludes(html, ``); 28 | assertStringIncludes(html, `