跳到主要内容

构建

命令

ev inspect
ev inspect --json
ev prepare
ev build
  • ev inspect 校验并报告框架输入,不写入 .evdist
  • ev prepare.ev 写入生成 IR,但不运行 bundler;
  • ev build 解析配置、创建 graph/plan、运行 bundler、链接 build fact 并写入 production output。

ev prepareev buildev dev 共用同一个项目级 operation lock。同一项目若再启动 一个会写产物的命令,会携带当前 operation 与进程 ID 直接失败,而不是并发覆盖 .ev、 route type、dist 或部署产物;不同项目目录仍可独立运行。

Inspect

canonical application 的 routing 摘要使用公开 Page-and-Route 词汇:mode、 Page root、发现的 page.* 锚点、目录派生 route pattern、Document 与 diagnostic。

canonical inspect 输出不会展示 provider、resolver 实现或 route-types path。它会 报告 resolved Page、Route、Document、server function、server route、rendering metadata、已安装 plugin settings、Page config source、provenance 与 diagnostic。 错误会让 inspect 非零退出。

生成 IR

ev prepare 写入 .ev,包括:

  • normalized CoreGraph;
  • 生成 framework/plugin module;
  • entry facade 与 framework slot;
  • import edge;
  • 最终 BuildPlan;
  • manifest input 与 provenance。

canonical application 把校验后的 semantic graph 写到 .ev/framework/core-graph.json.ev 是生成物,不得编辑。

输出

默认分离浏览器和 server 文件:

dist/
├── client/
│ ├── index.html
│ ├── main.[hash].js
│ └── [chunk].[hash].js
├── server/
│ └── main.[hash].js
└── deployment-metadata.json

部署平台需要其他目录时使用 output.client / output.server。两个目录都必须使用 以 / 分隔的可移植 project-relative 路径,不得包含空、... segment;它们 必须是 BuildPlan distDir 下不经过 symbolic link、互不相同且互不嵌套的严格子目录:

export default defineConfig({
routing: { mode: "spa" },
output: {
client: "dist/public",
server: "dist/runtime",
},
});

最终 BuildPlan 是 adapter cleanup、产物写入、stats 与 manifest 路径的唯一事实源。 Plugin configureBundler() hook 可以修改受支持的底层 bundler setting,但不能覆盖 framework 持有的 client 或 server 输出路径。

Bundler server fact 使用 serverEntryAssets,并以每个 server BuildPlan entry 的 精确名称为 key。每个 server entry 必须只发射一个自包含 JavaScript 产物。Bundler 提供完整 server 产物清单时,清单必须包含每个已声明 entry 产物,且不能包含额外的 无归属 JavaScript chunk;Core 不会再从 module stats 或文件名推断 server ownership。

生成 HTML 包含浏览器 bootstrap 所需 ClientRuntimedeployment-metadata.json 是 canonical serialized deployment projection; 完整 BuildOutput 只存在于内存中。应用代码不得 import 或编辑 deployment metadata。

SPA 与 MPA 输出

routing.mode 控制 Route/Document materialization:

Routing modeRoute 输出Document 输出
spa一个浏览器 route tree 中的 Client Route一个 Application-owned shell,外加每个静态 SSG Page 的 Page-owned 输出
mpa静态语义 route 的独立 Page entry每条静态 Page route 一个 Page-owned Document

二者使用相同 src/pages/**/page.* entry、目录 scope 与语义 route pattern。

两种 mode 下,静态 SSG Page 都按语义 route 决定输出路径:/ 写入 index.html/report 写入 report/index.html,不会从 Page id 推导文件名。 如果混合 SPA 的根 SSG Page 已拥有 index.html,同时其他 client route 还需要 fallback,Core 会把 Application shell 单独保留在 __evjs/<application-id>.html

MPA 只物化静态 Page route。$param 与终止 $...splat 仍是有效的 SPA route 身份,但为它们选择 MPA 会在 graph 校验失败,因为一个动态 pattern 不能唯一对应一个构建期 HTML 输出。Route layout 在两种 mode 中都会组合; router-only boundary facet 仍仅支持 SPA,MPA 会显式拒绝。

需要 Page-specific Document 模板时,把 index.html 放在 MPA Page 旁:

src/pages/report/
├── page.tsx
└── index.html

canonical SPA/MPA Page 都发现 Page 目录中可选的 page.config.ts

import { definePageConfig } from "@evjs/ev";

export default definePageConfig({
title: "报表",
meta: {
description: "构建生成的业务报表。",
keywords: "报表,分析",
viewport: "width=device-width, initial-scale=1",
"theme-color": "#ffffff",
},
render: "ssr",
hydrate: "load",
plugins: {
analytics: {
channel: "report",
},
},
});

该 module 在 graph build 阶段同步求值。Core rendering 字段进入 rendering BuildPlan。对于实际发射的 MPA/SSG Document,以及构建期编译的 SSR/PPR/RSC request-time document shell,静态 title 和 named meta 会物化缺失 tag,并覆盖 模板中匹配的 baseline 值;未声明值保留 baseline。Page plugin setting 保持 static graph data,除非能力所属插件把它显式投影到 generated runtime artifact。 Plugin transformHtml hook 在框架元信息、assets 与结构化 HTML contribution 物化后运行,可以显式覆盖最终结果。

