diff --git a/crates/base/src/deno_runtime.rs b/crates/base/src/deno_runtime.rs index d462a4e5a..e8f2f7e16 100644 --- a/crates/base/src/deno_runtime.rs +++ b/crates/base/src/deno_runtime.rs @@ -376,12 +376,7 @@ impl DenoRuntime { let op_fs = { if is_user_worker { - Arc::new(sb_fs::static_fs::StaticFs::new( - static_files, - vfs_path, - vfs, - npm_snapshot, - )) as Arc + Arc::new(DenoCompileFileSystem::from_rc(vfs)) as Arc } else { Arc::new(DenoCompileFileSystem::from_rc(vfs)) as Arc } diff --git a/crates/sb_core/js/bootstrap.js b/crates/sb_core/js/bootstrap.js index 52037f217..23dd75e64 100644 --- a/crates/sb_core/js/bootstrap.js +++ b/crates/sb_core/js/bootstrap.js @@ -375,7 +375,7 @@ const MOCK_FN = () => {/* do nothing */}; const DENIED_DENO_FS_API_LIST = ObjectKeys(fsVars) .reduce( (acc, it) => { - acc[it] = false; + acc[it] = true; return acc; }, {} diff --git a/crates/sb_core/js/permissions.js b/crates/sb_core/js/permissions.js index f4f6b2b8d..5aeb44fe7 100644 --- a/crates/sb_core/js/permissions.js +++ b/crates/sb_core/js/permissions.js @@ -102,9 +102,8 @@ class PermissionStatus extends EventTarget { } [SymbolFor("Deno.privateCustomInspect")](inspect) { - return `${this.constructor.name} ${ - inspect({ state: this.state, onchange: this.onchange }) - }`; + return `${this.constructor.name} ${inspect({ state: this.state, onchange: this.onchange }) + }`; } } @@ -195,6 +194,9 @@ class Permissions { } formDescriptor(desc); + if (desc.name === "run") { + return cache(desc, "prompt"); + } const state = opQuery(desc); return cache(desc, state); @@ -251,7 +253,7 @@ function serializePermissions(permissions) { const serializedPermissions = {}; for ( const key of new SafeArrayIterator(["read", "write", "run", "ffi"]) - ) { + ) { if (ArrayIsArray(permissions[key])) { serializedPermissions[key] = ArrayPrototypeMap( permissions[key], @@ -263,7 +265,7 @@ function serializePermissions(permissions) { } for ( const key of new SafeArrayIterator(["env", "hrtime", "net", "sys"]) - ) { + ) { if (ArrayIsArray(permissions[key])) { serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]); } else { diff --git a/deno.json b/deno.json index f830c46bc..b8572d807 100644 --- a/deno.json +++ b/deno.json @@ -6,5 +6,10 @@ "singleQuote": true, "proseWrap": "preserve", "include": ["examples/**/*.ts"] + }, + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "preact", + "experimentalDecorators": true } } diff --git a/deno.lock b/deno.lock index e6377b07c..26a1e7411 100644 --- a/deno.lock +++ b/deno.lock @@ -35,6 +35,9 @@ } } }, + "redirects": { + "https://esm.sh/preact-render-to-string": "https://esm.sh/preact-render-to-string@6.4.2" + }, "remote": { "https://deno.land/x/is_even@v1.0/mod.ts": "f0596cc34079796565f5fb7ebc0496c1dfe435f1039a7751e1922c89e6f85d6e" } diff --git a/examples/fresh-project/.gitignore b/examples/fresh-project/.gitignore new file mode 100644 index 000000000..5b4bdefcb --- /dev/null +++ b/examples/fresh-project/.gitignore @@ -0,0 +1,11 @@ +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# Fresh build directory +_fresh/ +# npm dependencies +node_modules/ diff --git a/examples/fresh-project/.vscode/extensions.json b/examples/fresh-project/.vscode/extensions.json new file mode 100644 index 000000000..09cf720d3 --- /dev/null +++ b/examples/fresh-project/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "denoland.vscode-deno" + ] +} diff --git a/examples/fresh-project/.vscode/settings.json b/examples/fresh-project/.vscode/settings.json new file mode 100644 index 000000000..a5f0701f2 --- /dev/null +++ b/examples/fresh-project/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "deno.enable": true, + "deno.lint": true, + "editor.defaultFormatter": "denoland.vscode-deno", + "[typescriptreact]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[typescript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "denoland.vscode-deno" + }, + "[javascript]": { + "editor.defaultFormatter": "denoland.vscode-deno" + } +} diff --git a/examples/fresh-project/README.md b/examples/fresh-project/README.md new file mode 100644 index 000000000..ec0e33ee3 --- /dev/null +++ b/examples/fresh-project/README.md @@ -0,0 +1,16 @@ +# Fresh project + +Your new Fresh project is ready to go. You can follow the Fresh "Getting +Started" guide here: https://fresh.deno.dev/docs/getting-started + +### Usage + +Make sure to install Deno: https://deno.land/manual/getting_started/installation + +Then start the project: + +``` +deno task start +``` + +This will watch the project directory and restart as necessary. diff --git a/examples/fresh-project/components/Button.tsx b/examples/fresh-project/components/Button.tsx new file mode 100644 index 000000000..f1b80a0b0 --- /dev/null +++ b/examples/fresh-project/components/Button.tsx @@ -0,0 +1,12 @@ +import { JSX } from "preact"; +import { IS_BROWSER } from "$fresh/runtime.ts"; + +export function Button(props: JSX.HTMLAttributes) { + return ( + +

