create-nifra 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/agents.d.ts +22 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +259 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth.d.ts +30 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +90 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +101 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +450 -0
- package/dist/cli.js.map +1 -0
- package/dist/db.d.ts +39 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +467 -0
- package/dist/db.js.map +1 -0
- package/package.json +50 -0
- package/template/README.md +24 -0
- package/template/gitignore +6 -0
- package/template/package.json +20 -0
- package/template/src/app.test.ts +8 -0
- package/template/src/app.ts +8 -0
- package/template/src/index.ts +5 -0
- package/template/tsconfig.json +16 -0
- package/template-isr/README.md +46 -0
- package/template-isr/backend.ts +12 -0
- package/template-isr/build.ts +23 -0
- package/template-isr/gitignore +7 -0
- package/template-isr/package.json +24 -0
- package/template-isr/routes/_404.tsx +10 -0
- package/template-isr/routes/_layout.tsx +18 -0
- package/template-isr/routes/index.tsx +34 -0
- package/template-isr/server.ts +56 -0
- package/template-isr/tsconfig.json +28 -0
- package/template-isr/worker.ts +52 -0
- package/template-isr/wrangler.toml +20 -0
- package/template-site/.dockerignore +14 -0
- package/template-site/Dockerfile +20 -0
- package/template-site/README.md +44 -0
- package/template-site/_worker.ts +17 -0
- package/template-site/backend.ts +12 -0
- package/template-site/build-bun.ts +27 -0
- package/template-site/build-deno.ts +25 -0
- package/template-site/build-node.ts +26 -0
- package/template-site/build-vercel.ts +45 -0
- package/template-site/build.ts +29 -0
- package/template-site/deno.json +7 -0
- package/template-site/framework.ts +6 -0
- package/template-site/gitignore +16 -0
- package/template-site/nifra.config.ts +9 -0
- package/template-site/package.json +37 -0
- package/template-site/routes/_404.tsx +20 -0
- package/template-site/routes/_layout.tsx +40 -0
- package/template-site/routes/index.tsx +53 -0
- package/template-site/server-bun.ts +35 -0
- package/template-site/server-deno.ts +38 -0
- package/template-site/server-node.ts +40 -0
- package/template-site/server-vercel.ts +17 -0
- package/template-site/tsconfig.json +37 -0
- package/template-site/wrangler.toml +4 -0
- package/template-site-preact/.dockerignore +14 -0
- package/template-site-preact/Dockerfile +20 -0
- package/template-site-preact/README.md +43 -0
- package/template-site-preact/_worker.ts +17 -0
- package/template-site-preact/backend.ts +12 -0
- package/template-site-preact/build-bun.ts +27 -0
- package/template-site-preact/build-deno.ts +25 -0
- package/template-site-preact/build-node.ts +26 -0
- package/template-site-preact/build-vercel.ts +45 -0
- package/template-site-preact/build.ts +29 -0
- package/template-site-preact/deno.json +7 -0
- package/template-site-preact/framework.ts +6 -0
- package/template-site-preact/gitignore +16 -0
- package/template-site-preact/nifra.config.ts +9 -0
- package/template-site-preact/package.json +34 -0
- package/template-site-preact/routes/_404.tsx +20 -0
- package/template-site-preact/routes/_layout.tsx +40 -0
- package/template-site-preact/routes/index.tsx +53 -0
- package/template-site-preact/server-bun.ts +35 -0
- package/template-site-preact/server-deno.ts +38 -0
- package/template-site-preact/server-node.ts +40 -0
- package/template-site-preact/server-vercel.ts +17 -0
- package/template-site-preact/tsconfig.json +38 -0
- package/template-site-preact/wrangler.toml +4 -0
- package/template-site-solid/.dockerignore +14 -0
- package/template-site-solid/Dockerfile +20 -0
- package/template-site-solid/README.md +43 -0
- package/template-site-solid/_worker.ts +17 -0
- package/template-site-solid/backend.ts +12 -0
- package/template-site-solid/build-bun.ts +33 -0
- package/template-site-solid/build-deno.ts +32 -0
- package/template-site-solid/build-node.ts +33 -0
- package/template-site-solid/build-vercel.ts +47 -0
- package/template-site-solid/build.ts +36 -0
- package/template-site-solid/deno.json +7 -0
- package/template-site-solid/framework.ts +6 -0
- package/template-site-solid/gitignore +16 -0
- package/template-site-solid/nifra.config.ts +14 -0
- package/template-site-solid/package.json +34 -0
- package/template-site-solid/routes/_404.tsx +20 -0
- package/template-site-solid/routes/_layout.tsx +39 -0
- package/template-site-solid/routes/index.tsx +53 -0
- package/template-site-solid/server-bun.ts +35 -0
- package/template-site-solid/server-deno.ts +38 -0
- package/template-site-solid/server-node.ts +40 -0
- package/template-site-solid/server-vercel.ts +17 -0
- package/template-site-solid/tsconfig.json +38 -0
- package/template-site-solid/wrangler.toml +4 -0
- package/template-site-svelte/.dockerignore +14 -0
- package/template-site-svelte/Dockerfile +20 -0
- package/template-site-svelte/README.md +45 -0
- package/template-site-svelte/_worker.ts +17 -0
- package/template-site-svelte/backend.ts +12 -0
- package/template-site-svelte/build-bun.ts +32 -0
- package/template-site-svelte/build-deno.ts +31 -0
- package/template-site-svelte/build-node.ts +32 -0
- package/template-site-svelte/build-vercel.ts +46 -0
- package/template-site-svelte/build.ts +35 -0
- package/template-site-svelte/deno.json +7 -0
- package/template-site-svelte/framework.ts +6 -0
- package/template-site-svelte/gitignore +16 -0
- package/template-site-svelte/nifra.config.ts +13 -0
- package/template-site-svelte/package.json +34 -0
- package/template-site-svelte/routes/_404.svelte +16 -0
- package/template-site-svelte/routes/_layout.svelte +35 -0
- package/template-site-svelte/routes/index.svelte +47 -0
- package/template-site-svelte/server-bun.ts +35 -0
- package/template-site-svelte/server-deno.ts +38 -0
- package/template-site-svelte/server-node.ts +40 -0
- package/template-site-svelte/server-vercel.ts +17 -0
- package/template-site-svelte/tsconfig.json +37 -0
- package/template-site-svelte/wrangler.toml +4 -0
- package/template-site-vue/.dockerignore +14 -0
- package/template-site-vue/Dockerfile +20 -0
- package/template-site-vue/README.md +45 -0
- package/template-site-vue/_worker.ts +17 -0
- package/template-site-vue/backend.ts +12 -0
- package/template-site-vue/build-bun.ts +37 -0
- package/template-site-vue/build-deno.ts +36 -0
- package/template-site-vue/build-node.ts +37 -0
- package/template-site-vue/build-vercel.ts +54 -0
- package/template-site-vue/build.ts +40 -0
- package/template-site-vue/deno.json +7 -0
- package/template-site-vue/framework.ts +6 -0
- package/template-site-vue/gitignore +16 -0
- package/template-site-vue/nifra.config.ts +19 -0
- package/template-site-vue/package.json +35 -0
- package/template-site-vue/routes/_404.vue +18 -0
- package/template-site-vue/routes/_layout.vue +40 -0
- package/template-site-vue/routes/index.vue +56 -0
- package/template-site-vue/server-bun.ts +35 -0
- package/template-site-vue/server-deno.ts +38 -0
- package/template-site-vue/server-node.ts +40 -0
- package/template-site-vue/server-vercel.ts +17 -0
- package/template-site-vue/tsconfig.json +37 -0
- package/template-site-vue/wrangler.toml +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The nifra CLI's config — read by `nifra dev|build|start`. It's imported ONLY by the CLI (which runs on
|
|
2
|
+
// Bun), so it can eagerly import the Vite plugin + the Svelte compiler — keeping them OUT of the edge
|
|
3
|
+
// worker bundle that `framework.ts` (the render-adapter source the entries import) must stay free of.
|
|
4
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
|
5
|
+
import { svelteBunPlugin } from "@nifrajs/web-svelte/plugin"
|
|
6
|
+
|
|
7
|
+
export { adapter } from "./framework"
|
|
8
|
+
export const clientModule = "@nifrajs/web-svelte/client"
|
|
9
|
+
// `nifra dev` HMR: Vite + the official Svelte plugin compile/HMR the .svelte client modules.
|
|
10
|
+
export const vitePlugins = [svelte()]
|
|
11
|
+
// `nifra build` (client) + `nifra start`/`nifra dev` (Bun-side SSR) compile .svelte via these.
|
|
12
|
+
export const clientPlugins = [svelteBunPlugin("dom")]
|
|
13
|
+
export const serverPlugins = [svelteBunPlugin("ssr")]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nifra-site",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "nifra dev",
|
|
8
|
+
"preview": "bunx wrangler pages dev dist",
|
|
9
|
+
"build": "bun run build.ts",
|
|
10
|
+
"build:bun": "bun run build-bun.ts",
|
|
11
|
+
"build:node": "bun run build-node.ts",
|
|
12
|
+
"build:deno": "bun run build-deno.ts",
|
|
13
|
+
"build:vercel": "bun run build-vercel.ts",
|
|
14
|
+
"start": "bun dist-bun/server-bun.js",
|
|
15
|
+
"start:node": "node dist-node/server-node.js",
|
|
16
|
+
"deploy:cf": "wrangler pages deploy dist",
|
|
17
|
+
"deploy:vercel": "vercel deploy --prebuilt"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@nifrajs/client": "^0.1.0",
|
|
21
|
+
"@nifrajs/core": "^0.1.0",
|
|
22
|
+
"@nifrajs/deno": "^0.1.0",
|
|
23
|
+
"@nifrajs/node": "^0.1.0",
|
|
24
|
+
"@nifrajs/web": "^0.1.0",
|
|
25
|
+
"@nifrajs/web-svelte": "^0.1.0",
|
|
26
|
+
"svelte": "^5.3.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
30
|
+
"@nifrajs/cli": "^0.1.0",
|
|
31
|
+
"typescript": "^6.0.3",
|
|
32
|
+
"vite": "^8.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export const meta = { title: "Not found" }
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
// nifra's catch-all renders _404 without the layout chain, so it wraps itself in the root Layout.
|
|
7
|
+
import Layout from "./_layout.svelte"
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<Layout>
|
|
11
|
+
<section class="hero">
|
|
12
|
+
<h1><span class="grad">404</span></h1>
|
|
13
|
+
<p>That page doesn't exist.</p>
|
|
14
|
+
<a class="btn" href="/">← Back home</a>
|
|
15
|
+
</section>
|
|
16
|
+
</Layout>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- Root layout — wraps every page via the `children` snippet (the Chain fold passes the page there). -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
let { children } = $props()
|
|
4
|
+
const css = `
|
|
5
|
+
:root { --bg:#0a0a0f; --panel:#14141c; --border:#26263a; --fg:#e8e8f0; --muted:#9a9ab0; --accent:#7c5cff; --accent2:#00d4ff; }
|
|
6
|
+
* { box-sizing: border-box; } html { color-scheme: dark; }
|
|
7
|
+
body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.6 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; -webkit-font-smoothing:antialiased; }
|
|
8
|
+
a { color:var(--accent2); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
9
|
+
.wrap { max-width:840px; margin:0 auto; padding:0 24px; }
|
|
10
|
+
header.site { border-bottom:1px solid var(--border); } header.site .wrap { display:flex; align-items:center; justify-content:space-between; height:62px; }
|
|
11
|
+
.logo, .logo b { font-weight:800; font-size:20px; } .logo b { background:linear-gradient(90deg,var(--accent),var(--accent2)); -webkit-background-clip:text; background-clip:text; color:transparent; }
|
|
12
|
+
.hero { text-align:center; padding:80px 0 56px; }
|
|
13
|
+
.hero h1 { font-size:clamp(36px,6vw,56px); line-height:1.05; margin:0 0 16px; letter-spacing:-0.03em; }
|
|
14
|
+
.hero h1 .grad { background:linear-gradient(90deg,var(--accent),var(--accent2)); -webkit-background-clip:text; background-clip:text; color:transparent; }
|
|
15
|
+
.hero p { font-size:19px; color:var(--muted); max-width:560px; margin:0 auto 28px; }
|
|
16
|
+
.card { background:var(--panel); border:1px solid var(--border); border-radius:14px; padding:24px 28px; display:flex; align-items:center; justify-content:space-between; gap:24px; flex-wrap:wrap; }
|
|
17
|
+
.card h3 { margin:0 0 4px; } .card p { margin:0; color:var(--muted); font-size:14px; }
|
|
18
|
+
.count { font-size:34px; font-weight:800; font-variant-numeric:tabular-nums; min-width:44px; text-align:center; background:linear-gradient(90deg,var(--accent),var(--accent2)); -webkit-background-clip:text; background-clip:text; color:transparent; }
|
|
19
|
+
.btn { background:linear-gradient(90deg,var(--accent),var(--accent2)); color:#0a0a0f; font:inherit; font-weight:700; border:none; cursor:pointer; padding:11px 20px; border-radius:10px; }
|
|
20
|
+
footer.site { border-top:1px solid var(--border); color:var(--muted); font-size:14px; } footer.site .wrap { padding:24px; }
|
|
21
|
+
`.trim()
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<div id="app">
|
|
25
|
+
<!-- Inject the global stylesheet verbatim (Svelte's component <style> is scoped). -->
|
|
26
|
+
{@html `<style>${css}</style>`}
|
|
27
|
+
<header class="site">
|
|
28
|
+
<div class="wrap">
|
|
29
|
+
<a href="/" class="logo"><b>nifra</b></a>
|
|
30
|
+
<a href="https://github.com">GitHub</a>
|
|
31
|
+
</div>
|
|
32
|
+
</header>
|
|
33
|
+
<main class="wrap">{@render children()}</main>
|
|
34
|
+
<footer class="site"><div class="wrap">Built with nifra</div></footer>
|
|
35
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import type { ActionArgs, LoaderArgs } from "@nifrajs/client"
|
|
3
|
+
import type { backend } from "../backend"
|
|
4
|
+
|
|
5
|
+
export const meta = {
|
|
6
|
+
title: "nifra site",
|
|
7
|
+
meta: [{ name: "description", content: "A nifra + Svelte SSR site, deployable to every runtime." }],
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Loader runs on the server (in-process during SSR). The action handles the form POST; after a
|
|
11
|
+
// client submit the loader revalidates with no full reload (progressive enhancement).
|
|
12
|
+
export async function loader({ api }: LoaderArgs<typeof backend>) {
|
|
13
|
+
const res = await api.count.get()
|
|
14
|
+
return { count: res.data?.count ?? 0 }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function action({ api }: ActionArgs<typeof backend>) {
|
|
18
|
+
await api.count.post()
|
|
19
|
+
return { ok: true }
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<script lang="ts">
|
|
24
|
+
let { data } = $props()
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<section class="hero">
|
|
28
|
+
<h1>Your nifra app,<br /><span class="grad">everywhere.</span></h1>
|
|
29
|
+
<p>
|
|
30
|
+
SSR + hydration, end-to-end types, Svelte. One source — deploy to Cloudflare Pages, Node, Deno,
|
|
31
|
+
or Vercel Edge. Edit <code>routes/index.svelte</code> to begin.
|
|
32
|
+
</p>
|
|
33
|
+
</section>
|
|
34
|
+
|
|
35
|
+
<div class="card">
|
|
36
|
+
<div>
|
|
37
|
+
<h3>Live full-stack loop</h3>
|
|
38
|
+
<p>
|
|
39
|
+
Count is rendered by a typed <code>loader</code>, incremented by an <code>action</code>,
|
|
40
|
+
revalidated with no full reload.
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
<form method="post" style="display:flex;align-items:center;gap:16px">
|
|
44
|
+
<span class="count">{data.count}</span>
|
|
45
|
+
<button class="btn" type="submit">increment →</button>
|
|
46
|
+
</form>
|
|
47
|
+
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { inProcessClient } from "@nifrajs/client"
|
|
2
|
+
import { createWebApp } from "@nifrajs/web"
|
|
3
|
+
import { backend } from "./backend"
|
|
4
|
+
import { adapter } from "./framework"
|
|
5
|
+
import { clientEntry, manifest } from "./server-manifest"
|
|
6
|
+
|
|
7
|
+
const app = createWebApp({
|
|
8
|
+
adapter,
|
|
9
|
+
manifest,
|
|
10
|
+
clientEntry,
|
|
11
|
+
api: inProcessClient(backend),
|
|
12
|
+
title: "nifra site",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Bun (nifra's flagship runtime): Bun.serve handles app.fetch; serve the client bundle (/assets/*)
|
|
16
|
+
// from disk with Bun.file. Assets live next to this file in the built dist-bun/ output.
|
|
17
|
+
const ASSETS = new URL("./assets/", import.meta.url)
|
|
18
|
+
const TYPES: Readonly<Record<string, string>> = { js: "text/javascript", css: "text/css" }
|
|
19
|
+
|
|
20
|
+
const server = Bun.serve({
|
|
21
|
+
port: Number(Bun.env.PORT ?? 3000),
|
|
22
|
+
async fetch(req) {
|
|
23
|
+
const { pathname } = new URL(req.url)
|
|
24
|
+
if (pathname.startsWith("/assets/")) {
|
|
25
|
+
const name = pathname.slice("/assets/".length)
|
|
26
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) return new Response("bad request", { status: 400 })
|
|
27
|
+
const file = Bun.file(new URL(name, ASSETS))
|
|
28
|
+
if (!(await file.exists())) return new Response("not found", { status: 404 })
|
|
29
|
+
const ext = name.slice(name.lastIndexOf(".") + 1)
|
|
30
|
+
return new Response(file, { headers: { "content-type": TYPES[ext] ?? "application/octet-stream" } })
|
|
31
|
+
}
|
|
32
|
+
return app.fetch(req)
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
console.log(`nifra (Bun) → http://localhost:${server.port}`)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { inProcessClient } from "@nifrajs/client"
|
|
2
|
+
import { serve } from "@nifrajs/deno"
|
|
3
|
+
import { createWebApp } from "@nifrajs/web"
|
|
4
|
+
import { backend } from "./backend"
|
|
5
|
+
import { adapter } from "./framework"
|
|
6
|
+
import { clientEntry, manifest } from "./server-manifest"
|
|
7
|
+
|
|
8
|
+
const app = createWebApp({
|
|
9
|
+
adapter,
|
|
10
|
+
manifest,
|
|
11
|
+
clientEntry,
|
|
12
|
+
api: inProcessClient(backend),
|
|
13
|
+
title: "nifra site",
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const ASSETS = new URL("./assets/", import.meta.url)
|
|
17
|
+
const TYPES: Readonly<Record<string, string>> = { js: "text/javascript", css: "text/css" }
|
|
18
|
+
|
|
19
|
+
await serve(
|
|
20
|
+
{
|
|
21
|
+
async fetch(req) {
|
|
22
|
+
const { pathname } = new URL(req.url)
|
|
23
|
+
if (pathname.startsWith("/assets/")) {
|
|
24
|
+
const name = pathname.slice("/assets/".length)
|
|
25
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) return new Response("bad request", { status: 400 })
|
|
26
|
+
try {
|
|
27
|
+
const body = await Deno.readFile(new URL(name, ASSETS))
|
|
28
|
+
const ext = name.slice(name.lastIndexOf(".") + 1)
|
|
29
|
+
return new Response(body, { headers: { "content-type": TYPES[ext] ?? "application/octet-stream" } })
|
|
30
|
+
} catch {
|
|
31
|
+
return new Response("not found", { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return app.fetch(req)
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{ port: Number(Deno.env.get("PORT") ?? "3000") },
|
|
38
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises"
|
|
2
|
+
import { inProcessClient } from "@nifrajs/client"
|
|
3
|
+
import { serve } from "@nifrajs/node"
|
|
4
|
+
import { createWebApp } from "@nifrajs/web"
|
|
5
|
+
import { backend } from "./backend"
|
|
6
|
+
import { adapter } from "./framework"
|
|
7
|
+
import { clientEntry, manifest } from "./server-manifest"
|
|
8
|
+
|
|
9
|
+
const app = createWebApp({
|
|
10
|
+
adapter,
|
|
11
|
+
manifest,
|
|
12
|
+
clientEntry,
|
|
13
|
+
api: inProcessClient(backend),
|
|
14
|
+
title: "nifra site",
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// @nifrajs/node serves app.fetch only — so serve /assets/* (the client bundle) from disk here.
|
|
18
|
+
const ASSETS = new URL("./assets/", import.meta.url)
|
|
19
|
+
const TYPES: Readonly<Record<string, string>> = { js: "text/javascript", css: "text/css" }
|
|
20
|
+
|
|
21
|
+
await serve(
|
|
22
|
+
{
|
|
23
|
+
async fetch(req) {
|
|
24
|
+
const { pathname } = new URL(req.url)
|
|
25
|
+
if (pathname.startsWith("/assets/")) {
|
|
26
|
+
const name = pathname.slice("/assets/".length)
|
|
27
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name)) return new Response("bad request", { status: 400 })
|
|
28
|
+
try {
|
|
29
|
+
const body = await readFile(new URL(name, ASSETS))
|
|
30
|
+
const ext = name.slice(name.lastIndexOf(".") + 1)
|
|
31
|
+
return new Response(body, { headers: { "content-type": TYPES[ext] ?? "application/octet-stream" } })
|
|
32
|
+
} catch {
|
|
33
|
+
return new Response("not found", { status: 404 })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return app.fetch(req)
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{ port: Number(process.env.PORT ?? 3000) },
|
|
40
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { inProcessClient } from "@nifrajs/client"
|
|
2
|
+
import { createWebApp } from "@nifrajs/web"
|
|
3
|
+
import { backend } from "./backend"
|
|
4
|
+
import { adapter } from "./framework"
|
|
5
|
+
import { clientEntry, manifest } from "./server-manifest"
|
|
6
|
+
|
|
7
|
+
const app = createWebApp({
|
|
8
|
+
adapter,
|
|
9
|
+
manifest,
|
|
10
|
+
clientEntry,
|
|
11
|
+
api: inProcessClient(backend),
|
|
12
|
+
title: "nifra site",
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Vercel Edge Function — Vercel serves /assets/* from its CDN, so this only SSRs page routes.
|
|
16
|
+
export const config = { runtime: "edge" }
|
|
17
|
+
export default (req: Request): Response | Promise<Response> => app.fetch(req)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noUncheckedIndexedAccess": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"types": ["svelte"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["**/*.ts"],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"node_modules",
|
|
17
|
+
"dist",
|
|
18
|
+
"dist-bun",
|
|
19
|
+
"dist-node",
|
|
20
|
+
"dist-deno",
|
|
21
|
+
"dist-vercel",
|
|
22
|
+
".nifra",
|
|
23
|
+
".wrangler",
|
|
24
|
+
".vercel",
|
|
25
|
+
"server-manifest.ts",
|
|
26
|
+
"server-bun.ts",
|
|
27
|
+
"server-node.ts",
|
|
28
|
+
"server-deno.ts",
|
|
29
|
+
"server-vercel.ts",
|
|
30
|
+
"_worker.ts",
|
|
31
|
+
"build.ts",
|
|
32
|
+
"build-bun.ts",
|
|
33
|
+
"build-node.ts",
|
|
34
|
+
"build-deno.ts",
|
|
35
|
+
"build-vercel.ts"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# nifra on Node, containerized. Build with Bun (nifra's build toolchain), then run the self-contained
|
|
2
|
+
# bundle on a slim Node image — the final image carries no build tools or node_modules.
|
|
3
|
+
# docker build -t my-nifra-app .
|
|
4
|
+
# docker run -p 3000:3000 my-nifra-app
|
|
5
|
+
FROM oven/bun:1 AS build
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
# Copy manifests first so the install layer caches across source-only changes.
|
|
8
|
+
COPY package.json bun.lock* ./
|
|
9
|
+
RUN bun install
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN bun run build:node
|
|
12
|
+
|
|
13
|
+
FROM node:22-slim AS run
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
ENV NODE_ENV=production
|
|
16
|
+
ENV PORT=3000
|
|
17
|
+
# build:node emits a self-contained server-bundle + the client assets it serves from disk.
|
|
18
|
+
COPY --from=build /app/dist-node ./dist-node
|
|
19
|
+
EXPOSE 3000
|
|
20
|
+
CMD ["node", "dist-node/server-node.js"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# nifra site (Vue)
|
|
2
|
+
|
|
3
|
+
A nifra + **Vue** SSR site — file-based routes, typed loaders/actions, hydration — that deploys to
|
|
4
|
+
**Bun, Node (Docker), Deno Deploy, Cloudflare Pages, and Vercel Edge from one source**. `app.fetch`
|
|
5
|
+
is Web-standard, so only the server entry + build target differ per platform.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun install
|
|
9
|
+
bun run dev # nifra dev → true-HMR dev server (Vite + nifra SSR) at http://localhost:3000
|
|
10
|
+
bun run preview # wrangler pages dev dist → preview the built Cloudflare bundle (run `bun run build` first)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Routes are **Vue SFCs** (`.vue`) under `routes/` — `index.vue` (landing + a live loader/action
|
|
14
|
+
counter), `_layout.vue` (chrome via the default slot), `_404.vue`. A route SFC's plain `<script>`
|
|
15
|
+
carries nifra's `loader`/`action`/`meta`; `<script setup>` + `<template>` are the component. The
|
|
16
|
+
build compiles them with `vueBunPlugin` (`@nifrajs/web-vue/plugin`). The frontend adapter is one line in
|
|
17
|
+
`framework.ts`; data lives behind `backend.ts`.
|
|
18
|
+
|
|
19
|
+
## Deploy — pick a target
|
|
20
|
+
|
|
21
|
+
| target | build | deploy |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| **Bun** (flagship) | `bun run build:bun` | `bun run start` — any host |
|
|
24
|
+
| **Node** | `bun run build:node` | `docker build -t app . && docker run -p 3000:3000 app` (or `bun run start:node`) |
|
|
25
|
+
| **Deno Deploy** | `bun run build:deno` | `deployctl deploy --prod --entrypoint=dist-deno/server-deno.js` |
|
|
26
|
+
| **Cloudflare Pages** | `bun run build` | `bun run deploy:cf` |
|
|
27
|
+
| **Vercel Edge** | `bun run build:vercel` | `bun run deploy:vercel` |
|
|
28
|
+
|
|
29
|
+
The routes, `backend.ts`, and the client bundle are shared; each `build*` script swaps `buildServer`'s
|
|
30
|
+
target/conditions and the server entry. nifra never enters your cloud credentials — it scaffolds the
|
|
31
|
+
configs (`Dockerfile`, `deno.json`, `wrangler.toml`, Vercel Build Output API); you run the vendor CLI.
|
|
32
|
+
|
|
33
|
+
## Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
routes/ index.vue (landing), _layout.vue (chrome), _404.vue (Vue SFCs)
|
|
37
|
+
framework.ts the adapter (vueAdapter) — imported by the server entries (edge-bundlable)
|
|
38
|
+
nifra.config.ts the nifra CLI's dev/build config (adapter + clientModule + Vue plugins) — read by `nifra dev`
|
|
39
|
+
backend.ts your contract (loaders/actions call it in-process)
|
|
40
|
+
server-bun.ts Bun entry (Bun.serve) build-bun.ts → dist-bun/
|
|
41
|
+
_worker.ts Cloudflare Pages entry build.ts → dist/
|
|
42
|
+
server-node.ts Node entry (@nifrajs/node) build-node.ts → dist-node/ (Dockerfile)
|
|
43
|
+
server-deno.ts Deno entry (@nifrajs/deno) build-deno.ts → dist-deno/ (deno.json)
|
|
44
|
+
server-vercel.ts Vercel Edge function build-vercel.ts → .vercel/output/
|
|
45
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { inProcessClient } from "@nifrajs/client"
|
|
2
|
+
import { toFetchHandler } from "@nifrajs/core"
|
|
3
|
+
import { createWebApp } from "@nifrajs/web"
|
|
4
|
+
import { backend } from "./backend"
|
|
5
|
+
import { adapter } from "./framework"
|
|
6
|
+
import { clientEntry, manifest } from "./server-manifest"
|
|
7
|
+
|
|
8
|
+
const app = createWebApp({
|
|
9
|
+
adapter,
|
|
10
|
+
manifest,
|
|
11
|
+
clientEntry,
|
|
12
|
+
api: inProcessClient(backend),
|
|
13
|
+
title: "nifra site",
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Cloudflare Pages: _routes.json serves /assets/* statically; everything else hits this (SSR).
|
|
17
|
+
export default toFetchHandler(app)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { server } from "@nifrajs/core"
|
|
2
|
+
|
|
3
|
+
// Your backend contract — page loaders/actions call it in-process during SSR (no network). Replace
|
|
4
|
+
// the demo counter with real data (KV/D1/Postgres/…); on the edge, reach bindings via `c.env`.
|
|
5
|
+
let count = 0
|
|
6
|
+
|
|
7
|
+
export const backend = server()
|
|
8
|
+
.get("/count", () => ({ count }))
|
|
9
|
+
.post("/count", () => {
|
|
10
|
+
count += 1
|
|
11
|
+
return { count }
|
|
12
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Build for Bun (nifra's flagship runtime) → dist-bun/ (server-bun.js + client assets).
|
|
2
|
+
// `bun run build:bun`. Run: `bun dist-bun/server-bun.js`.
|
|
3
|
+
import { cpSync, mkdirSync, rmSync } from "node:fs"
|
|
4
|
+
import { buildClient, buildServer } from "@nifrajs/web/build"
|
|
5
|
+
import { vueBunPlugin } from "@nifrajs/web-vue/plugin"
|
|
6
|
+
|
|
7
|
+
const dir = import.meta.dir
|
|
8
|
+
const dist = `${dir}/dist-bun`
|
|
9
|
+
const define = {
|
|
10
|
+
"process.env.NODE_ENV": '"production"',
|
|
11
|
+
__VUE_OPTIONS_API__: "true",
|
|
12
|
+
__VUE_PROD_DEVTOOLS__: "false",
|
|
13
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
|
|
14
|
+
}
|
|
15
|
+
rmSync(dist, { recursive: true, force: true })
|
|
16
|
+
mkdirSync(`${dist}/assets`, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const client = await buildClient({
|
|
19
|
+
routesDir: `${dir}/routes`,
|
|
20
|
+
outDir: `${dist}/assets`,
|
|
21
|
+
clientModule: "@nifrajs/web-vue/client",
|
|
22
|
+
plugins: [vueBunPlugin("dom")], // compile .vue SFCs → client bundle
|
|
23
|
+
conditions: ["bun", "browser"],
|
|
24
|
+
define,
|
|
25
|
+
})
|
|
26
|
+
const { worker } = await buildServer({
|
|
27
|
+
routesDir: `${dir}/routes`,
|
|
28
|
+
serverEntry: `${dir}/server-bun.ts`,
|
|
29
|
+
outDir: `${dir}/.build-bun`,
|
|
30
|
+
target: "bun",
|
|
31
|
+
clientEntry: client.entry,
|
|
32
|
+
plugins: [vueBunPlugin("ssr")], // compile .vue SFCs → SSR
|
|
33
|
+
define,
|
|
34
|
+
})
|
|
35
|
+
cpSync(worker, `${dist}/server-bun.js`)
|
|
36
|
+
rmSync(`${dir}/.build-bun`, { recursive: true, force: true })
|
|
37
|
+
console.log("Bun output → dist-bun (run: bun dist-bun/server-bun.js)")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Build for Deno → dist-deno/ (server-deno.js + client assets). `bun run build:deno`.
|
|
2
|
+
// Run: `deno task start` (or deployctl for Deno Deploy).
|
|
3
|
+
import { cpSync, mkdirSync, rmSync } from "node:fs"
|
|
4
|
+
import { buildClient, buildServer } from "@nifrajs/web/build"
|
|
5
|
+
import { vueBunPlugin } from "@nifrajs/web-vue/plugin"
|
|
6
|
+
|
|
7
|
+
const dir = import.meta.dir
|
|
8
|
+
const dist = `${dir}/dist-deno`
|
|
9
|
+
const define = {
|
|
10
|
+
"process.env.NODE_ENV": '"production"',
|
|
11
|
+
__VUE_OPTIONS_API__: "true",
|
|
12
|
+
__VUE_PROD_DEVTOOLS__: "false",
|
|
13
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
|
|
14
|
+
}
|
|
15
|
+
rmSync(dist, { recursive: true, force: true })
|
|
16
|
+
mkdirSync(`${dist}/assets`, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const client = await buildClient({
|
|
19
|
+
routesDir: `${dir}/routes`,
|
|
20
|
+
outDir: `${dist}/assets`,
|
|
21
|
+
clientModule: "@nifrajs/web-vue/client",
|
|
22
|
+
plugins: [vueBunPlugin("dom")], // compile .vue SFCs → client bundle
|
|
23
|
+
conditions: ["bun", "browser"],
|
|
24
|
+
define,
|
|
25
|
+
})
|
|
26
|
+
const { worker } = await buildServer({
|
|
27
|
+
routesDir: `${dir}/routes`,
|
|
28
|
+
serverEntry: `${dir}/server-deno.ts`,
|
|
29
|
+
outDir: `${dir}/.build-deno`,
|
|
30
|
+
clientEntry: client.entry,
|
|
31
|
+
plugins: [vueBunPlugin("ssr")], // compile .vue SFCs → SSR
|
|
32
|
+
define,
|
|
33
|
+
})
|
|
34
|
+
cpSync(worker, `${dist}/server-deno.js`)
|
|
35
|
+
rmSync(`${dir}/.build-deno`, { recursive: true, force: true })
|
|
36
|
+
console.log("Deno output → dist-deno (run: deno run -A dist-deno/server-deno.js)")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Build for Node → dist-node/ (server-node.js + client assets). `bun run build:node`.
|
|
2
|
+
// Run: `node dist-node/server-node.js` (or the Dockerfile).
|
|
3
|
+
import { cpSync, mkdirSync, rmSync } from "node:fs"
|
|
4
|
+
import { buildClient, buildServer } from "@nifrajs/web/build"
|
|
5
|
+
import { vueBunPlugin } from "@nifrajs/web-vue/plugin"
|
|
6
|
+
|
|
7
|
+
const dir = import.meta.dir
|
|
8
|
+
const dist = `${dir}/dist-node`
|
|
9
|
+
const define = {
|
|
10
|
+
"process.env.NODE_ENV": '"production"',
|
|
11
|
+
__VUE_OPTIONS_API__: "true",
|
|
12
|
+
__VUE_PROD_DEVTOOLS__: "false",
|
|
13
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
|
|
14
|
+
}
|
|
15
|
+
rmSync(dist, { recursive: true, force: true })
|
|
16
|
+
mkdirSync(`${dist}/assets`, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const client = await buildClient({
|
|
19
|
+
routesDir: `${dir}/routes`,
|
|
20
|
+
outDir: `${dist}/assets`,
|
|
21
|
+
clientModule: "@nifrajs/web-vue/client",
|
|
22
|
+
plugins: [vueBunPlugin("dom")], // compile .vue SFCs → client bundle
|
|
23
|
+
conditions: ["bun", "browser"],
|
|
24
|
+
define,
|
|
25
|
+
})
|
|
26
|
+
const { worker } = await buildServer({
|
|
27
|
+
routesDir: `${dir}/routes`,
|
|
28
|
+
serverEntry: `${dir}/server-node.ts`,
|
|
29
|
+
outDir: `${dir}/.build-node`,
|
|
30
|
+
target: "node",
|
|
31
|
+
clientEntry: client.entry,
|
|
32
|
+
plugins: [vueBunPlugin("ssr")], // compile .vue SFCs → SSR
|
|
33
|
+
define,
|
|
34
|
+
})
|
|
35
|
+
cpSync(worker, `${dist}/server-node.js`)
|
|
36
|
+
rmSync(`${dir}/.build-node`, { recursive: true, force: true })
|
|
37
|
+
console.log("Node output → dist-node (run: node dist-node/server-node.js)")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Build for Vercel, emitting Vercel's Build Output API v3 at .vercel/output/ so it deploys with no
|
|
2
|
+
// framework preset: `vercel deploy --prebuilt`. Layout:
|
|
3
|
+
// .vercel/output/config.json — serve static files, else SSR via the function
|
|
4
|
+
// .vercel/output/static/assets/<client bundle> — Vercel's CDN serves these directly
|
|
5
|
+
// .vercel/output/functions/index.func/index.js — the Edge SSR function (+ .vc-config.json)
|
|
6
|
+
import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
7
|
+
import { buildClient, buildServer } from "@nifrajs/web/build"
|
|
8
|
+
import { vueBunPlugin } from "@nifrajs/web-vue/plugin"
|
|
9
|
+
|
|
10
|
+
const dir = import.meta.dir
|
|
11
|
+
const out = `${dir}/.vercel/output`
|
|
12
|
+
const fn = `${out}/functions/index.func`
|
|
13
|
+
const define = {
|
|
14
|
+
"process.env.NODE_ENV": '"production"',
|
|
15
|
+
__VUE_OPTIONS_API__: "true",
|
|
16
|
+
__VUE_PROD_DEVTOOLS__: "false",
|
|
17
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
|
|
18
|
+
}
|
|
19
|
+
rmSync(`${dir}/.vercel`, { recursive: true, force: true })
|
|
20
|
+
mkdirSync(`${out}/static/assets`, { recursive: true })
|
|
21
|
+
mkdirSync(fn, { recursive: true })
|
|
22
|
+
|
|
23
|
+
const client = await buildClient({
|
|
24
|
+
routesDir: `${dir}/routes`,
|
|
25
|
+
outDir: `${out}/static/assets`,
|
|
26
|
+
clientModule: "@nifrajs/web-vue/client",
|
|
27
|
+
plugins: [vueBunPlugin("dom")], // compile .vue SFCs → client bundle
|
|
28
|
+
conditions: ["bun", "browser"],
|
|
29
|
+
define,
|
|
30
|
+
})
|
|
31
|
+
const { worker } = await buildServer({
|
|
32
|
+
routesDir: `${dir}/routes`,
|
|
33
|
+
serverEntry: `${dir}/server-vercel.ts`,
|
|
34
|
+
outDir: `${dir}/.build-vercel`,
|
|
35
|
+
clientEntry: client.entry,
|
|
36
|
+
plugins: [vueBunPlugin("ssr")], // compile .vue SFCs → SSR
|
|
37
|
+
define,
|
|
38
|
+
})
|
|
39
|
+
cpSync(worker, `${fn}/index.js`)
|
|
40
|
+
rmSync(`${dir}/.build-vercel`, { recursive: true, force: true })
|
|
41
|
+
|
|
42
|
+
writeFileSync(
|
|
43
|
+
`${fn}/.vc-config.json`,
|
|
44
|
+
`${JSON.stringify({ runtime: "edge", entrypoint: "index.js" }, null, 2)}\n`,
|
|
45
|
+
)
|
|
46
|
+
writeFileSync(
|
|
47
|
+
`${out}/config.json`,
|
|
48
|
+
`${JSON.stringify(
|
|
49
|
+
{ version: 3, routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index" }] },
|
|
50
|
+
null,
|
|
51
|
+
2,
|
|
52
|
+
)}\n`,
|
|
53
|
+
)
|
|
54
|
+
console.log("Vercel output → .vercel/output (deploy: vercel deploy --prebuilt)")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Build for Cloudflare Pages → dist/ (_worker.js + _routes.json + client assets). `bun run build`.
|
|
2
|
+
import { cpSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { buildClient, buildServer } from "@nifrajs/web/build"
|
|
4
|
+
import { vueBunPlugin } from "@nifrajs/web-vue/plugin"
|
|
5
|
+
|
|
6
|
+
const dir = import.meta.dir
|
|
7
|
+
const dist = `${dir}/dist`
|
|
8
|
+
// Vue feature flags: silence "feature flag not explicitly defined" warnings + trim dev-only code.
|
|
9
|
+
const define = {
|
|
10
|
+
"process.env.NODE_ENV": '"production"',
|
|
11
|
+
__VUE_OPTIONS_API__: "true",
|
|
12
|
+
__VUE_PROD_DEVTOOLS__: "false",
|
|
13
|
+
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
|
|
14
|
+
}
|
|
15
|
+
rmSync(dist, { recursive: true, force: true })
|
|
16
|
+
mkdirSync(`${dist}/assets`, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const client = await buildClient({
|
|
19
|
+
routesDir: `${dir}/routes`,
|
|
20
|
+
outDir: `${dist}/assets`,
|
|
21
|
+
clientModule: "@nifrajs/web-vue/client",
|
|
22
|
+
plugins: [vueBunPlugin("dom")], // compile .vue SFCs → client bundle
|
|
23
|
+
conditions: ["bun", "browser"],
|
|
24
|
+
define,
|
|
25
|
+
})
|
|
26
|
+
const { worker } = await buildServer({
|
|
27
|
+
routesDir: `${dir}/routes`,
|
|
28
|
+
serverEntry: `${dir}/_worker.ts`,
|
|
29
|
+
outDir: `${dir}/.build`,
|
|
30
|
+
clientEntry: client.entry,
|
|
31
|
+
plugins: [vueBunPlugin("ssr")], // compile .vue SFCs → SSR
|
|
32
|
+
define,
|
|
33
|
+
})
|
|
34
|
+
cpSync(worker, `${dist}/_worker.js`)
|
|
35
|
+
rmSync(`${dir}/.build`, { recursive: true, force: true })
|
|
36
|
+
writeFileSync(
|
|
37
|
+
`${dist}/_routes.json`,
|
|
38
|
+
`${JSON.stringify({ version: 1, include: ["/*"], exclude: ["/assets/*"] }, null, 2)}\n`,
|
|
39
|
+
)
|
|
40
|
+
console.log("Cloudflare Pages output → dist (deploy: wrangler pages deploy dist)")
|