每个 server-rendered Page 都会在构建期把它配置的 HTML 模板编译成 request-time document shell。模板中手写的 <html><head><body> 属性和 内容会被保留,同时应用与 static Document 相同的 assets、Page metadata、 html.tag contribution 和 transformHtml hook。默认 React renderer 在请求时把 Page HTML 与请求相关的 bootstrap data 插入该 shell。

提供自定义 renderDocument 会完全替换 compiled shell:仍可从 ctx.page.metadata 读取数据,但自定义 renderer 需要自行持有模板 baseline、 assets 与 document structure。插入 @evjs/server/reactrenderReactPageMetadata(ctx),可以保留 core 的安全序列化与 SPA cleanup 行为。构建期 transformHtml hook 不会继续处理 custom document renderer 逐请求返回的任意字符串。

Page Rendering Setting

Page component 不读取 literal renderhydrateprerenderrsc export。 把这些值写入同目录 page.config.ts

import { definePageConfig } from "@evjs/ev";

export default definePageConfig({
render: "ssr",
hydrate: "none",
prerender: { partial: true },
});

静态生成使用受支持的 "ssg" rendering contract。RSC 与 partial-prerendered Page 必须省略 hydrate 或将其设为 "none"。RSC Page 使用 render: "ssr"rsc: true;Flight endpoint 从 server.basePath 派生,除非用 server.rsc.endpoint 覆盖。这两个 runtime path setting 都必须由非空 ASCII URL-safe segment 组成,每个 segment 只能包含字母、数字、._~-; 空 segment、单独的 ... segment、:param*、percent escape 与原始 非 ASCII 字符都会被拒绝。同一 Page 不能组合 RSC 与 partial prerendering。这些 setting normalize 到 Core Page rendering field,且不改变 Page identity。

省略 render 时,Page 始终归一化为 "csr"。CSR 会在浏览器中 mount 一棵新的 client tree,因此必须省略 hydrate;只有显式选择 SSR 或 SSG 的 Page 才能配置 hydrate: "load" | "none"。普通 SSR 默认执行 load hydration,SSG 默认不执行 hydration,RSC/PPR 则保持 Page 级不 hydration。生成的 runtime metadata 仍会 用有效值 "load" 表示 CSR bootstrap 的 client activation,但该内部值不是 Page authoring option。

Server-function 与启用后的 RSC endpoint 是精确路径;启用后的 PPR endpoint 持有 以自身为根的子树。BuildPlan 要求这些 active endpoint 互不相交,并拒绝任何可能 匹配 reserved runtime path 的 Page、redirect 或 server request Route pattern。 server.basePath 只用于派生默认 endpoint,本身并不持有 request 子树;dev 与 生成的 Node/Edge deployment routing 都保持这一边界。

服务端函数与路由

"use server"; 开头的 reachable module 贡献受支持的命名 server function。

服务端 request Route 独立从 src/apis 下的 positive api.* 锚点发现:

// src/apis/api/health/api.ts
export const GET = async () => Response.json({ ok: true });

构建检查

优先检查用户可控输入:

  • ev.config.ts 声明 routing.mode
  • 每个发布的客户端 Page 只使用一个 page.* 扩展名变体;
  • 每个 Page 最多使用一个 page.config.tspage.config.js,其 default export 是 static JSON data;
  • Page entry 默认导出组件;
  • route 目录使用合法 static、$param、终止 $...splat(group) segment,且没有 normalized-path 冲突;
  • MPA 不使用不受支持的动态路径或 router-only boundary facet;
  • template 包含配置的 mount element;
  • Page title 以及每个 meta name/content 都是合法 static string;
  • page.config.ts 中 Page rendering metadata 使用受支持的值与组合;
  • "use server" module 以 directive 开头并导出命名 callable;
  • 每个发布的 server request Route 在其 URL 目录只使用一个 api.* 扩展名变体;
  • api.* 锚点只导出大写 HTTP method;
  • 每个占用 URL 的客户端 Route(Page 或 redirect)都必须与 server request Route pattern 互不相交,包括 static、dynamic 与终止 splat 之间的匹配。Static alias 按恰好一次 URL decode 后比较,因此 /%75sers/users 的 alias,而双重编码 文本仍保持不同。

运行 build 前先用 ev inspect 审核 Page source、Page config、route、Document、 provenance 与 diagnostic。

要点

  • SPA/MPA 从同一棵 page.* Page-and-Route 树构建;
  • ev inspect 报告 routingMode、Page root、source、Document 默认值,不暴露内部 provider 选择;
  • .ev、manifest、build output 与生成的 route-type declaration 都是生成物;
  • Bundler adapter 以 BuildPlan 作为 routing、runtime 与 output ownership 的事实源, 然后返回 build fact。
  • 当 stats 能提供可靠且完整的物理产物清单时,adapter 会通过 BundlerBuildFacts.emittedFiles 返回它。已返回的每一侧都是完整清单;省略 client 或 server 一侧表示未知,绝不表示空输出。