{props.count}

+ + + ); +} diff --git a/examples/fresh-project/main.ts b/examples/fresh-project/main.ts new file mode 100644 index 000000000..675f529bb --- /dev/null +++ b/examples/fresh-project/main.ts @@ -0,0 +1,13 @@ +/// +/// +/// +/// +/// + +import "$std/dotenv/load.ts"; + +import { start } from "$fresh/server.ts"; +import manifest from "./fresh.gen.ts"; +import config from "./fresh.config.ts"; + +await start(manifest, config); diff --git a/examples/fresh-project/routes/_404.tsx b/examples/fresh-project/routes/_404.tsx new file mode 100644 index 000000000..c63ae2ece --- /dev/null +++ b/examples/fresh-project/routes/_404.tsx @@ -0,0 +1,27 @@ +import { Head } from "$fresh/runtime.ts"; + +export default function Error404() { + return ( + <> + + 404 - Page not found + +
+
+ the Fresh logo: a sliced lemon dripping with juice +

404 - Page not found

+

+ The page you were looking for doesn't exist. +

+ Go back home +
+
+ + ); +} diff --git a/examples/fresh-project/routes/_app.tsx b/examples/fresh-project/routes/_app.tsx new file mode 100644 index 000000000..509c1b470 --- /dev/null +++ b/examples/fresh-project/routes/_app.tsx @@ -0,0 +1,17 @@ +/** @jsxImportSource preact */ +import { type PageProps } from "$fresh/server.ts"; +export default function App({ Component }: PageProps) { + return ( + + + + + fresh-project + + + + + + + ); +} diff --git a/examples/fresh-project/routes/api/joke.ts b/examples/fresh-project/routes/api/joke.ts new file mode 100644 index 000000000..db17eddac --- /dev/null +++ b/examples/fresh-project/routes/api/joke.ts @@ -0,0 +1,21 @@ +import { FreshContext } from "$fresh/server.ts"; + +// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/ +const JOKES = [ + "Why do Java developers often wear glasses? They can't C#.", + "A SQL query walks into a bar, goes up to two tables and says “can I join you?”", + "Wasn't hard to crack Forrest Gump's password. 1forrest1.", + "I love pressing the F5 key. It's refreshing.", + "Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”", + "There are 10 types of people in the world. Those who understand binary and those who don't.", + "Why are assembly programmers often wet? They work below C level.", + "My favourite computer based band is the Black IPs.", + "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.", + "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.", +]; + +export const handler = (_req: Request, _ctx: FreshContext): Response => { + const randomIndex = Math.floor(Math.random() * JOKES.length); + const body = JOKES[randomIndex]; + return new Response(body); +}; diff --git a/examples/fresh-project/routes/greet/[name].tsx b/examples/fresh-project/routes/greet/[name].tsx new file mode 100644 index 000000000..9c068270a --- /dev/null +++ b/examples/fresh-project/routes/greet/[name].tsx @@ -0,0 +1,5 @@ +import { PageProps } from "$fresh/server.ts"; + +export default function Greet(props: PageProps) { + return
Hello {props.params.name}
; +} diff --git a/examples/fresh-project/routes/index.tsx b/examples/fresh-project/routes/index.tsx new file mode 100644 index 000000000..fc8061e03 --- /dev/null +++ b/examples/fresh-project/routes/index.tsx @@ -0,0 +1,25 @@ +import { useSignal } from "@preact/signals"; +import Counter from "../islands/Counter.tsx"; + +export default function Home() { + const count = useSignal(3); + return ( +
+
+ the Fresh logo: a sliced lemon dripping with juice +

Welcome to Fresh

+

+ Try updating this message in the + ./routes/index.tsx file, and refresh. +

+ +
+
+ ); +} diff --git a/examples/fresh-project/static/favicon.ico b/examples/fresh-project/static/favicon.ico new file mode 100644 index 000000000..1cfaaa219 Binary files /dev/null and b/examples/fresh-project/static/favicon.ico differ diff --git a/examples/fresh-project/static/logo.svg b/examples/fresh-project/static/logo.svg new file mode 100644 index 000000000..ef2fbe4cc --- /dev/null +++ b/examples/fresh-project/static/logo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/fresh-project/static/styles.css b/examples/fresh-project/static/styles.css new file mode 100644 index 000000000..e94132d8f --- /dev/null +++ b/examples/fresh-project/static/styles.css @@ -0,0 +1,129 @@ + +*, +*::before, +*::after { + box-sizing: border-box; +} +* { + margin: 0; +} +button { + color: inherit; +} +button, [role="button"] { + cursor: pointer; +} +code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Liberation Mono", "Courier New", monospace; + font-size: 1em; +} +img, +svg { + display: block; +} +img, +video { + max-width: 100%; + height: auto; +} + +html { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} +.transition-colors { + transition-property: background-color, border-color, color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.my-6 { + margin-bottom: 1.5rem; + margin-top: 1.5rem; +} +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} +.my-4 { + margin-bottom: 1rem; + margin-top: 1rem; +} +.mx-auto { + margin-left: auto; + margin-right: auto; +} +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} +.py-8 { + padding-bottom: 2rem; + padding-top: 2rem; +} +.bg-\[\#86efac\] { + background-color: #86efac; +} +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} +.py-6 { + padding-bottom: 1.5rem; + padding-top: 1.5rem; +} +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.py-1 { + padding-bottom: 0.25rem; + padding-top: 0.25rem; +} +.border-gray-500 { + border-color: #6b7280; +} +.bg-white { + background-color: #fff; +} +.flex { + display: flex; +} +.gap-8 { + grid-gap: 2rem; + gap: 2rem; +} +.font-bold { + font-weight: 700; +} +.max-w-screen-md { + max-width: 768px; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-center { + justify-content: center; +} +.border-2 { + border-width: 2px; +} +.rounded { + border-radius: 0.25rem; +} +.hover\:bg-gray-200:hover { + background-color: #e5e7eb; +} +.tabular-nums { + font-variant-numeric: tabular-nums; +} diff --git a/examples/main/index.ts b/examples/main/index.ts index 931fbc05b..008dde1ab 100644 --- a/examples/main/index.ts +++ b/examples/main/index.ts @@ -1,8 +1,4 @@ -import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'; - -console.log('main function started'); - -serve(async (req: Request) => { +Deno.serve((req: Request) => { const url = new URL(req.url); const { pathname } = url; @@ -14,61 +10,26 @@ serve(async (req: Request) => { ); } - if (pathname === '/_internal/metric') { - const metric = await EdgeRuntime.getRuntimeMetrics(); - return Response.json(metric); - } - - // NOTE: You can test WebSocket in the main worker by uncommenting below. - // if (pathname === '/_internal/ws') { - // const upgrade = req.headers.get("upgrade") || ""; - - // if (upgrade.toLowerCase() != "websocket") { - // return new Response("request isn't trying to upgrade to websocket."); - // } - - // const { socket, response } = Deno.upgradeWebSocket(req); - - // socket.onopen = () => console.log("socket opened"); - // socket.onmessage = (e) => { - // console.log("socket message:", e.data); - // socket.send(new Date().toString()); - // }; - - // socket.onerror = e => console.log("socket errored:", e.message); - // socket.onclose = () => console.log("socket closed"); - - // return response; // 101 (Switching Protocols) - // } - - const path_parts = pathname.split('/'); - const service_name = path_parts[1]; - - if (!service_name || service_name === '') { - const error = { msg: 'missing function name in request' }; - return new Response( - JSON.stringify(error), - { status: 400, headers: { 'Content-Type': 'application/json' } }, - ); - } - - const servicePath = `./examples/${service_name}`; - // console.error(`serving the request with ${servicePath}`); + const servicePath = `./examples/fresh-project`; const createWorker = async () => { - const memoryLimitMb = 150; + const memoryLimitMb = 1024; const workerTimeoutMs = 5 * 60 * 1000; const noModuleCache = false; - // you can provide an import map inline - // const inlineImportMap = { - // imports: { - // "std/": "https://deno.land/std@0.131.0/", - // "cors": "./examples/_shared/cors.ts" - // } - // } - // const importMapPath = `data:${encodeURIComponent(JSON.stringify(importMap))}?${encodeURIComponent('/home/deno/functions/test')}`; - const importMapPath = null; + const inlineImportMap = { + 'imports': { + '$fresh/': 'https://deno.land/x/fresh@1.6.8/', + 'preact': 'https://esm.sh/preact@10.19.6', + 'preact/': 'https://esm.sh/preact@10.19.6/', + '@preact/signals': 'https://esm.sh/*@preact/signals@1.2.2', + '@preact/signals-core': 'https://esm.sh/*@preact/signals-core@1.5.1', + '$std/': 'https://deno.land/std@0.216.0/', + }, + }; + const importMapPath = `data:${encodeURIComponent(JSON.stringify(inlineImportMap))}?${ + encodeURIComponent(`${Deno.cwd()}${servicePath.slice(1)}`) + }`; const envVarsObj = Deno.env.toObject(); const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]]); const forceCreate = false; @@ -82,8 +43,8 @@ serve(async (req: Request) => { // or load module source from an inline module // const maybeModuleCode = 'Deno.serve((req) => new Response("Hello from Module Code"));'; // - const cpuTimeSoftLimitMs = 10000; - const cpuTimeHardLimitMs = 20000; + const cpuTimeSoftLimitMs = 5000; + const cpuTimeHardLimitMs = 5000; return await EdgeRuntime.userWorkers.create({ servicePath, @@ -91,11 +52,19 @@ serve(async (req: Request) => { workerTimeoutMs, noModuleCache, importMapPath, - envVars, + envVars: [...envVars, [ + 'FRESH_ESBUILD_LOADER', + 'portable', + ]], forceCreate, netAccessDisabled, cpuTimeSoftLimitMs, cpuTimeHardLimitMs, + jsxImportSourceConfig: { + defaultSpecifier: 'preact', + module: 'jsx-runtime', + baseUrl: servicePath, + }, // maybeEszip, // maybeEntrypoint, // maybeModuleCode, @@ -117,23 +86,6 @@ serve(async (req: Request) => { return await worker.fetch(req, { signal }); } catch (e) { console.error(e); - - if (e instanceof Deno.errors.WorkerRequestCancelled) { - // XXX(Nyannyacha): I can't think right now how to re-poll - // inside the worker pool without exposing the error to the - // surface. - - // It is satisfied when the supervisor that handled the original - // request terminated due to reaches such as CPU time limit or - // Wall-clock limit. - // - // The current request to the worker has been canceled due to - // some internal reasons. We should repoll the worker and call - // `fetch` again. - // return await callWorker(); - console.log('cancelled!'); - } - const error = { msg: e.toString() }; return new Response( JSON.stringify(error), diff --git a/scripts/run.sh b/scripts/run.sh index 527780268..4e033ba56 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -GIT_V_TAG=0.1.1 cargo build && EDGE_RUNTIME_PORT=9998 RUST_BACKTRACE=full ./target/debug/edge-runtime "$@" start --main-service ./examples/main --event-worker ./examples/event-manager +GIT_V_TAG=0.1.1 cargo build && EDGE_RUNTIME_PORT=9998 RUST_BACKTRACE=full ./target/debug/edge-runtime "$@" start --main-service ./examples/main --event-worker ./examples/event-manager --jsx-specifier preact --jsx-module jsx-runtime