@modern-js/main-doc 3.0.0-alpha.0 → 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/en/apis/app/commands.mdx +6 -30
- package/docs/en/components/bff-upload.mdx +3 -5
- package/docs/en/components/bundler.mdx +1 -1
- package/docs/en/components/enable-bff.mdx +6 -2
- package/docs/en/components/enable-ssg.mdx +1 -0
- package/docs/en/components/esbuild.mdx +2 -2
- package/docs/en/components/extend-bff-function.mdx +2 -4
- package/docs/en/components/hono.mdx +119 -0
- package/docs/en/components/international/custom-instance-code.mdx +16 -0
- package/docs/en/components/international/init-options-desc.mdx +1 -0
- package/docs/en/components/international/install-command.mdx +15 -1
- package/docs/en/components/international/instance-code.mdx +26 -0
- package/docs/en/configure/app/builder-plugins.mdx +1 -2
- package/docs/en/configure/app/dev/server.mdx +108 -0
- package/docs/en/configure/app/experiments/source-build.mdx +0 -1
- package/docs/en/configure/app/output/assets-retry.mdx +1 -1
- package/docs/en/configure/app/output/disable-inline-runtime-chunk.mdx +2 -2
- package/docs/en/configure/app/output/filename.mdx +2 -4
- package/docs/en/configure/app/output/temp-dir.mdx +3 -3
- package/docs/en/configure/app/performance/build-cache.mdx +1 -1
- package/docs/en/configure/app/performance/profile.mdx +1 -1
- package/docs/en/configure/app/plugins.mdx +1 -3
- package/docs/en/configure/app/runtime/router.mdx +0 -4
- package/docs/en/configure/app/security/sri.mdx +0 -1
- package/docs/en/configure/app/source/alias.mdx +1 -1
- package/docs/en/configure/app/source/enable-async-entry.mdx +1 -1
- package/docs/en/configure/app/source/include.mdx +2 -14
- package/docs/en/configure/app/tools/dev-server.mdx +8 -8
- package/docs/en/configure/app/usage.mdx +0 -12
- package/docs/en/guides/_meta.json +5 -0
- package/docs/en/guides/advanced-features/bff/_meta.json +9 -1
- package/docs/en/guides/advanced-features/bff/cross-project.mdx +1 -1
- package/docs/en/guides/advanced-features/bff/frameworks.mdx +2 -15
- package/docs/en/guides/advanced-features/bff/function.mdx +4 -4
- package/docs/en/guides/advanced-features/bff/operators.mdx +628 -0
- package/docs/en/guides/advanced-features/bff/sdk.mdx +17 -9
- package/docs/en/guides/advanced-features/bff/upload.mdx +3 -1
- package/docs/en/guides/advanced-features/international/configuration.mdx +7 -16
- package/docs/en/guides/advanced-features/international/quick-start.mdx +4 -32
- package/docs/en/guides/advanced-features/page-performance/optimize-bundle.mdx +1 -1
- package/docs/en/guides/advanced-features/page-performance/react-compiler.mdx +18 -4
- package/docs/en/guides/advanced-features/rspack-start.mdx +1 -1
- package/docs/en/guides/advanced-features/server-monitor/monitors.mdx +62 -5
- package/docs/en/guides/basic-features/data/data-cache.mdx +60 -76
- package/docs/en/guides/basic-features/data/data-fetch.mdx +15 -14
- package/docs/en/guides/basic-features/debug/proxy.mdx +6 -9
- package/docs/en/guides/basic-features/render/rsc.mdx +24 -19
- package/docs/en/guides/basic-features/render/ssg.mdx +4 -9
- package/docs/en/guides/basic-features/render/ssr-cache.mdx +0 -4
- package/docs/en/guides/basic-features/static-assets/svg-assets.mdx +0 -4
- package/docs/en/guides/get-started/tech-stack.mdx +1 -1
- package/docs/en/guides/upgrade/_meta.json +1 -0
- package/docs/en/guides/upgrade/config.mdx +936 -0
- package/docs/en/guides/upgrade/entry.mdx +463 -0
- package/docs/en/guides/upgrade/other.mdx +83 -0
- package/docs/en/guides/upgrade/overview.mdx +33 -0
- package/docs/en/guides/upgrade/tailwindcss.mdx +130 -0
- package/docs/en/guides/upgrade/web-server.mdx +91 -0
- package/docs/en/plugin/_meta.json +5 -0
- package/docs/en/plugin/cli-plugins/_meta.json +1 -1
- package/docs/en/plugin/cli-plugins/api.mdx +13 -63
- package/docs/en/plugin/cli-plugins/life-cycle.mdx +0 -4
- package/docs/en/plugin/introduction.mdx +8 -20
- package/docs/en/plugin/plugin-system.mdx +3 -3
- package/docs/en/plugin/runtime-plugins/_meta.json +1 -1
- package/docs/en/plugin/runtime-plugins/api.mdx +1 -1
- package/docs/en/plugin/server-plugins/_meta.json +1 -0
- package/docs/en/plugin/server-plugins/api.mdx +210 -1
- package/docs/en/plugin/server-plugins/life-cycle.mdx +41 -1
- package/docs/zh/apis/app/commands.mdx +6 -30
- package/docs/zh/components/bff-operator-code.mdx +5 -0
- package/docs/zh/components/bff-upload.mdx +0 -2
- package/docs/zh/components/bundler.mdx +1 -1
- package/docs/zh/components/enable-bff.mdx +6 -2
- package/docs/zh/components/enable-ssg.mdx +3 -1
- package/docs/zh/components/esbuild.mdx +2 -2
- package/docs/zh/components/extend-bff-function.mdx +2 -4
- package/docs/zh/components/hono.mdx +119 -0
- package/docs/zh/components/international/custom-instance-code.mdx +16 -0
- package/docs/zh/components/international/init-options-desc.mdx +1 -0
- package/docs/zh/components/international/install-command.mdx +15 -0
- package/docs/zh/components/international/instance-code.mdx +26 -0
- package/docs/zh/configure/app/builder-plugins.mdx +1 -2
- package/docs/zh/configure/app/dev/server.mdx +109 -2
- package/docs/zh/configure/app/experiments/source-build.mdx +0 -1
- package/docs/zh/configure/app/output/assets-retry.mdx +1 -1
- package/docs/zh/configure/app/output/disable-inline-runtime-chunk.mdx +2 -2
- package/docs/zh/configure/app/output/filename.mdx +2 -4
- package/docs/zh/configure/app/output/temp-dir.mdx +3 -3
- package/docs/zh/configure/app/performance/build-cache.mdx +1 -1
- package/docs/zh/configure/app/performance/profile.mdx +1 -1
- package/docs/zh/configure/app/plugins.mdx +1 -2
- package/docs/zh/configure/app/runtime/router.mdx +0 -4
- package/docs/zh/configure/app/security/sri.mdx +0 -1
- package/docs/zh/configure/app/source/alias.mdx +1 -1
- package/docs/zh/configure/app/source/enable-async-entry.mdx +1 -1
- package/docs/zh/configure/app/source/include.mdx +2 -16
- package/docs/zh/configure/app/tools/dev-server.mdx +5 -5
- package/docs/zh/configure/app/usage.mdx +0 -12
- package/docs/zh/guides/advanced-features/bff/_meta.json +9 -1
- package/docs/zh/guides/advanced-features/bff/frameworks.mdx +2 -16
- package/docs/zh/guides/advanced-features/bff/operators.mdx +628 -0
- package/docs/zh/guides/advanced-features/bff/sdk.mdx +19 -12
- package/docs/zh/guides/advanced-features/bff/upload.mdx +3 -1
- package/docs/zh/guides/advanced-features/international/configuration.mdx +7 -16
- package/docs/zh/guides/advanced-features/international/quick-start.mdx +2 -25
- package/docs/zh/guides/advanced-features/page-performance/optimize-bundle.mdx +1 -1
- package/docs/zh/guides/advanced-features/page-performance/react-compiler.mdx +18 -4
- package/docs/zh/guides/advanced-features/server-monitor/monitors.mdx +60 -5
- package/docs/zh/guides/basic-features/data/data-cache.mdx +47 -54
- package/docs/zh/guides/basic-features/data/data-fetch.mdx +9 -12
- package/docs/zh/guides/basic-features/debug/proxy.mdx +4 -7
- package/docs/zh/guides/basic-features/render/rsc.mdx +23 -37
- package/docs/zh/guides/basic-features/render/ssr-cache.mdx +0 -4
- package/docs/zh/guides/basic-features/static-assets/svg-assets.mdx +0 -4
- package/docs/zh/guides/get-started/tech-stack.mdx +1 -1
- package/docs/zh/guides/troubleshooting/builder.mdx +1 -1
- package/docs/zh/guides/upgrade/config.mdx +132 -1
- package/docs/zh/plugin/_meta.json +5 -0
- package/docs/zh/plugin/cli-plugins/_meta.json +1 -1
- package/docs/zh/plugin/cli-plugins/api.mdx +15 -65
- package/docs/zh/plugin/cli-plugins/life-cycle.mdx +0 -4
- package/docs/zh/plugin/introduction.mdx +4 -16
- package/docs/zh/plugin/plugin-system.mdx +3 -14
- package/docs/zh/plugin/runtime-plugins/_meta.json +1 -1
- package/docs/zh/plugin/runtime-plugins/api.mdx +1 -1
- package/docs/zh/plugin/server-plugins/_meta.json +1 -0
- package/docs/zh/plugin/server-plugins/api.mdx +210 -1
- package/docs/zh/plugin/server-plugins/life-cycle.mdx +41 -1
- package/package.json +2 -2
- package/src/components/FrameworkCode/index.tsx +605 -0
- package/docs/en/configure/app/performance/bundle-analyze.mdx +0 -24
- package/docs/en/configure/app/tools/babel.mdx +0 -225
- package/docs/en/plugin/cli-plugins/migration.mdx +0 -83
- package/docs/en/plugin/runtime-plugins/migration.mdx +0 -110
- package/docs/zh/components/default-mwa-generate.mdx +0 -4
- package/docs/zh/configure/app/performance/bundle-analyze.mdx +0 -24
- package/docs/zh/configure/app/tools/babel.mdx +0 -224
- package/docs/zh/plugin/cli-plugins/migration.mdx +0 -83
- package/docs/zh/plugin/runtime-plugins/migration.mdx +0 -110
- /package/docs/en/components/{router-legacy-tip.mdx → upgrade-config-deploy.mdx} +0 -0
- /package/docs/zh/components/{router-legacy-tip.mdx → upgrade-config-deploy.mdx} +0 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
title: 创建可扩展的 BFF 函数
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 创建可扩展的 BFF 函数
|
|
7
|
+
|
|
8
|
+
上一小节展示了如何在文件中导出一个简单的 BFF 函数。在更复杂的场景下,每个 BFF 函数可能需要做独立的类型校验,前置逻辑等。
|
|
9
|
+
|
|
10
|
+
因此,Modern.js 暴露了 `Api`,支持通过该 API 来创建 BFF 函数,通过这种方式创建的 BFF 函数能方便的进行功能拓展。
|
|
11
|
+
|
|
12
|
+
## 示例
|
|
13
|
+
|
|
14
|
+
:::caution 注意
|
|
15
|
+
|
|
16
|
+
- `Api` 函数只能在 ts 项目中使用,无法在纯 js 项目中使用。
|
|
17
|
+
- 操作符函数(如下述 `Get`,`Query` 等)依赖 [`zod`](https://www.npmjs.com/package/zod),需要先在项目中安装。
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
pnpm add zod
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
:::
|
|
24
|
+
|
|
25
|
+
一个由 `Api` 函数创建的 BFF 函数由以下几部分组成:
|
|
26
|
+
|
|
27
|
+
- `Api()`,定义接口的函数。
|
|
28
|
+
- `Get(path?: string)`,指定接口路由。
|
|
29
|
+
- `Query(schema: T)`,`Redirect(url: string)`,扩展接口,如指定接口入参。
|
|
30
|
+
- `Handler: (...args: any[]) => any | Promise<any>`,接口处理请求逻辑的函数。
|
|
31
|
+
|
|
32
|
+
服务端可以定义接口的入参与类型,根据类型,服务端在运行时会做自动的类型校验:
|
|
33
|
+
|
|
34
|
+
import BFFOperatorCode from '@site-docs/components/bff-operator-code';
|
|
35
|
+
|
|
36
|
+
<BFFOperatorCode>
|
|
37
|
+
|
|
38
|
+
```typescript title="api/lambda/user.ts"
|
|
39
|
+
import { Api, Post, Query, Data } from '@modern-js/plugin-bff/server';
|
|
40
|
+
import { z } from 'zod';
|
|
41
|
+
|
|
42
|
+
const UserSchema = z.object({
|
|
43
|
+
name: z.string().min(2).max(10),
|
|
44
|
+
email: z.string().email(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const DataSchema = z.object({
|
|
48
|
+
phone: z.string(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const addUser = Api(
|
|
52
|
+
Post('/user'),
|
|
53
|
+
Query(UserSchema),
|
|
54
|
+
Data(DataSchema),
|
|
55
|
+
async ({ query, data }) => ({
|
|
56
|
+
name: query.name,
|
|
57
|
+
phone: data.phone,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
</BFFOperatorCode>
|
|
63
|
+
|
|
64
|
+
:::caution 注意
|
|
65
|
+
使用 `Api` 函数的文件,要保证所有的代码逻辑都放在函数内。如函数外做 `console.log`、使用 `fs` 等操作都是不允许的。
|
|
66
|
+
|
|
67
|
+
:::
|
|
68
|
+
|
|
69
|
+
浏览器端同样可以使用一体化调用的方式,拥有静态类型提示:
|
|
70
|
+
|
|
71
|
+
```typescript title="routes/page.tsx"
|
|
72
|
+
import { addUser } from '@api/user';
|
|
73
|
+
|
|
74
|
+
addUser({
|
|
75
|
+
query: {
|
|
76
|
+
name: 'modern.js',
|
|
77
|
+
email: 'modern.js@example.com',
|
|
78
|
+
},
|
|
79
|
+
data: {
|
|
80
|
+
phone: '12345',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 接口路由
|
|
86
|
+
|
|
87
|
+
如下面示例,你可以通过 `Get` 函数指定路由和 HTTP Method:
|
|
88
|
+
|
|
89
|
+
<BFFOperatorCode>
|
|
90
|
+
|
|
91
|
+
```typescript title="api/lambda/user.ts"
|
|
92
|
+
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
|
|
93
|
+
|
|
94
|
+
// 指定接口路由,Modern.js 默认设置 `bff.prefix` 为 `/api`,
|
|
95
|
+
// 因此该接口路由为 `/api/user`,Http Method 为 GET。
|
|
96
|
+
export const getHello = Api(
|
|
97
|
+
Get('/hello'),
|
|
98
|
+
Query(HelloSchema),
|
|
99
|
+
async ({ query }) => query,
|
|
100
|
+
);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
</BFFOperatorCode>
|
|
104
|
+
|
|
105
|
+
当未指定路由时,接口路由根据文件约定定义,如下面示例,函数写法下,有代码路径 `api/lambda/user.ts`,会注册相应的接口 `/api/user`。
|
|
106
|
+
|
|
107
|
+
<BFFOperatorCode>
|
|
108
|
+
|
|
109
|
+
```typescript title="api/lambda/user.ts"
|
|
110
|
+
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
|
|
111
|
+
|
|
112
|
+
// 未指定接口路由,根据文件约定和函数名,该接口为 api/user,Http Method 为 get。
|
|
113
|
+
export const get = Api(Query(UserSchema), async ({ query }) => query);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
</BFFOperatorCode>
|
|
117
|
+
|
|
118
|
+
:::info
|
|
119
|
+
Modern.js 推荐基于文件约定去定义接口,保持项目中路由清晰。具体规则可见[函数路由](/guides/advanced-features/bff/function#函数路由)。
|
|
120
|
+
|
|
121
|
+
:::
|
|
122
|
+
|
|
123
|
+
除了 `Get` 函数外,你可以使用以下函数定义 Http 接口:
|
|
124
|
+
|
|
125
|
+
| 函数 | 说明 |
|
|
126
|
+
| :--------------------- | :---------------- |
|
|
127
|
+
| Get(path?: string) | 接受 Get 请求 |
|
|
128
|
+
| Post(path?: string) | 接受 POST 请求 |
|
|
129
|
+
| Put(path?: string) | 接受 PUT 请求 |
|
|
130
|
+
| Delete(path?: string) | 接受 DELETE 请求 |
|
|
131
|
+
| Patch(path?: string) | 接受 PATCH 请求 |
|
|
132
|
+
| Head(path?: string) | 接受 HEAD 请求 |
|
|
133
|
+
| Options(path?: string) | 接受 OPTIONS 请求 |
|
|
134
|
+
|
|
135
|
+
## 请求
|
|
136
|
+
|
|
137
|
+
以下为请求相关的操作符,操作符可以组合使用,但需符合 Http 协议,如 get 请求无法使用 Data 操作符。
|
|
138
|
+
|
|
139
|
+
### 查询参数 Query
|
|
140
|
+
|
|
141
|
+
使用 `Query` 函数可以定义 query 的类型,使用 `Query` 函数后,接口处理函数的入参中就可以拿到 query 信息,前端请求函数的入参中可以加入 `query` 字段:
|
|
142
|
+
|
|
143
|
+
<BFFOperatorCode>
|
|
144
|
+
|
|
145
|
+
```typescript title="api/lambda/user.ts"
|
|
146
|
+
// 服务端代码
|
|
147
|
+
import { Api, Query } from '@modern-js/plugin-bff/server';
|
|
148
|
+
import { z } from 'zod';
|
|
149
|
+
|
|
150
|
+
const UserSchema = z.object({
|
|
151
|
+
name: z.string().min(2).max(10),
|
|
152
|
+
email: z.string().email(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export const get = Api(Query(UserSchema), async ({ query }) => ({
|
|
156
|
+
name: query.name,
|
|
157
|
+
}));
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
</BFFOperatorCode>
|
|
161
|
+
|
|
162
|
+
```typescript title="routes/page.tsx"
|
|
163
|
+
// 前端代码
|
|
164
|
+
get({
|
|
165
|
+
query: {
|
|
166
|
+
name: 'modern.js',
|
|
167
|
+
email: 'modern.js@example.com',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Query 参数类型转换
|
|
173
|
+
|
|
174
|
+
URL query 参数默认是字符串类型,如果需要数字类型,需要使用 `z.coerce.number()` 进行类型转换:
|
|
175
|
+
|
|
176
|
+
<BFFOperatorCode>
|
|
177
|
+
|
|
178
|
+
```typescript title="api/lambda/user.ts"
|
|
179
|
+
import { Api, Get, Query } from '@modern-js/plugin-bff/server';
|
|
180
|
+
import { z } from 'zod';
|
|
181
|
+
|
|
182
|
+
const QuerySchema = z.object({
|
|
183
|
+
id: z.string(),
|
|
184
|
+
page: z.coerce.number().min(1).max(100), // 使用 z.coerce.number() 转换字符串到数字
|
|
185
|
+
status: z.enum(['active', 'inactive']),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export const getUser = Api(
|
|
189
|
+
Get('/user'),
|
|
190
|
+
Query(QuerySchema),
|
|
191
|
+
async ({ query }) => {
|
|
192
|
+
return {
|
|
193
|
+
id: query.id,
|
|
194
|
+
page: query.page, // page 是 number 类型
|
|
195
|
+
status: query.status,
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
</BFFOperatorCode>
|
|
202
|
+
|
|
203
|
+
:::caution 注意
|
|
204
|
+
URL query 参数都是字符串类型,如果需要数字类型,需要使用 `z.coerce.number()` 进行转换,而不是直接使用 `z.number()`。
|
|
205
|
+
:::
|
|
206
|
+
|
|
207
|
+
### 传递数据 Data
|
|
208
|
+
|
|
209
|
+
使用 `Data` 函数可以定义接口传递数据的类型,使用 `Data` 后,接口处理函数的入参中就可以拿到接口数据信息。
|
|
210
|
+
|
|
211
|
+
:::caution
|
|
212
|
+
使用 Data 函数的话,必须遵循 HTTP 协议,HTTP Method 为 Get 或 Head 时,无法使用 Data 函数。
|
|
213
|
+
|
|
214
|
+
:::
|
|
215
|
+
|
|
216
|
+
<BFFOperatorCode>
|
|
217
|
+
|
|
218
|
+
```typescript title="api/lambda/user.ts"
|
|
219
|
+
import { Api, Data } from '@modern-js/plugin-bff/server';
|
|
220
|
+
import { z } from 'zod';
|
|
221
|
+
|
|
222
|
+
const DataSchema = z.object({
|
|
223
|
+
name: z.string(),
|
|
224
|
+
phone: z.string(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const post = Api(Data(DataSchema), async ({ data }) => ({
|
|
228
|
+
name: data.name,
|
|
229
|
+
phone: data.phone,
|
|
230
|
+
}));
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
</BFFOperatorCode>
|
|
234
|
+
|
|
235
|
+
```typescript title="routes/page.tsx"
|
|
236
|
+
// 前端代码
|
|
237
|
+
post({
|
|
238
|
+
data: {
|
|
239
|
+
name: 'modern.js',
|
|
240
|
+
phone: '12345',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 路由参数 Params
|
|
246
|
+
|
|
247
|
+
路由参数可以实现动态路由,并且从路径中获取参数。可以通过 `Params<T>(schema: z.ZodType<T>)` 指定路径参数
|
|
248
|
+
|
|
249
|
+
<BFFOperatorCode>
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { Api, Get, Params } from '@modern-js/plugin-bff/server';
|
|
253
|
+
import { z } from 'zod';
|
|
254
|
+
|
|
255
|
+
const UserSchema = z.object({
|
|
256
|
+
id: z.string(),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export const queryUser = Api(
|
|
260
|
+
Get('/user/:id'),
|
|
261
|
+
Params(UserSchema),
|
|
262
|
+
async ({ params }) => ({
|
|
263
|
+
name: params.id,
|
|
264
|
+
}),
|
|
265
|
+
);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
</BFFOperatorCode>
|
|
269
|
+
|
|
270
|
+
### 请求头 Headers
|
|
271
|
+
|
|
272
|
+
可以通过 `Headers<T>(schema: z.ZodType<T>)` 函数定义接口需要的请求头,并通过一体化调用传递请求头:
|
|
273
|
+
|
|
274
|
+
<BFFOperatorCode>
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { Api, Headers } from '@modern-js/plugin-bff/server';
|
|
278
|
+
import { z } from 'zod';
|
|
279
|
+
|
|
280
|
+
const headerSchema = z.object({
|
|
281
|
+
token: z.string(),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
export const queryUser = Api(Headers(headerSchema), async ({ headers }) => ({
|
|
285
|
+
name: headers.token,
|
|
286
|
+
}));
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
</BFFOperatorCode>
|
|
290
|
+
|
|
291
|
+
## 参数校验
|
|
292
|
+
|
|
293
|
+
如前面提到的,当使用 `Query`,`Data` 等函数定义接口时,服务端会根据这些函数传入的 schema,对前端传入的数据做自动的校验。
|
|
294
|
+
|
|
295
|
+
当校验失败时,可以通过 Try/Catch 捕获错误:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
try {
|
|
299
|
+
const res = await postUser({
|
|
300
|
+
query: {
|
|
301
|
+
user: 'modern.js',
|
|
302
|
+
},
|
|
303
|
+
data: {
|
|
304
|
+
message: 'hello',
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
return res;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.log(error.data.code); // VALIDATION_ERROR
|
|
310
|
+
console.log(JSON.parse(error.data.message));
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
同时,可以通过 `error.data.message` 获取完整的错误信息:
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
[
|
|
318
|
+
{
|
|
319
|
+
code: 'invalid_string',
|
|
320
|
+
message: "Invalid email",
|
|
321
|
+
path: [0, 'user'],
|
|
322
|
+
validation: "email"
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## 中间件 Middleware
|
|
328
|
+
|
|
329
|
+
可以通过 `Middleware` 操作符设置函数中间件,函数中间件会在校验和接口逻辑之前执行。
|
|
330
|
+
|
|
331
|
+
:::info
|
|
332
|
+
`Middleware` 操作符可以配置多次,中间件的执行顺序为从上至下
|
|
333
|
+
|
|
334
|
+
:::
|
|
335
|
+
|
|
336
|
+
<BFFOperatorCode>
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { Api, Query, Middleware } from '@modern-js/plugin-bff/server';
|
|
340
|
+
import { z } from 'zod';
|
|
341
|
+
|
|
342
|
+
const UserSchema = z.object({
|
|
343
|
+
name: z.string().min(2).max(10),
|
|
344
|
+
email: z.string().email(),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
export const get = Api(
|
|
348
|
+
Query(UserSchema),
|
|
349
|
+
Middleware(async (c, next) => {
|
|
350
|
+
console.info(`access url: ${c.req.url}`);
|
|
351
|
+
await next();
|
|
352
|
+
}),
|
|
353
|
+
async ({ query }) => ({
|
|
354
|
+
name: query.name,
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
</BFFOperatorCode>
|
|
360
|
+
|
|
361
|
+
## 数据转换 Pipe
|
|
362
|
+
|
|
363
|
+
`Pipe` 操作符可以传入一个函数,在中间件和校验完成之后执行,主要有以下场景可以使用:
|
|
364
|
+
|
|
365
|
+
1. 对请求携带的查询参数或数据进行转换。
|
|
366
|
+
2. 对请求的数据进行自定义校验,如果校验失败,可以选择抛出异常,或者直接返回错误的信息。
|
|
367
|
+
3. 希望只做校验,不执行接口逻辑,(如前端不做单独的校验,使用接口做校验,但在一些场景下又不希望接口逻辑执行)可以在此函数中终止后续的执行。
|
|
368
|
+
|
|
369
|
+
`Pipe` 定义转换函数,转换函数的入参是接口请求携带的 `query`,`data` 和 `headers`,返回值会传递给下一个 `Pipe` 函数或接口处理函数作为入参,所以返回值的数据结构一般需和入参相同。
|
|
370
|
+
|
|
371
|
+
:::info
|
|
372
|
+
`Pipe` 操作符可以配置多次,函数的执行顺序为从上至下,前一个函数的返回值,是后一个函数的入参。
|
|
373
|
+
|
|
374
|
+
:::
|
|
375
|
+
|
|
376
|
+
<BFFOperatorCode>
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
380
|
+
import { z } from 'zod';
|
|
381
|
+
|
|
382
|
+
const UserSchema = z.object({
|
|
383
|
+
name: z.string().min(2).max(10),
|
|
384
|
+
email: z.string(),
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
export const get = Api(
|
|
388
|
+
Query(UserSchema),
|
|
389
|
+
Pipe<{
|
|
390
|
+
query: z.infer<typeof UserSchema>;
|
|
391
|
+
}>(input => {
|
|
392
|
+
const { query } = input;
|
|
393
|
+
if (!query.email.includes('@')) {
|
|
394
|
+
query.email = `${query.email}@example.com`;
|
|
395
|
+
}
|
|
396
|
+
return input;
|
|
397
|
+
}),
|
|
398
|
+
async ({ query }) => ({
|
|
399
|
+
name: query.name,
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
</BFFOperatorCode>
|
|
405
|
+
|
|
406
|
+
同时,
|
|
407
|
+
|
|
408
|
+
<BFFOperatorCode>
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
412
|
+
import { z } from 'zod';
|
|
413
|
+
|
|
414
|
+
const UserSchema = z.object({
|
|
415
|
+
name: z.string().min(2).max(10),
|
|
416
|
+
email: z.string().email(),
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
export const get = Api(
|
|
420
|
+
Query(UserSchema),
|
|
421
|
+
Pipe<{
|
|
422
|
+
query: z.infer<typeof UserSchema>;
|
|
423
|
+
}>((input, end) => {
|
|
424
|
+
const { query } = input;
|
|
425
|
+
const { name, email } = query;
|
|
426
|
+
if (!email.startsWith(name)) {
|
|
427
|
+
return end({
|
|
428
|
+
message: 'email must start with name',
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return input;
|
|
432
|
+
}),
|
|
433
|
+
async ({ query }) => ({
|
|
434
|
+
name: query.name,
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
</BFFOperatorCode>
|
|
440
|
+
|
|
441
|
+
如果需要对响应做更多自定义操作,可以给 `end` 函数传入一个函数,函数的入参是 Hono 的 Context (`c`),可以对 `c.req` 和 `c.res` 进行操作:
|
|
442
|
+
|
|
443
|
+
<BFFOperatorCode>
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
447
|
+
import { z } from 'zod';
|
|
448
|
+
|
|
449
|
+
const UserSchema = z.object({
|
|
450
|
+
name: z.string().min(2).max(10),
|
|
451
|
+
email: z.string().email(),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
export const get = Api(
|
|
455
|
+
Query(UserSchema),
|
|
456
|
+
Pipe<{
|
|
457
|
+
query: z.infer<typeof UserSchema>;
|
|
458
|
+
}>((input, end) => {
|
|
459
|
+
const { query } = input;
|
|
460
|
+
const { name, email } = query;
|
|
461
|
+
if (!email.startsWith(name)) {
|
|
462
|
+
return end(c => {
|
|
463
|
+
c.res.status = 400;
|
|
464
|
+
c.res.body = {
|
|
465
|
+
message: 'email must start with name',
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return input;
|
|
470
|
+
}),
|
|
471
|
+
async ({ query }) => ({
|
|
472
|
+
name: query.name,
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
</BFFOperatorCode>
|
|
478
|
+
|
|
479
|
+
## 响应
|
|
480
|
+
|
|
481
|
+
以下为响应相关操作符,通过响应操作符可以对响应进行处理。
|
|
482
|
+
|
|
483
|
+
### 状态码 HttpCode
|
|
484
|
+
|
|
485
|
+
可以通过 `HttpCode(statusCode: number)` 函数指定接口返回的状态码
|
|
486
|
+
|
|
487
|
+
<BFFOperatorCode>
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import { Api, Query, Data, HttpCode } from '@modern-js/plugin-bff/server';
|
|
491
|
+
import { z } from 'zod';
|
|
492
|
+
|
|
493
|
+
const UserSchema = z.object({
|
|
494
|
+
name: z.string().min(2).max(10),
|
|
495
|
+
email: z.string().email(),
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const DataSchema = z.object({
|
|
499
|
+
phone: z.string(),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
export const post = Api(
|
|
503
|
+
Query(UserSchema),
|
|
504
|
+
Data(DataSchema),
|
|
505
|
+
HttpCode(202),
|
|
506
|
+
async ({ query, data }) => {
|
|
507
|
+
someTask({
|
|
508
|
+
user: {
|
|
509
|
+
...query,
|
|
510
|
+
...data,
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
</BFFOperatorCode>
|
|
518
|
+
|
|
519
|
+
### 响应头 SetHeaders
|
|
520
|
+
|
|
521
|
+
支持通过 `SetHeaders(headers: Record<string, string>)` 函数设置响应头
|
|
522
|
+
|
|
523
|
+
<BFFOperatorCode>
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
import { Api, Get, SetHeaders } from '@modern-js/plugin-bff/server';
|
|
527
|
+
|
|
528
|
+
export default Api(
|
|
529
|
+
Get('/hello'),
|
|
530
|
+
SetHeaders({
|
|
531
|
+
'x-log-id': 'xxx',
|
|
532
|
+
}),
|
|
533
|
+
async () => 'Hello World!',
|
|
534
|
+
);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
</BFFOperatorCode>
|
|
538
|
+
|
|
539
|
+
### 重定向 Redirect
|
|
540
|
+
|
|
541
|
+
支持通过 `Redirect(url: string)` 对接口做重定向:
|
|
542
|
+
|
|
543
|
+
<BFFOperatorCode>
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
import { Api, Get, Redirect } from '@modern-js/plugin-bff/server';
|
|
547
|
+
|
|
548
|
+
export default Api(
|
|
549
|
+
Get('/hello'),
|
|
550
|
+
Redirect('https://modernjs.dev/'),
|
|
551
|
+
async () => 'Hello Modern.js!',
|
|
552
|
+
);
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
</BFFOperatorCode>
|
|
556
|
+
|
|
557
|
+
## 请求上下文
|
|
558
|
+
|
|
559
|
+
如上面所述,通过操作符可以执行接口处理函数的入参,获得 `query`,`data`,`params` 等。但有时我们需要获得更多请求上下文的信息,此时可以通过 [`useHonoContext`](/apis/app/runtime/bff/use-hono-context) 获取:
|
|
560
|
+
|
|
561
|
+
<BFFOperatorCode>
|
|
562
|
+
|
|
563
|
+
```typescript title="api/lambda/user.ts"
|
|
564
|
+
import { Api, Get, Query, useHonoContext } from '@modern-js/plugin-bff/server';
|
|
565
|
+
import { z } from 'zod';
|
|
566
|
+
|
|
567
|
+
const UserSchema = z.object({
|
|
568
|
+
name: z.string().min(2).max(10),
|
|
569
|
+
email: z.string().email(),
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
export const queryUser = Api(
|
|
573
|
+
Get('/user'),
|
|
574
|
+
Query(UserSchema),
|
|
575
|
+
async ({ query }) => {
|
|
576
|
+
const c = useHonoContext();
|
|
577
|
+
const userAgent = c.req.header('user-agent');
|
|
578
|
+
return {
|
|
579
|
+
name: query.name,
|
|
580
|
+
userAgent,
|
|
581
|
+
};
|
|
582
|
+
},
|
|
583
|
+
);
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
</BFFOperatorCode>
|
|
587
|
+
|
|
588
|
+
## 常见问题
|
|
589
|
+
|
|
590
|
+
### 是否可以使用 ts 代替 zod schema
|
|
591
|
+
|
|
592
|
+
如果你想使用 ts ,而不是 zod schema,可以使用 [ts-to-zod](https://www.npmjs.com/package/ts-to-zod),先将 ts 转为 zod schema,然后使用转换后的 schema。
|
|
593
|
+
|
|
594
|
+
我们选用 zod ,而不是纯粹的 ts 定义入参类型信息的原因是:
|
|
595
|
+
|
|
596
|
+
- zod 学习成本足够低。
|
|
597
|
+
- 在校验这个场景,zod schema 拥有比 ts 更强的表达能力。
|
|
598
|
+
- zod 更容易扩展。
|
|
599
|
+
- 在运行时获取 ts 静态类型信息的方案都不够成熟。
|
|
600
|
+
|
|
601
|
+
具体可以参考不同方案的比较,可以参考[为什么使用 zod](https://bytedance.feishu.cn/wiki/wikcnrNnidvxHLY2SIT4nadXOCh#doxcnGoki68KEOiw8UD1fYd3lRh) ,如果有更多的想法和疑问,也欢迎联系我们。
|
|
602
|
+
|
|
603
|
+
## 更多实践
|
|
604
|
+
|
|
605
|
+
### 为接口添加 http 缓存
|
|
606
|
+
|
|
607
|
+
在前端开发中,有些服务端接口(如一些配置接口)响应时间会比较久,但其实长时间无需更新,针对这类接口我们可以设置 HTTP 缓存以提高页面的性能:
|
|
608
|
+
|
|
609
|
+
<BFFOperatorCode>
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
import { Api, SetHeaders } from '@modern-js/plugin-bff/server';
|
|
613
|
+
|
|
614
|
+
export const get = Api(
|
|
615
|
+
// 缓存使用一体化调用或者 fetch 进行请求才会生效
|
|
616
|
+
// 在 1s 内,缓存不做验证,直接返回响应
|
|
617
|
+
// 1s-60s 内获取先返回旧的缓存信息,同时重新发起验证请求,使用新值填充缓存
|
|
618
|
+
SetHeaders({
|
|
619
|
+
'Cache-Control': 'max-age=1, stale-while-revalidate=59',
|
|
620
|
+
}),
|
|
621
|
+
async () => {
|
|
622
|
+
await wait(500);
|
|
623
|
+
return 'Hello Modern.js';
|
|
624
|
+
},
|
|
625
|
+
);
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
</BFFOperatorCode>
|
|
@@ -13,15 +13,14 @@ BFF 函数的一体化调用在 CSR 和 SSR 是同构的。Modern.js 封装的
|
|
|
13
13
|
|
|
14
14
|
:::
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
```tsx title="routes/page.tsx"
|
|
18
17
|
import { configure } from '@modern-js/plugin-bff/client';
|
|
19
18
|
|
|
20
19
|
configure({
|
|
21
20
|
// ...
|
|
22
|
-
})
|
|
21
|
+
});
|
|
23
22
|
|
|
24
|
-
const Index = () => <div>Hello world</div
|
|
23
|
+
const Index = () => <div>Hello world</div>;
|
|
25
24
|
export default Index;
|
|
26
25
|
```
|
|
27
26
|
|
|
@@ -34,15 +33,15 @@ export default Index;
|
|
|
34
33
|
目前以下请求头在 Modernjs 中是自动透传的:
|
|
35
34
|
|
|
36
35
|
```ts
|
|
37
|
-
['cookie', 'user-agent', 'x-tt-logid', 'x-tt-stress']
|
|
36
|
+
['cookie', 'user-agent', 'x-tt-logid', 'x-tt-stress'];
|
|
38
37
|
```
|
|
39
38
|
|
|
40
39
|
可以通过 `configure` 配置请求头。例如以下例子,Modern.js 会自动将 SSR 页面请求的 `x-uid` 信息透传给 BFF 服务:
|
|
41
40
|
|
|
42
41
|
```tsx
|
|
43
42
|
configure({
|
|
44
|
-
allowedHeaders: ['x-uid']
|
|
45
|
-
})
|
|
43
|
+
allowedHeaders: ['x-uid'],
|
|
44
|
+
});
|
|
46
45
|
```
|
|
47
46
|
|
|
48
47
|
## 添加拦截器
|
|
@@ -53,12 +52,17 @@ configure({
|
|
|
53
52
|
configure({
|
|
54
53
|
// 这里的 request 是一体化默认的请求工具,interceptor 函数需返回一个新的 request。
|
|
55
54
|
// 新 request 的出参必须是 parse body 之后的结果
|
|
56
|
-
interceptor(request){
|
|
57
|
-
return async(url, params) => {
|
|
55
|
+
interceptor(request) {
|
|
56
|
+
return async (url, params) => {
|
|
58
57
|
const res = await request(url, params);
|
|
59
|
-
|
|
58
|
+
// 拦截器可能返回 Response 对象,需要手动解析 JSON
|
|
59
|
+
if (res instanceof Response) {
|
|
60
|
+
return res.json();
|
|
61
|
+
}
|
|
62
|
+
// 如果已经是解析后的数据,直接返回
|
|
63
|
+
return res;
|
|
60
64
|
};
|
|
61
|
-
}
|
|
65
|
+
},
|
|
62
66
|
});
|
|
63
67
|
```
|
|
64
68
|
|
|
@@ -70,7 +74,10 @@ configure({
|
|
|
70
74
|
import nodeFetch from 'node-fetch';
|
|
71
75
|
|
|
72
76
|
const customFetch = (input: RequestInfo | URL, init: RequestInit) => {
|
|
73
|
-
const curFetch =
|
|
77
|
+
const curFetch =
|
|
78
|
+
process.env.MODERN_TARGET !== 'node'
|
|
79
|
+
? fetch
|
|
80
|
+
: (nodeFetch as unknown as typeof fetch);
|
|
74
81
|
return curFetch(input, init).then(async res => {
|
|
75
82
|
const data = await res.json();
|
|
76
83
|
data.hello = 'hello custom sdk';
|
|
@@ -99,7 +106,7 @@ configure({
|
|
|
99
106
|
async request(...config: Parameters<typeof fetch>) {
|
|
100
107
|
const [url, params] = config;
|
|
101
108
|
const res = await axios({
|
|
102
|
-
url: url as string,
|
|
109
|
+
url: url as string, // 这里因为 fetch 和 axios 类型有些不兼容,需要使用 as
|
|
103
110
|
method: params?.method as Method,
|
|
104
111
|
data: params?.body,
|
|
105
112
|
headers: params?.headers as Headers,
|