Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
18e51de
commit 300e27c
Showing
23 changed files
with
649 additions
and
349 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/ultra |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { lazy, Suspense, useState } from "react"; | ||
import { ErrorBoundary } from "https://esm.sh/*react-error-boundary@4.0.11"; | ||
import { ImportMapScript } from "ultra/lib/react/client.js"; | ||
|
||
const LazyComponent = lazy(() => import("./components/Test.tsx")); | ||
|
||
const logError = (error: Error, info: { componentStack: string }) => { | ||
console.log(error, info); | ||
}; | ||
|
||
export default function App() { | ||
const [state, setState] = useState(0); | ||
return ( | ||
<html> | ||
<head> | ||
<title>Testing</title> | ||
<link rel="stylesheet" href="/style.css" /> | ||
<ImportMapScript /> | ||
</head> | ||
<body> | ||
<main>Hello World {state}</main> | ||
<ErrorBoundary | ||
fallback={<div>Something went wrong</div>} | ||
onError={logError} | ||
> | ||
<Suspense fallback={<div>Loading...</div>}> | ||
<LazyComponent /> | ||
</Suspense> | ||
</ErrorBoundary> | ||
<button onClick={() => setState(state + 1)}>Click Me</button> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import UltraClient, { hydrate } from "ultra/lib/react/client.js"; | ||
import App from "/~/app.tsx"; | ||
|
||
hydrate( | ||
document, | ||
<UltraClient> | ||
<App /> | ||
</UltraClient>, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Test() { | ||
return <div>Test</div>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"imports": { | ||
"react": "https://esm.sh/stable/react@18.2.0?dev", | ||
"react/": "https://esm.sh/stable/react@18.2.0&dev/", | ||
"react-dom": "https://esm.sh/react-dom@18.2.0?external=react&dev", | ||
"react-dom/": "https://esm.sh/react-dom@18.2.0&external=react&dev/", | ||
"ultra/": "../", | ||
"/~/": "./" | ||
}, | ||
"tasks": { | ||
"dev": "deno run -A server.tsx" | ||
}, | ||
"compilerOptions": { | ||
"jsx": "react-jsxdev", | ||
"jsxImportSource": "react" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { renderToReadableStream } from "react-dom/server"; | ||
import { createReactHandler } from "ultra/lib/react/mod.ts"; | ||
import UltraServer from "ultra/lib/react/server.js"; | ||
import App from "./app.tsx"; | ||
|
||
const root = Deno.cwd(); | ||
|
||
// create symlink to ultra for development | ||
try { | ||
await Deno.symlink("../", "./ultra", { type: "dir" }); | ||
} catch (error) { | ||
// ignore | ||
} | ||
|
||
const importMap = { | ||
imports: { | ||
"react": "https://esm.sh/react@18?dev", | ||
"react/": "https://esm.sh/react@18&dev/", | ||
"react-dom/": "https://esm.sh/react-dom@18&dev&external=react/", | ||
"/~/": import.meta.resolve("./"), | ||
"ultra/": import.meta.resolve("./ultra/"), | ||
}, | ||
}; | ||
|
||
const handler = createReactHandler({ | ||
root, | ||
render(request) { | ||
return renderToReadableStream( | ||
<UltraServer request={request} importMap={importMap}> | ||
<App /> | ||
</UltraServer>, | ||
{ | ||
bootstrapModules: [ | ||
import.meta.resolve("./client.tsx"), | ||
], | ||
}, | ||
); | ||
}, | ||
}); | ||
|
||
Deno.serve((request) => { | ||
const url = new URL(request.url, "http://localhost"); | ||
|
||
if (url.pathname === "/favicon.ico") { | ||
return new Response(null, { status: 404 }); | ||
} | ||
|
||
if (handler.supportsRequest(request)) { | ||
return handler.handleRequest(request); | ||
} | ||
|
||
return new Response("Not Found", { status: 404 }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,51 @@ | ||
import { createServer } from "ultra/server.ts"; | ||
import { renderToReadableStream } from "react-dom/server"; | ||
import { createCompilerHandler } from "ultra/lib/react/compiler.ts"; | ||
import { createRenderHandler } from "ultra/lib/react/renderer.ts"; | ||
import UltraServer from "ultra/lib/react/server.js"; | ||
import App from "./src/app.tsx"; | ||
import { readImportMap } from "ultra/lib/utils/import-map.ts"; | ||
import { createStaticHandler } from "ultra/lib/static/handler.ts"; | ||
import { composeHandlers } from "ultra/lib/handler.ts"; | ||
|
||
const server = await createServer({ | ||
importMapPath: Deno.env.get("ULTRA_MODE") === "development" | ||
? import.meta.resolve("./importMap.dev.json") | ||
: import.meta.resolve("./importMap.json"), | ||
browserEntrypoint: import.meta.resolve("./client.tsx"), | ||
const root = Deno.cwd(); | ||
|
||
const importMap = Deno.env.get("ULTRA_MODE") === "development" | ||
? await readImportMap("./importMap.dev.json") | ||
: await readImportMap("./importMap.json"); | ||
|
||
const renderer = createRenderHandler({ | ||
root, | ||
render(request) { | ||
return renderToReadableStream( | ||
<UltraServer request={request} importMap={importMap}> | ||
<App /> | ||
</UltraServer>, | ||
{ | ||
bootstrapModules: [ | ||
import.meta.resolve("./client.tsx"), | ||
], | ||
}, | ||
); | ||
}, | ||
}); | ||
|
||
server.get("*", async (context) => { | ||
/** | ||
* Render the request | ||
*/ | ||
const result = await server.render(<App />); | ||
const compiler = createCompilerHandler({ | ||
root, | ||
}); | ||
|
||
return context.body(result, 200, { | ||
"content-type": "text/html; charset=utf-8", | ||
}); | ||
const staticHandler = createStaticHandler({ | ||
pathToRoot: import.meta.resolve("./public"), | ||
}); | ||
|
||
if (import.meta.main) { | ||
Deno.serve(server.fetch); | ||
} | ||
const executeHandlers = composeHandlers( | ||
compiler, | ||
renderer, | ||
staticHandler | ||
); | ||
|
||
export default server; | ||
Deno.serve((request) => { | ||
const response = executeHandlers(request); | ||
if (response) return response; | ||
|
||
return new Response("Not Found", { status: 404 }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export interface RequestHandler { | ||
handleRequest: (request: Request) => Promise<Response>; | ||
supportsRequest: (request: Request) => boolean; | ||
} | ||
|
||
export function executeHandler (request: Request, handler: RequestHandler) { | ||
try { | ||
if (handler.supportsRequest(request)) { | ||
return handler.handleRequest(request); | ||
} | ||
} catch (_) { | ||
return null; | ||
} | ||
} | ||
|
||
export function composeHandlers (...handlers: RequestHandler[]) { | ||
return function executeHandlerArray (request: Request) { | ||
for (const handler of handlers) { | ||
const response = executeHandler(request, handler); | ||
if (response) return response; | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { type ImportMapJson, parseFromJson, toFileUrl } from "./deps.ts"; | ||
|
||
export type ImportMap = ImportMapJson; | ||
|
||
export async function createImportMapProxy( | ||
target: ImportMapJson, | ||
root: string | URL, | ||
) { | ||
const base = root instanceof URL ? root : toFileUrl(root); | ||
const importMap = await parseFromJson(base, target); | ||
|
||
const importsProxy = new Proxy(target.imports, { | ||
get: (target, prop) => { | ||
if (typeof prop === "symbol") { | ||
throw new TypeError("Symbol properties are not supported."); | ||
} | ||
|
||
const value = target[prop]; | ||
const resolved = !value ? importMap.resolve(prop, base) : value; | ||
|
||
return resolved; | ||
}, | ||
}); | ||
|
||
return new Proxy(target, { | ||
get: (target, prop) => { | ||
if (typeof prop === "symbol") { | ||
throw new TypeError("Symbol properties are not supported."); | ||
} | ||
|
||
if (prop === "toJSON") { | ||
return () => target; | ||
} | ||
|
||
if (prop === "imports") { | ||
return importsProxy; | ||
} | ||
|
||
return target[prop as keyof typeof target]; | ||
}, | ||
}); | ||
} | ||
|
||
type ImportMapProxyOptions = { | ||
root: string | URL; | ||
}; | ||
|
||
export class ImportMapProxy { | ||
imports: object; | ||
scopes: object; | ||
|
||
constructor(target: ImportMapJson, options: ImportMapProxyOptions) { | ||
const root = options.root instanceof URL | ||
? options.root | ||
: toFileUrl(options.root); | ||
|
||
this.imports = new Proxy(target.imports ?? {}, { | ||
get: (target, prop) => { | ||
if (typeof prop === "symbol") { | ||
throw new TypeError("Symbol properties are not supported."); | ||
} | ||
|
||
const specifier = target[prop]; | ||
|
||
if (specifier) { | ||
return new URL(specifier, root).href; | ||
} | ||
|
||
return undefined; | ||
}, | ||
}); | ||
|
||
this.scopes = new Proxy(target.scopes ?? {}, { | ||
get: (target, prop) => { | ||
if (typeof prop === "symbol") { | ||
throw new TypeError("Symbol properties are not supported."); | ||
} | ||
|
||
const scope = target[prop]; | ||
if (scope) { | ||
return new Proxy(scope, { | ||
get: (target, prop) => { | ||
if (typeof prop === "symbol") { | ||
throw new TypeError("Symbol properties are not supported."); | ||
} | ||
const specifier = target[prop]; | ||
if (specifier) { | ||
return new URL(specifier, root).href; | ||
} | ||
return undefined; | ||
}, | ||
}); | ||
} | ||
return undefined; | ||
}, | ||
}); | ||
} | ||
} |
Empty file.
Oops, something went wrong.