前端客户端
脚手架默认只生成后端。--web 会额外生成 packages/web:一个没有 UI 框架的 Vite 项目,用来演示 客户端如何直接从服务端处理函数的类型得到路径、载荷和响应。
bash
pnpm create tealina my-app --web前端开发服务器是 http://localhost:5173,它把 API 前缀代理到 http://localhost:8000。
生成的两个文件
text
packages/web/
index.html
vite.config.ts
src/
main.ts 页面:调用 GET /health 并渲染结果
api/client.ts req:类型来自 api-v1 的处理函数src/api/client.ts 是重点:
ts
import { createFetchClient } from '@tealina/client'
import type { ApiTypesForClient } from 'api-v1/api'
import { API_BASE_URL } from 'api-v1/version'
export const base = API_BASE_URL
let token = ''
export const setToken = (next: string) => {
token = next
}
const requester = async (url: string, config: RequestInit) => {
const headers = new Headers(config.headers)
headers.set('Content-Type', 'application/json')
if (token !== '') headers.set('Authorization', `Bearer ${token}`)
const response = await fetch(`${base}${url}`, { ...config, headers })
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`)
}
return response.json()
}
export const req = createFetchClient<ApiTypesForClient, RequestInit>(requester)ApiTypesForClient通过api-v1的exports["./api"]到达这里,是处理函数注解的客户端投影。API_BASE_URL与后端挂载、文档 JSON 文件使用同一个值,改前缀只改version.js。import type是必需的:./api只发布types条件,没有运行时模块。
src/main.ts 只做一件事:
ts
import { req } from './api/client'
const health = await req.get('health')
const isOk: boolean = health.isOkisOk 的类型来自 GET /health 处理函数的响应注解。前端没有第二份 API 声明;改了处理函数里的 字段,这一行会编译失败,这正是目的。
main.ts 是占位页面,可以直接删掉,在 src/ 里写自己的应用。
调用约定
端点键是逻辑路径,不带前导斜杠:
ts
await req.get('health')
await req.get('user/:id', { params: { id: 'u1' } })
await req.post('article', { body: { title: 'Hello' } })RPC 风格按路径段嵌套:
ts
import { createFetchRPC } from '@tealina/client'
const rpc = createFetchRPC<ApiTypesForClient, RequestInit>(requester)
await rpc.get.health()
await rpc.get.user[':id']({ params: { id: 'u1' } })两种风格、fetch/axios 适配器和原始响应的完整说明见 @tealina/client。
代理与同源
vite.config.ts 把 API_BASE_URL 代理到 http://localhost:8000:
ts
import { defineConfig } from 'vite-plus'
import { API_BASE_URL } from 'api-v1/version'
export default defineConfig({
server: {
proxy: { [API_BASE_URL]: 'http://localhost:8000' },
},
})页面和 API 因此在开发时同源,后端模板不需要发送 CORS 头。PORT 不是 8000 时,修改这里的代理目标。
没有 --web 时添加前端
前端没有任何脚手架特有的机制。在 packages/web/package.json 里声明依赖:
json
{
"devDependencies": {
"api-v1": "workspace:*"
}
}在仓库根运行 pnpm install 建立链接,然后:
ts
import type { ApiTypesForClient } from 'api-v1/api'同样必须用 import type。
两点注意
- 解析这些类型会把处理函数的源码拉进来(
api.d.ts→api-v1/src/→core→ 框架 →tealina/utility-types)。后端的tealina和框架类型包要一直安装着;前端的skipLibCheck不能掩盖这里的错误,因为解析的是.ts,不是.d.ts。 @tealina/client仅支持 ESM。Axios 不是它的运行时依赖,axios 适配器只要求你传入自己的 请求函数。