marko-run

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Marko Run

Marko Run

Distilled from https://markojs.com/docs/marko-run. File-based routing on Vite, streaming SSR, zero config.
references/routing.md
(files, paths, flat routes, execution order),
references/handlers.md
(verb helpers, validation, data loading, context, typed URLs, embedding),
references/deploy.md
(vite plugin, adapters, CLI).
整理自 https://markojs.com/docs/marko-run。基于 Vite 的文件式路由、流式 SSR、零配置。
references/routing.md
(文件、路径、扁平路由、执行顺序)、
references/handlers.md
(HTTP 动词辅助方法、验证、数据加载、上下文、类型化 URL、嵌入)、
references/deploy.md
(Vite 插件、适配器、CLI)。

Mental model

心智模型

  • src/routes/**
    — only
    +
    -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.
  • ctx
    (handlers/middleware) ===
    $global
    (templates).
    ctx.params
    ,
    ctx.url
    are serialized to the browser by default; add others via
    ctx.serializedGlobals
    .
  • Data flows down through
    next({ ... })
    →
    ctx.data
    /
    $global.data
    (shallow merge). Pass promises,
    <await>
    them in the page
    — awaiting before
    next
    kills streaming.
  • Run
    is a global (no import):
    Run.GET/HEAD/POST/PUT/DELETE/PATCH/OPTIONS/QUERY/ALL
    ,
    Run.href
    , types
    Run.Context
    ,
    Run.Platform
    .
  • src/routes/**
    ——只有带
    +
    前缀的文件才能作为路由;其他所有内容都会被忽略(可以自由共置组件和测试)。
  • 请求 → 中间件(根→叶)→ 处理器 → 布局(根→叶)→ 页面。布局和页面会编译成同一个组件。
  • ctx
    (处理器/中间件)===
    $global
    (模板)。默认会将
    ctx.params
    、
    ctx.url
    序列化到浏览器;可通过
    ctx.serializedGlobals
    添加其他值。
  • 数据通过
    next({ ... })
    向下流动 →
    ctx.data
    /
    $global.data
    (浅合并)。传递 Promise,在页面中使用
    <await>
    等待
    ——在
    next
    之前 await 会终止流式传输。
  • Run
    是全局变量(无需 import):
    Run.GET/HEAD/POST/PUT/DELETE/PATCH/OPTIONS/QUERY/ALL
    、
    Run.href
    ,类型
    Run.Context
    、
    Run.Platform
    。

Quick reference

快速参考

Files (any depth under routes)

文件(routes 下的任意深度)

FileRole
+page.marko
GET → HTML. One per path.
+layout.marko
wraps nested layouts/pages;
input.content
= child.
$global
= ctx.
+handler.ts
export const GET/POST/... = Run.X(...)
. One per path. Return
Response
, or
undefined
→
next()
auto. Missing HEAD → GET minus body.
+middleware.ts
export default Run.ALL(...)
(all verbs) or
Run.POST(...)
(one verb); runs before handlers; options-only form
Run.ALL({ search })
attaches validation below.
+meta.json
/
+meta.ts
static
ctx.meta
; verb keys (
"POST": {...}
) shallow-override per method.
+404.marko
,
+500.marko
root-only; when
Accept
has
text/html
; wrapped by root layout.
文件作用
+page.marko
GET → HTML。每条路径只能有一个。
+layout.marko
包裹嵌套的布局和页面;
input.content
= 子内容。
$global
= ctx。
+handler.ts
export const GET/POST/... = Run.X(...)
。每条路径只能有一个。返回
Response
,或返回
undefined
→ 自动调用
next()
。缺少 HEAD → 使用不含响应体的 GET。
+middleware.ts
export default Run.ALL(...)
(所有动词)或
Run.POST(...)
(单一动词);在处理器之前运行;仅包含 options 的表单
Run.ALL({ search })
会在下层附加验证。
+meta.json
/
+meta.ts
静态
ctx.meta
;动词键(
"POST": {...}
)会按方法进行浅层覆盖。
+404.marko
、
+500.marko
仅限根级;当
Accept
包含
text/html
时使用;由根布局包裹。

Path segments

路径段

users
static ·
_admin
pathless (ignored in URL, scopes layouts/middleware) ·
$id
param (
$
alone = match, no capture) ·
$$rest
catch-all to end (
$$
no capture; nothing nests inside). Flat routes:
projects.$projectId.members+page.marko
; dirs may use it too (
projects.$projectId/members+page.marko
); merged with nested dirs. Multi-path
members,people+page.marko
; groups
(members,people)
; optional
(home,)
or
(home,_x)
. Escape control chars with backticks:
`sitemap.xml`/+handler.ts
.
users
静态 ·
_admin
无路径(不会出现在 URL 中,但会划分布局和中间件的作用域)·
$id
参数(单独的
$
表示匹配但不捕获)·
$$rest
捕获从当前位置到结尾的所有路径(
$$
不捕获;其内部不能再嵌套路由)。 扁平路由:
projects.$projectId.members+page.marko
;目录也可以使用(
projects.$projectId/members+page.marko
);会与嵌套目录合并。多路径:
members,people+page.marko
;分组:
(members,people)
;可选路径:
(home,)
或
(home,_x)
。使用反引号转义控制字符:
`sitemap.xml`/+handler.ts
。

Handlers & 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
    ,
    search
    (fn or sync Standard Schema; lazy, cached on first access),
    json
    (
    validator
    ,
    maxBytes
    1MiB),
    form
    (
    validator
    ,
    maxBytes
    ,
    maxParts
    1000,
    maxFiles
    20,
    maxFileBytes
    1MiB,
    onFile(ctx, file)
    ; repeated fields → arrays). Forms:
    Run.POST(options, [auth, handler])
    arrays compose.
  • ctx.body
    is a promise (
    await ctx.body
    ), lazy,
    undefined
    when no
    json
    /
    form
    option. Don't call
    ctx.request.json()
    — bypasses validation/limits.
  • Options merge across middleware+handler (last validator wins, objects shallow-merge).
  • ctx
    methods:
    fetch(url, init)
    (internal re-request, sets
    parent
    ),
    render(template, input, init)
    ,
    redirect(to, status=302)
    ,
    back(fallback="/", status)
    .
  • Run.href("/projects/$projectId/members", { params: { projectId: 42 }, search: {...}, hash })
    — typed against real routes when
    .marko-run/routes.d.ts
    is in tsconfig
    include
    ; rewritten at compile time on the client.
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
    、
    search
    (函数或同步 Standard Schema;延迟执行,首次访问时缓存)、
    json
    (
    validator
    、
    maxBytes
    1MiB)、
    form
    (
    validator
    、
    maxBytes
    、
    maxParts
    1000、
    maxFiles
    20、
    maxFileBytes
    1MiB、
    onFile(ctx, file)
    ;重复字段 → 数组)。表单:
    Run.POST(options, [auth, handler])
    数组可组合。
  • ctx.body
    是一个 Promise(使用
    await ctx.body
    ),采用延迟求值;没有
    json
    /
    form
    option 时为
    undefined
    。不要调用
    ctx.request.json()
    ——这会绕过验证和限制。
  • Options 会跨中间件和处理器合并(最后一个验证器生效,对象进行浅合并)。
  • ctx
    方法:
    fetch(url, init)
    (内部重新发起请求并设置
    parent
    )、
    render(template, input, init)
    、
    redirect(to, status=302)
    、
    back(fallback="/", status)
    。
  • Run.href("/projects/$projectId/members", { params: { projectId: 42 }, search: {...}, hash })
    ——当 tsconfig 的
    include
    包含
    .marko-run/routes.d.ts
    时,可根据真实路由进行类型检查;编译时会在客户端重写。

Types

类型

Generated
.marko-run/routes.d.ts
(needs tsconfig at root; add to
include
) narrows
Run.Context
per file (params, meta, search, body, upstream
data
). Extend globally:
declare module "@marko/run" { interface Context { db: DB } interface Platform { ... } }
— reserve for app-wide props; per-route data goes through
next()
.
生成的
.marko-run/routes.d.ts
(需要在根目录放置 tsconfig;将其添加到
include
)会按文件收窄
Run.Context
(params、meta、search、body、上游
data
)。全局扩展:
declare module "@marko/run" { interface Context { db: DB } interface Platform { ... } }
——保留给应用级属性;每条路由的数据通过
next()
传递。

Config / deploy

配置与部署

  • No
    vite.config
    needed. When needed:
    import marko from "@marko/run/vite"; plugins: [marko({ adapter, routesDir, trailingSlashes })]
    (+ all
    @marko/vite
    options).
    trailingSlashes
    :
    RedirectWithout
    (default) |
    RedirectWith
    |
    RewriteWithout
    |
    RewriteWith
    |
    Ignore
    .
  • Adapter resolution: option → first dep named
    @marko/run-adapter*
    /
    *marko-run-adapter*
    → built-in Node. Packages:
    @marko/run-adapter-node
    (Express middleware:
    routerMiddleware()
    ,
    matchMiddleware()
    →
    req.route.invoke
    ),
    @marko/run-adapter-static
    (
    urls: [...] | async fn
    , crawls links),
    @marko/run-adapter-netlify
    (
    { edge: true }
    +
    netlify.toml
    functions
    /
    edge_functions = "dist"
    , publish
    dist/public
    ).
  • Embed:
    import * as router from "@marko/run/router"
    →
    router.fetch(request, platform)
    ,
    router.match(method, path)
    +
    router.invoke(route, request, platform)
    .
  • CLI (
    marko-run
    , run via
    bun run
    /
    bunx
    ):
    dev
    (default,
    -p
    ),
    build
    (
    -o
    ),
    preview
    (
    -o -p -f
    ); all take
    -c config
    ,
    -e dotenv
    .
  • 无需
    vite.config
    。需要时:
    import marko from "@marko/run/vite"; plugins: [marko({ adapter, routesDir, trailingSlashes })]
    (加上所有
    @marko/vite
    options)。
    trailingSlashes
    :
    RedirectWithout
    (默认)|
    RedirectWith
    |
    RewriteWithout
    |
    RewriteWith
    |
    Ignore
    。
  • 适配器解析顺序:option → 第一个名称匹配
    @marko/run-adapter*
    /
    *marko-run-adapter*
    的依赖 → 内置 Node。软件包:
    @marko/run-adapter-node
    (Express 中间件:
    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
    GET
    before
    next()
    → whole response blocked. Pass the promise.
  • Returning
    next()
    from a successful
    POST
    → refresh resubmits, URL unshareable. Redirect instead.
  • Async Standard Schema validators throw at runtime — sync only.
  • ctx.body()
    — it's a promise, not a function.
  • Handler that always returns its own
    Response
    never renders the page (its verb is dropped from the page's typed context).
  • Nesting route files inside a
    $$catch-all
    dir — never traversed.
  • Expecting
    $global.session
    on the client — only
    params
    /
    url
    serialize by default; add to
    ctx.serializedGlobals
    (and never secrets).
  • 在 GET 中于
    next()
    之前 await 数据 → 整个响应被阻塞。应传递 Promise。
  • POST 成功后返回
    next()
    → 刷新会重新提交,URL 无法分享。应该改为重定向。
  • 异步 Standard Schema 验证器会在运行时抛出异常——只能使用同步验证器。
  • ctx.body()
    ——它是一个 Promise,不是函数。
  • 始终返回自身
    Response
    的处理器永远不会渲染页面(该动词会从页面的类型化上下文中移除)。
  • 在
    $$catch-all
    目录内嵌套路由文件——永远不会遍历。
  • 以为客户端上存在
    $global.session
    ——默认只有
    params
    /
    url
    会序列化;请添加到
    ctx.serializedGlobals
    (且绝不要包含机密信息)。