跳到主要内容

部署

生产部署从 ev build 开始。默认情况下,evjs 会把浏览器文件写到 dist/client; 当应用使用服务端能力时,还会把服务端文件写到 dist/server

如果希望 evjs 生成平台专属文件,例如 Node server 入口、静态托管 redirects 或 edge worker,可以使用部署 adapter。

生产构建

npm run build
# 通常执行:ev build

典型产物:

dist/
├── client/
│ └── ...
├── server/
│ └── ...
└── deployment-metadata.json

重要路径:

  • dist/client/:浏览器资源和生成的 HTML。
  • dist/server/:应用使用服务端函数、服务端文件路由、SSR、PPR 或 RSC 时生成的服务端 bundle 和服务端元信息。
  • dist/deployment-metadata.json:面向工具和部署 adapter 的 canonical deployment metadata。应用代码不应导入或修改它。

deployment-metadata.json 是 core 唯一序列化的 deployment projection。 其他平台专属产物由 deployment adapter 持有。

生成的 HTML 会内嵌浏览器 ClientRuntime。手动使用 @evjs/client runtime URL API 时,可以从显式配置的 URL 加载 JSON;框架没有默认的独立 runtime JSON 产物。 Runtime-only 的 FrameworkRuntime 数据通过 build/plugin result 传递,并注入 dev 或 deployment adapter bootstrap。

选择部署目标

目标适用场景Adapter
静态托管应用只需要浏览器资源、CSR、MPA client page,或完全静态/SSG 页面。staticDeploymentAdapter()
Node.js一个 Node 进程负责资源和全部服务端能力。nodeDeploymentAdapter()
Edge worker平台提供 fetch() worker 和静态资源 binding。edgeDeploymentAdapter()
CDN + origin 拆分静态资源在 CDN,服务端能力部署在另一个 origin。使用具备服务端能力的 adapter,并配置平台路由。

当应用使用服务端函数、服务端文件路由、SSR、PPR 或 RSC 时,不要只部署 dist/client。 这些能力需要具备服务端能力的部署目标。

运行时路径

服务端运行时路径从 server.basePath 派生:

/__evjs/fn 服务端函数
/__evjs/ppr 存在 PPR 页面时的 PPR 支持端点
/__evjs/rsc 存在 RSC 页面时的 RSC Flight 端点

多数应用可以保留默认的 server.basePath。只有当宿主平台占用了 /__evjs,或反向代理要求其他前缀时,才需要修改它。

PPR 文档请求仍然通过页面 route。PPR 支持端点用于框架/runtime 协作和直接调试,不是用户编写的 API route。

当浏览器资源和服务端运行时位于不同 origin 时,在构建时设置 transport.baseUrl

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

export default defineConfig({
transport: {
baseUrl: "https://api.example.com",
},
});

内置 Adapter

@evjs/ev 内置三类部署 adapter:

  • nodeDeploymentAdapter():输出 Node server 入口和部署元信息。
  • staticDeploymentAdapter():输出静态托管元信息和 _redirects
  • edgeDeploymentAdapter():输出 edge worker 入口和部署元信息。

Adapter 基于 evjs 构建结果工作,不应从文件名或 bundler stats 反推框架能力。

Node.js

普通 Node 服务可以使用 Node adapter 接管生产请求路径:

// ev.config.ts
import { defineConfig } from "@evjs/ev";
import { nodeDeploymentAdapter } from "@evjs/ev/deployment";

export default defineConfig({
plugins: [nodeDeploymentAdapter()],
});

执行 ev build 后会生成:

dist/
├── deployment.node.json
└── server.mjs

运行生成的服务:

node dist/server.mjs

生成的 server 会提供 dist/client,处理服务端函数和服务端文件路由,挂载 SSR/PPR/RSC 文档路由,并对客户端导航回退到应用 HTML。默认从 PORT 读取端口。

静态托管

当应用兼容静态托管时,可以使用 static adapter:

import { defineConfig } from "@evjs/ev";
import { staticDeploymentAdapter } from "@evjs/ev/deployment";

export default defineConfig({
plugins: [staticDeploymentAdapter()],
});

adapter 会把静态托管文件写入 public output 目录:

dist/client/
├── deployment.static.json
└── _redirects

生成的 redirects 会把静态或 SSG 页面映射到对应 HTML,并把 app route 映射到应用 HTML fallback。无路由器 MPA 页面使用精确 rewrite,不会创建全局 catch-all。

如果构建中包含 SSR、PPR、RSC、服务端函数或服务端文件路由,static adapter 仍会输出资源和元信息, 但会在 deployment.static.json 中标记静态产物不完整。这种情况下,应用还需要一条具备服务端能力的部署路径。

Edge Runtime

当平台提供 fetch() worker 和静态资源 binding 时,可以使用 edge adapter:

import { defineConfig } from "@evjs/ev";
import { edgeDeploymentAdapter } from "@evjs/ev/deployment";

export default defineConfig({
plugins: [
edgeDeploymentAdapter({
assetsBinding: "ASSETS",
}),
],
});

执行 ev build 后会生成:

dist/
├── deployment.edge.json
└── worker.mjs

生成的 worker 会把服务端运行时请求和服务端渲染页面请求转发给服务端 bundle,并通过配置的 asset binding 提供浏览器资源。

Docker

Docker 部署可以使用 Node adapter,并运行生成的 dist/server.mjs

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.mjs"]

自定义部署插件

部署插件可以使用 afterBuild({ deploymentMetadata }) 输出平台文件。需要平台专属 schema 字段时,可以在写文件前包装这份 metadata:

import { definePlugin } from "@evjs/ev/plugin";

export const deployAdapter = definePlugin({
id: "deploy-adapter",
setup() {
return {
afterBuild({ deploymentMetadata }) {
const artifact = {
...deploymentMetadata,
platform: "custom",
};

emitPlatformFiles(artifact);
},
};
},
});

应用通过 plugins: [deployAdapter()] 安装该工厂返回的插件实例。

自定义 adapter 应聚焦平台路由、资源服务、进程或 worker 启动逻辑。应用代码应继续使用 evjs 文件约定,而不是直接读取部署元信息。