marko-run
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMarko Run
Marko Run
Distilled from https://markojs.com/docs/marko-run. File-based routing on Vite, streaming SSR, zero config. (files, paths, flat routes, execution order), (verb helpers, validation, data loading, context, typed URLs, embedding), (vite plugin, adapters, CLI).
references/routing.mdreferences/handlers.mdreferences/deploy.md整理自 https://markojs.com/docs/marko-run。基于 Vite 的文件式路由、流式 SSR、零配置。(文件、路径、扁平路由、执行顺序)、(HTTP 动词辅助方法、验证、数据加载、上下文、类型化 URL、嵌入)、(Vite 插件、适配器、CLI)。
references/routing.mdreferences/handlers.mdreferences/deploy.mdMental model
心智模型
- — only
src/routes/**-prefixed files are routable; everything else is ignored (colocate components/tests freely).+ - Request → middlewares (root→leaf) → handler → layouts (root→leaf) → page. Layouts+page compile into one component.
- (handlers/middleware) ===
ctx(templates).$global,ctx.paramsare serialized to the browser by default; add others viactx.url.ctx.serializedGlobals - Data flows down through →
next({ ... })/ctx.data(shallow merge). Pass promises,$global.datathem in the page — awaiting before<await>kills streaming.next - is a global (no import):
Run,Run.GET/HEAD/POST/PUT/DELETE/PATCH/OPTIONS/QUERY/ALL, typesRun.href,Run.Context.Run.Platform
- ——只有带
src/routes/**前缀的文件才能作为路由;其他所有内容都会被忽略(可以自由共置组件和测试)。+ - 请求 → 中间件(根→叶)→ 处理器 → 布局(根→叶)→ 页面。布局和页面会编译成同一个组件。
- (处理器/中间件)===
ctx(模板)。默认会将$global、ctx.params序列化到浏览器;可通过ctx.url添加其他值。ctx.serializedGlobals - 数据通过 向下流动 →
next({ ... })/ctx.data(浅合并)。传递 Promise,在页面中使用$global.data等待——在<await>之前 await 会终止流式传输。next - 是全局变量(无需 import):
Run、Run.GET/HEAD/POST/PUT/DELETE/PATCH/OPTIONS/QUERY/ALL,类型Run.href、Run.Context。Run.Platform
Quick reference
快速参考
Files (any depth under routes)
文件(routes 下的任意深度)
| File | Role |
|---|---|
| GET → HTML. One per path. |
| wraps nested layouts/pages; |
| |
| |
| static |
| root-only; when |
| 文件 | 作用 |
|---|---|
| GET → HTML。每条路径只能有一个。 |
| 包裹嵌套的布局和页面; |
| |
| |
| 静态 |
| 仅限根级;当 |
Path segments
路径段
users_admin$id$$$rest$$projects.$projectId.members+page.markoprojects.$projectId/members+page.markomembers,people+page.marko(members,people)(home,)(home,_x)`sitemap.xml`/+handler.tsusers_admin$id$$$rest$$projects.$projectId.members+page.markoprojects.$projectId/members+page.markomembers,people+page.marko(members,people)(home,)(home,_x)`sitemap.xml`/+handler.tsHandlers & validation
处理器与验证
ts
export const POST = Run.POST({ json: NoteSchema }, async (ctx, next) => {
const [note, issues] = await ctx.body; // Standard Schema → [value, issues]; fn validator → its return
if (issues) return Response.json({ issues }, { status: 422 });
return ctx.redirect("/thanks"); // PRG on success; next({ issues }) to re-render form with errors
});- Options: ,
params(fn or sync Standard Schema; lazy, cached on first access),search(json,validator1MiB),maxBytes(form,validator,maxBytes1000,maxParts20,maxFiles1MiB,maxFileBytes; repeated fields → arrays). Forms:onFile(ctx, file)arrays compose.Run.POST(options, [auth, handler]) - is a promise (
ctx.body), lazy,await ctx.bodywhen noundefined/jsonoption. Don't callform— bypasses validation/limits.ctx.request.json() - Options merge across middleware+handler (last validator wins, objects shallow-merge).
- methods:
ctx(internal re-request, setsfetch(url, init)),parent,render(template, input, init),redirect(to, status=302).back(fallback="/", status) - — typed against real routes when
Run.href("/projects/$projectId/members", { params: { projectId: 42 }, search: {...}, hash })is in tsconfig.marko-run/routes.d.ts; rewritten at compile time on the client.include
ts
export const POST = Run.POST({ json: NoteSchema }, async (ctx, next) => {
const [note, issues] = await ctx.body; // Standard Schema → [value, issues]; fn validator → its return
if (issues) return Response.json({ issues }, { status: 422 });
return ctx.redirect("/thanks"); // PRG on success; next({ issues }) to re-render form with errors
});- Options:、
params(函数或同步 Standard Schema;延迟执行,首次访问时缓存)、search(json、validator1MiB)、maxBytes(form、validator、maxBytes1000、maxParts20、maxFiles1MiB、maxFileBytes;重复字段 → 数组)。表单:onFile(ctx, file)数组可组合。Run.POST(options, [auth, handler]) - 是一个 Promise(使用
ctx.body),采用延迟求值;没有await ctx.body/jsonoption 时为form。不要调用undefined——这会绕过验证和限制。ctx.request.json() - Options 会跨中间件和处理器合并(最后一个验证器生效,对象进行浅合并)。
- 方法:
ctx(内部重新发起请求并设置fetch(url, init))、parent、render(template, input, init)、redirect(to, status=302)。back(fallback="/", status) - ——当 tsconfig 的
Run.href("/projects/$projectId/members", { params: { projectId: 42 }, search: {...}, hash })包含include时,可根据真实路由进行类型检查;编译时会在客户端重写。.marko-run/routes.d.ts
Types
类型
Generated (needs tsconfig at root; add to ) narrows per file (params, meta, search, body, upstream ). Extend globally: — reserve for app-wide props; per-route data goes through .
.marko-run/routes.d.tsincludeRun.Contextdatadeclare module "@marko/run" { interface Context { db: DB } interface Platform { ... } }next()生成的 (需要在根目录放置 tsconfig;将其添加到 )会按文件收窄 (params、meta、search、body、上游 )。全局扩展:——保留给应用级属性;每条路由的数据通过 传递。
.marko-run/routes.d.tsincludeRun.Contextdatadeclare module "@marko/run" { interface Context { db: DB } interface Platform { ... } }next()Config / deploy
配置与部署
- No needed. When needed:
vite.config(+ allimport marko from "@marko/run/vite"; plugins: [marko({ adapter, routesDir, trailingSlashes })]options).@marko/vite:trailingSlashes(default) |RedirectWithout|RedirectWith|RewriteWithout|RewriteWith.Ignore - Adapter resolution: option → first dep named /
@marko/run-adapter*→ built-in Node. Packages:*marko-run-adapter*(Express middleware:@marko/run-adapter-node,routerMiddleware()→matchMiddleware()),req.route.invoke(@marko/run-adapter-static, crawls links),urls: [...] | async fn(@marko/run-adapter-netlify+{ edge: true }netlify.toml/functions, publishedge_functions = "dist").dist/public - Embed: →
import * as router from "@marko/run/router",router.fetch(request, platform)+router.match(method, path).router.invoke(route, request, platform) - CLI (, run via
marko-run/bun run):bunx(default,dev),-p(build),-o(preview); all take-o -p -f,-c config.-e dotenv
- 无需 。需要时:
vite.config(加上所有import marko from "@marko/run/vite"; plugins: [marko({ adapter, routesDir, trailingSlashes })]options)。@marko/vite:trailingSlashes(默认)|RedirectWithout|RedirectWith|RewriteWithout|RewriteWith。Ignore - 适配器解析顺序:option → 第一个名称匹配 /
@marko/run-adapter*的依赖 → 内置 Node。软件包:*marko-run-adapter*(Express 中间件:@marko/run-adapter-node、routerMiddleware()→matchMiddleware())、req.route.invoke(@marko/run-adapter-static,会爬取链接)、urls: [...] | async fn(@marko/run-adapter-netlify+{ edge: true }中的netlify.toml/functions,发布edge_functions = "dist")。dist/public - 嵌入:→
import * as router from "@marko/run/router"、router.fetch(request, platform)+router.match(method, path)。router.invoke(route, request, platform) - CLI(,通过
marko-run/bun run运行):bunx(默认,dev)、-p(build)、-o(preview);全部支持-o -p -f、-c config。-e dotenv
Common mistakes
常见错误
- Awaiting data in before
GET→ whole response blocked. Pass the promise.next() - Returning from a successful
next()→ refresh resubmits, URL unshareable. Redirect instead.POST - Async Standard Schema validators throw at runtime — sync only.
- — it's a promise, not a function.
ctx.body() - Handler that always returns its own never renders the page (its verb is dropped from the page's typed context).
Response - Nesting route files inside a dir — never traversed.
$$catch-all - Expecting on the client — only
$global.session/paramsserialize by default; add tourl(and never secrets).ctx.serializedGlobals
- 在 GET 中于 之前 await 数据 → 整个响应被阻塞。应传递 Promise。
next() - POST 成功后返回 → 刷新会重新提交,URL 无法分享。应该改为重定向。
next() - 异步 Standard Schema 验证器会在运行时抛出异常——只能使用同步验证器。
- ——它是一个 Promise,不是函数。
ctx.body() - 始终返回自身 的处理器永远不会渲染页面(该动词会从页面的类型化上下文中移除)。
Response - 在 目录内嵌套路由文件——永远不会遍历。
$$catch-all - 以为客户端上存在 ——默认只有
$global.session/params会序列化;请添加到url(且绝不要包含机密信息)。ctx.serializedGlobals