文档全栈框架
从 SvelteKit 到 Nexy
在 Nexy 中,SvelteKit 项目将其 Svelte 页面和布局保留为普通 .svelte 文件,而 +server.ts、+page.server.ts 加载,表单操作和挂钩移动到它们旁边的 Python 文件。
下面的每个部分都展示了 SvelteKit 习惯,然后是 Nexy 中的相同内容。
首先选择模式
| 您的 SvelteKit 应用程序是 | 邻接模式 | 页面是 |
|---|---|---|
| 具有客户端导航功能的应用程序 | 客户端模式,useCSR = True |
src/routes/ 下的 .svelte 文件 |
| 主要内容,一些互动部分 | 服务器模式,默认 | .nexy 页面包含 Svelte 岛屿 |
from nexy.core.models import NexyConfigModelfrom nexy.frontend import svelte class NexyConfig(NexyConfigModel): useFF = svelte() useCSR = True useAliases = {"@": "src"}除非另有说明,以下部分均使用客户端模式。
+page.svelte
<h1>About</h1>页面是文件本身,以其 URL 命名:
<h1>About</h1>src/routes/+page.svelte 是 src/routes/index.svelte。
动态路线和 params
<script lang="ts"> import { page } from "$app/state";</script> <h1>{page.params.slug}</h1>这些段作为 props 到达页面:
<script lang="ts"> let { slug }: { slug: string } = $props();</script> <h1>{slug}</h1>在任何其他组件中,@nexy/client 的 params 存储保存它们:$params.slug。 [...rest] 和 (group) 文件夹的工作方式与 SvelteKit 中相同。
+layout.svelte
<script lang="ts"> let { children } = $props();</script> <header>My app</header><main>{@render children()}</main>不存在 app.html:根 layout.svelte 拥有该文档。
<script lang="ts"> import type { Snippet } from "svelte"; let { children }: { children?: Snippet } = $props();</script> <html> <body> <header>My app</header> <main>{@render children?.()}</main> </body></html>+server.ts
import { json } from "@sveltejs/kit"; export function GET({ params }) { return json({ id: Number(params.id) });}相同的 GET 导出,在 Python 中:
async def GET(id: int): return {"id": id}from pydantic import BaseModel, Field from nexy.http import JSONResponse class NewItem(BaseModel): name: str price: float = Field(gt=0) async def POST(item: NewItem): return JSONResponse(item.model_dump(), status_code=201)error()
import { error, json } from "@sveltejs/kit"; export function GET({ params, url }) { if (Number(params.id) > 100) error(404, "No such order"); return json({ id: Number(params.id), currency: url.searchParams.get("currency") ?? "EUR" });}from nexy.http import HTTPException async def GET(id: int, currency: str = "EUR"): if id > 100: raise HTTPException(status_code=404, detail="No such order") return {"id": id, "currency": currency}load 在 +page.server.ts
export async function load() { return { users: await db.user.findMany() };}<script lang="ts"> let { data } = $props();</script> <ul> {#each data.users as user (user.id)} <li>{user.name}</li> {/each}</ul>在客户端模式下,页面向处理程序询问其数据:
async def GET(): return [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]<script lang="ts"> let users = $state<{ id: number; name: string }[]>([]); $effect(() => { fetch("/api/users").then((response) => response.json()).then((list) => (users = list)); });</script> <ul> {#each users as user (user.id)} <li>{user.name}</li> {/each}</ul>在服务器模式下, .nexy 页面的 frontmatter 是 load 函数,它在服务器渲染时运行:
users = [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]<ul> {% for user in users %} <li>{{ user.name }}</li> {% endfor %}</ul>表单操作和 use:enhance
export const actions = { default: async ({ request }) => { const data = await request.formData(); await db.todo.create({ title: data.get("title") }); },};<script lang="ts"> import { enhance } from "$app/forms";</script> <form method="POST" use:enhance> <input name="title" /> <button>Add</button></form>一个动作是一个带有生成的、类型化客户端的 Python 函数:
from pydantic import BaseModel from nexy.decorators import Action, revalidate class Todo(BaseModel): title: str done: bool = False @Actionasync def add_todo(title: str) -> Todo: revalidate("/todos") return Todo(title=title)<script lang="ts"> import { useAction } from "@nexy/client"; import { add_todo } from "@nexy/actions"; let title = $state(""); const save = useAction(add_todo, { onSuccess: () => (title = "") });</script> <form onsubmit={(e) => { e.preventDefault(); save.run({ title }); }}> <input bind:value={title} /> <button disabled={$save.pending}>Add</button></form>revalidate("/todos") 扮演 invalidateAll() 的角色:页面再次获取自身。
goto、链接和 $app/state
<script lang="ts"> import { goto } from "$app/navigation"; import { page } from "$app/state";</script> <a href="/about">About</a><span>{page.url.pathname}</span><button onclick={() => goto("/dashboard")}>Dashboard</button><script lang="ts"> import Link from "@nexy/Link.svelte"; import { pathname, router } from "@nexy/client";</script> <Link href="/about" activeClass="active">About</Link><span>{$pathname}</span><button onclick={() => router.push("/dashboard")}>Dashboard</button>hooks.server.ts
export async function handle({ event, resolve }) { if (event.url.pathname.startsWith("/dashboard") && !event.cookies.get("session")) { return new Response(null, { status: 307, headers: { location: "/login" } }); } const response = await resolve(event); response.headers.set("x-app", "demo"); return response;}路径测试是文件夹。根 middleware.py 包含所有内容, dashboard/middleware.py 仅包含其下面的路由:
from nexy.http import Request async def middleware(request: Request, call_next): response = await call_next(request) response.headers["x-app"] = "demo" return responsefrom nexy.http import RedirectResponse, Request async def middleware(request: Request, call_next): if "session" not in request.cookies: return RedirectResponse("/login", status_code=307) return await call_next(request)async def GET(): return {"visits": 12}prerender 和 entries
export const prerender = true; export function entries() { return [{ slug: "hello" }, { slug: "world" }];}从页面导出 getStaticParams,为每个值提供一个预渲染文档:
<script lang="ts" module> export function getStaticParams() { return [{ slug: "hello" }, { slug: "world" }]; }</script> <script lang="ts"> let { slug }: { slug: string } = $props();</script> <h1>{slug}</h1>在服务器模式下,.nexy 页面的 frontmatter 中的 revalidate = 60 会按 URL 缓存它:
revalidate = 60<h1>News</h1><svelte:head>
<svelte:head> <title>Blog</title></svelte:head>在客户端模式下,将标签写入根 layout.svelte。在服务器模式下,.nexy 页面设置它们,其布局用 {{ useMetadata().html() }}写入它们:
metadata = {"title": "Blog", "description": "Posts about Nexy"}<h1>Blog</h1>Svelte 服务器页面中的组件
<script lang="ts"> let { start = 0 }: { start?: number } = $props(); let count = $state(start);</script> <button onclick={() => count++}>{count}</button>在服务器模式下,该组件是 .nexy 页面的一个孤岛:
from "@/components/Counter.svelte" import Counter<h1>Static heading</h1><Counter start=3 directive="client:visible" />+error.svelte
src/routes/notfound.svelte 在客户端模式下回答未知 URL,notfound.nexy 在服务器模式下回答未知 URL,API 客户端得到 {"detail": ...}。
$env/static/private
import { DATABASE_URL } from "$env/static/private";import os DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///dev.db")跑步
| 苗条套件 | 内克西 |
|---|---|
npx sv create |
nx new,模板 web-fbr-svelte |
vite dev |
nx dev |
vite build 使用适配器,然后 node build |
nx build,然后 nx start |
static/ |
public/,服务于 / |
src/lib/ 和 $lib |
src/components/ 和 @ |