@bleedingdev/modern-js-main-doc 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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/components/deploy-command.mdx +1 -0
- package/docs/en/components/hono.mdx +3 -3
- package/docs/en/components/init-app.mdx +1 -5
- package/docs/en/components/prerequisites.mdx +1 -1
- package/docs/en/configure/app/bff/effect.mdx +34 -36
- package/docs/en/configure/app/source/react-compiler.mdx +2 -0
- package/docs/en/guides/advanced-features/bff/data-platform.mdx +2 -2
- package/docs/en/guides/advanced-features/bff/frameworks.mdx +33 -21
- package/docs/en/guides/advanced-features/bff/function.mdx +2 -2
- package/docs/en/guides/advanced-features/bff/operators.mdx +17 -17
- package/docs/en/guides/basic-features/render/ssr-cache.mdx +14 -1
- package/docs/en/guides/get-started/ultramodern.mdx +14 -97
- package/docs/en/plugin/server-plugins/api.mdx +1 -0
- package/docs/zh/components/bff-operator-code.mdx +1 -1
- package/docs/zh/components/deploy-command.mdx +1 -0
- package/docs/zh/components/hono.mdx +3 -3
- package/docs/zh/components/prerequisites.mdx +1 -1
- package/docs/zh/configure/app/bff/effect.mdx +28 -33
- package/docs/zh/configure/app/source/react-compiler.mdx +2 -0
- package/docs/zh/guides/advanced-features/bff/data-platform.mdx +2 -2
- package/docs/zh/guides/advanced-features/bff/frameworks.mdx +31 -20
- package/docs/zh/guides/advanced-features/bff/function.mdx +2 -2
- package/docs/zh/guides/advanced-features/bff/operators.mdx +17 -17
- package/docs/zh/guides/basic-features/render/ssr-cache.mdx +14 -1
- package/docs/zh/guides/get-started/ultramodern.mdx +17 -83
- package/docs/zh/plugin/server-plugins/api.mdx +1 -0
- package/package.json +10 -10
- package/src/sandbox/csr-auth/src/routes/Auth-tsx.txt +10 -6
- package/src/sandbox/csr-auth/src/routes/page-tsx.txt +1 -0
- package/ultramodern-preset/package.json +2 -2
|
@@ -18,17 +18,9 @@ UltraModern.js 3.0 is our SuperApp framework forked from Modern.js. It keeps the
|
|
|
18
18
|
- Add platform-level contracts only where they improve cross-team reliability.
|
|
19
19
|
- Keep escape hatches explicit and outside the generated HTTP API path.
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Current Workspace Contract
|
|
22
22
|
|
|
23
|
-
UltraModern.js
|
|
24
|
-
|
|
25
|
-
- Modern.js app/config/plugin mental model.
|
|
26
|
-
- Existing project structure and command flow.
|
|
27
|
-
- Progressive adoption path (apps can stay mostly unchanged).
|
|
28
|
-
|
|
29
|
-
UltraModern.js additions are designed as the default product surface for new SuperApps. The framework direction is Effect + TanStack + SSR + Micro Verticals, and generated HTTP API work uses that direction by default instead of preserving parallel raw-handler layouts.
|
|
30
|
-
Existing Modern.js apps can migrate gradually; once an API surface is generated
|
|
31
|
-
or migrated as UltraModern HTTP API, it must use the strict Effect HttpApi path.
|
|
23
|
+
UltraModern.js uses Effect HttpApi, TanStack Router, SSR, and independently deployable Micro Verticals. Generated workspaces use `api/index.ts`, `shared/api.ts`, and `src/api/*` with concrete Effect schemas. The CLI creates, adds to, and validates workspaces against the current contract.
|
|
32
24
|
|
|
33
25
|
## Intentional Differences (v3 line)
|
|
34
26
|
|
|
@@ -56,21 +48,9 @@ or migrated as UltraModern HTTP API, it must use the strict Effect HttpApi path.
|
|
|
56
48
|
- Generated UltraModern API work uses the Effect runtime only; raw Hono/function handlers are not part of the generated API architecture.
|
|
57
49
|
- We make incompatible scaffold changes when they remove architecture drift.
|
|
58
50
|
|
|
59
|
-
##
|
|
60
|
-
|
|
61
|
-
For teams already on Modern.js 3.0 or an older BleedingDev UltraModern scaffold, the adoption path is to move API work onto the strict Effect HttpApi surface instead of preserving older raw handler layouts.
|
|
62
|
-
|
|
63
|
-
1. Keep existing Modern.js apps running as-is while they are outside the generated UltraModern surface. TanStack Router is the preferred path for new scaffolds and incremental route adoption, but route migration can happen on the team's schedule.
|
|
64
|
-
2. Use `bff.runtimeFramework: 'effect'` with `bff.effect.strictEffectApproach: true` for API work. Entries live at `api/index.ts`, contracts live at `shared/api.ts`, clients live under `src/api/*`, and request/response/error shapes come from Effect `Schema` plus `HttpApi`.
|
|
65
|
-
3. Treat raw handlers, `api/lambda/**`, manual `Response` construction, and manual request parsing as migration defects in generated or migrated UltraModern workspaces.
|
|
66
|
-
4. The public preset now ships with explicit release and certification gates. Generated workspaces include `.github/workflows/ultramodern-workspace-gates.yml`, so `pnpm check` and `pnpm build` stay part of the local adoption contract from day one while CI runs the primitive gates as parallel matrix jobs.
|
|
67
|
-
|
|
68
|
-
For an older generated workspace, migrate by treating the published cohort as
|
|
69
|
-
the source of truth:
|
|
51
|
+
## Create and Validate a Workspace
|
|
70
52
|
|
|
71
53
|
```bash
|
|
72
|
-
pnpm dlx @bleedingdev/modern-js-ultramodern-create@latest --help
|
|
73
|
-
pnpm dlx @bleedingdev/modern-js-ultramodern-create@latest catalog --vertical --dry-run
|
|
74
54
|
pnpm dlx @bleedingdev/modern-js-ultramodern-create@latest catalog --vertical
|
|
75
55
|
mise install
|
|
76
56
|
mise exec -- pnpm install
|
|
@@ -78,94 +58,31 @@ mise exec -- pnpm check
|
|
|
78
58
|
mise exec -- pnpm build
|
|
79
59
|
```
|
|
80
60
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Current cohorts localize Cloudflare SSR workspaces.
|
|
84
|
-
The framework moves bare locale-root redirects into the framework-owned
|
|
85
|
-
Cloudflare Worker entry and the i18n server runtime. A request for `/` is
|
|
86
|
-
redirected server-side with `302` to the negotiated locale path, for example
|
|
87
|
-
`/cs` for `Accept-Language: cs-CZ` or `/en` for English and fallback traffic.
|
|
88
|
-
|
|
89
|
-
Existing generated workspaces should upgrade the whole BleedingDev Modern
|
|
90
|
-
package cohort together. Resolve the current cohort version first, then run
|
|
91
|
-
the matching migration:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
COHORT="$(npm view @bleedingdev/modern-js-ultramodern-create version)"
|
|
95
|
-
pnpm dlx "@bleedingdev/modern-js-ultramodern-create@$COHORT" ultramodern \
|
|
96
|
-
migrate-strict-effect --version "$COHORT"
|
|
97
|
-
pnpm install
|
|
98
|
-
pnpm check
|
|
99
|
-
pnpm build
|
|
100
|
-
pnpm cloudflare:build
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
For deployed Cloudflare Workers, redeploy after the cohort update and verify
|
|
104
|
-
the root response before accepting the migration:
|
|
61
|
+
Localized Cloudflare SSR workspaces redirect `/` on the server to the negotiated locale. For example, `Accept-Language: cs-CZ` returns `302` with `Location: /cs`, `Cache-Control: private, no-store`, and `Vary` covering locale detection headers. Validate the deployed Worker with:
|
|
105
62
|
|
|
106
63
|
```bash
|
|
107
64
|
curl -I -H 'Accept-Language: cs-CZ,cs;q=0.9,en;q=0.1' https://<worker-host>/
|
|
108
65
|
```
|
|
109
66
|
|
|
110
|
-
The expected response is `302` with `Location: /cs` or the matching locale,
|
|
111
|
-
`Cache-Control: private, no-store`, and `Vary` covering the locale detection
|
|
112
|
-
headers. Following the redirect should return the SSR locale page with the
|
|
113
|
-
matching document language and i18n SSR data.
|
|
114
|
-
|
|
115
|
-
Do not fix older root `404` responses by adding app-owned root route files,
|
|
116
|
-
client-side redirects, custom navigation wrappers, Cloudflare Worker
|
|
117
|
-
postprocessing, generated output edits, or local redirect shims. If `/` still
|
|
118
|
-
returns `404` after upgrading, confirm the production build log shows
|
|
119
|
-
`Modern.js Framework v<cohort-version>` for the cohort you migrated to and
|
|
120
|
-
redeploy the Worker.
|
|
121
|
-
|
|
122
|
-
Strict generated API migration is part of every current cohort: the direct
|
|
123
|
-
`api/index.ts` generator, generated `.mts` checks, the strict Oxlint boundary
|
|
124
|
-
rule set, Effect cohort overrides, and the strict Effect migration command.
|
|
125
|
-
Agents that cannot install the BleedingDev cohort yet should use the local
|
|
126
|
-
Modern.js workspace for migration validation; otherwise pin the target cohort
|
|
127
|
-
with `--ultramodern-package-version`.
|
|
128
|
-
|
|
129
|
-
Before hand-editing package aliases or generated metadata, run the framework
|
|
130
|
-
migration command from the target workspace:
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
COHORT="$(npm view @bleedingdev/modern-js-ultramodern-create version)"
|
|
134
|
-
pnpm dlx "@bleedingdev/modern-js-ultramodern-create@$COHORT" ultramodern \
|
|
135
|
-
migrate-strict-effect --version "$COHORT"
|
|
136
|
-
pnpm api:check
|
|
137
|
-
pnpm contract:check
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
The command updates `.modernjs/ultramodern.json`, root
|
|
141
|
-
`modernjs.packageSource`, generated Modern package aliases, framework-owned
|
|
142
|
-
toolchain pins, direct topology API metadata, strict Effect pnpm
|
|
143
|
-
overrides/trust policy, and the pnpm lockfile. Remaining failures are source
|
|
144
|
-
migration work: move code to `shared/api.ts`, `api/index.ts`, and
|
|
145
|
-
`src/api/*-client.ts`, then delete `api/effect`, `api/lambda`, `shared/effect`,
|
|
146
|
-
and `src/effect`.
|
|
147
|
-
|
|
148
67
|
Generated strict Effect workspaces pin the compatible Effect cohort with pnpm
|
|
149
|
-
overrides: `effect@4.0.0-rc.
|
|
150
|
-
and `@effect/vitest@4.0.0-rc.
|
|
68
|
+
overrides: `effect@4.0.0-rc.117`, `@effect/opentelemetry@4.0.0-rc.117`,
|
|
69
|
+
and `@effect/vitest@4.0.0-rc.117`. Do not add app-local direct Effect
|
|
151
70
|
versions that disagree with those overrides. The strict 24-hour release-age
|
|
152
71
|
gate applies to installed packages; this cohort carries no Effect age
|
|
153
72
|
exemption, and override-only `@effect/vitest` is not an installed approval
|
|
154
|
-
target.
|
|
155
|
-
and `@effect/opentelemetry` cover their trusted-publisher to provenance
|
|
156
|
-
metadata transition; they are not release-age approvals.
|
|
73
|
+
target.
|
|
157
74
|
|
|
158
75
|
### Current generated dependency baseline
|
|
159
76
|
|
|
160
77
|
| Surface | Pin |
|
|
161
78
|
| --- | --- |
|
|
162
|
-
| Effect runtime and test cohort | `effect`, `@effect/opentelemetry`, and `@effect/vitest`: `4.0.0-rc.
|
|
163
|
-
| Effect compiler | `@effect/tsgo@0.
|
|
164
|
-
| Oxc and Ultracite | `oxlint@1.
|
|
165
|
-
| TanStack Router | `@tanstack/react-router@1.170.
|
|
166
|
-
| Module Federation | `bridge-react`, `manifest`, `modern-js-v3`, and `rspack`: `2.9.
|
|
79
|
+
| Effect runtime and test cohort | `effect`, `@effect/opentelemetry`, and `@effect/vitest`: `4.0.0-rc.117` |
|
|
80
|
+
| Effect compiler | `@effect/tsgo@0.45.0` |
|
|
81
|
+
| Oxc and Ultracite | `oxlint@1.85.0`, `oxfmt@0.70.0`, `ultracite@7.12.0` |
|
|
82
|
+
| TanStack Router | `@tanstack/react-router@1.170.39`, `@tanstack/router-core@1.171.32`, `@tanstack/history@1.162.4` |
|
|
83
|
+
| Module Federation | `bridge-react`, `manifest`, `modern-js-v3`, and `rspack`: `2.9.2`; `@module-federation/node@2.7.52` |
|
|
167
84
|
| Tailwind CSS | `tailwindcss@4.3.3` |
|
|
168
|
-
| Node and package tooling | Node `26.7.0`, `@types/node@^26.
|
|
85
|
+
| Node and package tooling | Node `26.7.0`, `@types/node@^26.6.2`, pnpm `11.27.1` |
|
|
169
86
|
|
|
170
87
|
Generated workspaces default to Module Federation bridge-react's router-free
|
|
171
88
|
base entry (`bridge.enableBridgeRouter: false`), with TanStack Router as the
|
|
@@ -213,7 +130,7 @@ transport surfaces when needed. They do not make raw request handlers valid
|
|
|
213
130
|
inside generated HTTP API modules.
|
|
214
131
|
|
|
215
132
|
Strict API tests should exercise the `HttpApi` contract. Use
|
|
216
|
-
`createEffectBffTestHandler` from `@modern-js/
|
|
133
|
+
`createEffectBffTestHandler` from `@modern-js/bff-effect/effect-edge` for
|
|
217
134
|
edge-compatible proof tests; if you manually compose a web handler, provide
|
|
218
135
|
`HttpServer.layerServices` beside your API group layer before calling
|
|
219
136
|
`HttpRouter.toWebHandler`.
|
|
@@ -191,6 +191,7 @@ Adds additional logic when the server resets.
|
|
|
191
191
|
- `'file-change'`: File change event
|
|
192
192
|
- `event.payload`: When `type` is `'file-change'`, contains an array of file change information.
|
|
193
193
|
- **Execution Phase:** When files change or repack is needed.
|
|
194
|
+
- **Repack Order:** On `'repack'` the dev server awaits the handlers in registration order before it purges the previous server bundle from the require cache. A rejecting handler skips the handlers after it. New requests wait until the handlers settle; requests already running keep the modules they loaded. A repack handler must not await a request to the dev server, because that request waits for the handler.
|
|
194
195
|
- **Example:**
|
|
195
196
|
|
|
196
197
|
```typescript
|
|
@@ -21,7 +21,7 @@ export const get = async () => {
|
|
|
21
21
|
在 BFF 函数中获取 Cookie 时,需要通过 `useHonoContext` 获取请求上下文,然后使用 `c.req.header('cookie')` 获取 Cookie 字符串并手动解析:
|
|
22
22
|
|
|
23
23
|
```ts title="api/lambda/cookies.ts"
|
|
24
|
-
import { Api, Get } from '@modern-js/plugin-bff/
|
|
24
|
+
import { Api, Get } from '@modern-js/plugin-bff/server';
|
|
25
25
|
import { useHonoContext } from '@modern-js/server-runtime';
|
|
26
26
|
|
|
27
27
|
// 解析 Cookie 字符串的辅助函数
|
|
@@ -64,7 +64,7 @@ export const getCookies = Api(Get('/cookies'), async () => {
|
|
|
64
64
|
使用 Hono 作为运行时框架时,可以通过 [Api 函数](/guides/advanced-features/bff/operators.html) 定义接口:
|
|
65
65
|
|
|
66
66
|
```ts title="api/lambda/user.ts"
|
|
67
|
-
import { Api, Get, Query } from '@modern-js/plugin-bff/
|
|
67
|
+
import { Api, Get, Query } from '@modern-js/plugin-bff/server';
|
|
68
68
|
import { z } from 'zod';
|
|
69
69
|
|
|
70
70
|
const QuerySchema = z.object({
|
|
@@ -93,7 +93,7 @@ export const getUser = Api(
|
|
|
93
93
|
Hono 支持丰富的中间件生态,可以在 BFF 函数中使用中间件:
|
|
94
94
|
|
|
95
95
|
```ts title="api/lambda/user.ts"
|
|
96
|
-
import { Api, Get, Middleware } from '@modern-js/plugin-bff/
|
|
96
|
+
import { Api, Get, Middleware } from '@modern-js/plugin-bff/server';
|
|
97
97
|
|
|
98
98
|
export const getUser = Api(
|
|
99
99
|
Get('/user'),
|
|
@@ -46,20 +46,29 @@ import EnableBFFCaution from "@site-docs/components/enable-bff-caution";
|
|
|
46
46
|
仅当 `bff.runtimeFramework` 设置为 `'effect'` 时,`bff.effect` 才会生效。
|
|
47
47
|
|
|
48
48
|
:::caution 需要自行安装 Effect peer 依赖
|
|
49
|
-
`effect` 与 `@effect/opentelemetry` 是 `@modern-js/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
或引入 `@modern-js/plugin-bff/effect`、`/effect-server`、`/effect-edge`、
|
|
49
|
+
`effect` 与 `@effect/opentelemetry` 是 `@modern-js/bff-effect` 的**可选精确 peer
|
|
50
|
+
依赖**。应用必须安装这些依赖,让 API 模块与框架使用同一个 Effect 实例。在设置
|
|
51
|
+
`runtimeFramework: 'effect'` 或引入 `@modern-js/bff-effect/effect`、`/effect-edge`、
|
|
53
52
|
`/effect-client` 之前,请安装精确版本的依赖组:
|
|
54
53
|
|
|
55
54
|
```bash
|
|
56
|
-
pnpm add effect@4.0.0-rc.
|
|
55
|
+
pnpm add effect@4.0.0-rc.117 @effect/opentelemetry@4.0.0-rc.117
|
|
57
56
|
```
|
|
58
57
|
|
|
59
58
|
采用精确版本是因为 UltraModern 以锁步依赖组的方式发布 Effect。只使用
|
|
60
|
-
`runtimeFramework: 'hono'` 或
|
|
59
|
+
原生 `runtimeFramework: 'hono'` 或 `@modern-js/bff-effect/data-platform` 的应用无需安装这两个包。
|
|
61
60
|
:::
|
|
62
61
|
|
|
62
|
+
使用 `@modern-js/plugin-bff-build-extensions` 导出的 `bffPlugin` 注册 Effect
|
|
63
|
+
运行时,它会组合原生 BFF 插件。将同一框架版本组中的 `@modern-js/bff-effect` 和
|
|
64
|
+
`@modern-js/plugin-bff-extensions` 安装为应用的生产依赖,保证移除开发依赖后仍能加载
|
|
65
|
+
运行时适配器。构建插件可以作为开发依赖。参见[运行时配置示例](/guides/advanced-features/bff/frameworks)。
|
|
66
|
+
|
|
67
|
+
原生 `@modern-js/plugin-bff/server` 导出 Hono API。Node Effect API 从
|
|
68
|
+
`@modern-js/bff-effect/effect` 导入 `defineEffectBff` 等框架 helper,命名空间从对应的
|
|
69
|
+
`effect/*` 模块导入。Worker handler 和 Worker 请求上下文使用
|
|
70
|
+
`@modern-js/bff-effect/effect-edge`。
|
|
71
|
+
|
|
63
72
|
生成的 UltraModern workspace 只把这个运行时作为生成 HTTP API 路径。API 契约固定在
|
|
64
73
|
`shared/api.ts`,服务端运行时固定在 `api/index.ts`,客户端固定在
|
|
65
74
|
`src/api/*-client.ts`。生成检查会拒绝 `api/effect`、`api/lambda`、
|
|
@@ -169,7 +178,6 @@ export default defineConfig({
|
|
|
169
178
|
endpoint: '/_data/batch',
|
|
170
179
|
maxBatchSize: 16,
|
|
171
180
|
maxBatchBytes: 64 * 1024,
|
|
172
|
-
flushIntervalMs: 8,
|
|
173
181
|
maxConcurrency: 4,
|
|
174
182
|
requestTimeoutMs: 10000,
|
|
175
183
|
allowedMethods: ['GET'],
|
|
@@ -203,9 +211,9 @@ export default defineConfig({
|
|
|
203
211
|
});
|
|
204
212
|
```
|
|
205
213
|
|
|
206
|
-
`
|
|
214
|
+
`maxConcurrency` 与 `requestTimeoutMs` 控制服务端批处理网关。原生 `HttpApiClient` 发送独立请求,不会自动批处理。
|
|
207
215
|
|
|
208
|
-
|
|
216
|
+
导入共享 `HttpApi` 契约并传给 `HttpApiClient.make` 或 `makeEffectHttpApiClient`,即可获得完全类型推导的客户端。`defineEffectBff` 只提供服务端 handler,不包含客户端。
|
|
209
217
|
|
|
210
218
|
## Effect 版本组
|
|
211
219
|
|
|
@@ -213,23 +221,16 @@ UltraModern 生成的 workspace 会通过 `pnpm-workspace.yaml` overrides 锁定
|
|
|
213
221
|
Effect 版本组。当前 UltraModern 版本组使用:
|
|
214
222
|
|
|
215
223
|
```yaml
|
|
216
|
-
trustPolicyExclude:
|
|
217
|
-
- 'effect@4.0.0-rc.112'
|
|
218
|
-
- '@effect/opentelemetry@4.0.0-rc.112'
|
|
219
|
-
|
|
220
224
|
overrides:
|
|
221
|
-
'@effect/opentelemetry': 4.0.0-rc.
|
|
222
|
-
'@effect/vitest': 4.0.0-rc.
|
|
223
|
-
effect: 4.0.0-rc.
|
|
225
|
+
'@effect/opentelemetry': 4.0.0-rc.117
|
|
226
|
+
'@effect/vitest': 4.0.0-rc.117
|
|
227
|
+
effect: 4.0.0-rc.117
|
|
224
228
|
```
|
|
225
229
|
|
|
226
230
|
不要在应用包里添加不同版本的直接 `effect` 依赖。Effect 预发布版本不一致时,Layer 或 HTTP
|
|
227
231
|
middleware 构建可能因为运行时 service 来自不同包实例而失败。严格的 24 小时发布年龄
|
|
228
232
|
门禁适用于实际安装的包;当前版本组没有 Effect 年龄豁免,且仅用于 override 的
|
|
229
233
|
`@effect/vitest` 不是已安装的审批目标。
|
|
230
|
-
`trustPolicyExclude` 是另一项独立策略:其中精确的 `effect` 与
|
|
231
|
-
`@effect/opentelemetry` 例外用于处理 trusted-publisher metadata 向 provenance
|
|
232
|
-
attestation 的迁移,并不等同于 release-age 审批。
|
|
233
234
|
|
|
234
235
|
## 契约测试
|
|
235
236
|
|
|
@@ -237,7 +238,7 @@ attestation 的迁移,并不等同于 release-age 审批。
|
|
|
237
238
|
Edge 兼容测试可以使用框架 helper:
|
|
238
239
|
|
|
239
240
|
```ts
|
|
240
|
-
import { createEffectBffTestHandler } from '@modern-js/
|
|
241
|
+
import { createEffectBffTestHandler } from '@modern-js/bff-effect/effect-edge';
|
|
241
242
|
import apiModule from '../api/index';
|
|
242
243
|
|
|
243
244
|
const testApi = await createEffectBffTestHandler({
|
|
@@ -252,12 +253,9 @@ const response = await testApi.handler(new Request('http://localhost/api/ping'))
|
|
|
252
253
|
`HttpServer.layerServices`:
|
|
253
254
|
|
|
254
255
|
```ts
|
|
255
|
-
import
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
HttpServer,
|
|
259
|
-
Layer,
|
|
260
|
-
} from '@modern-js/plugin-bff/effect-server';
|
|
256
|
+
import * as Layer from 'effect/Layer';
|
|
257
|
+
import { HttpRouter, HttpServer } from 'effect/unstable/http';
|
|
258
|
+
import { HttpApiBuilder } from 'effect/unstable/httpapi';
|
|
261
259
|
|
|
262
260
|
const handler = HttpRouter.toWebHandler(
|
|
263
261
|
HttpApiBuilder.layer(api).pipe(
|
|
@@ -275,13 +273,10 @@ const handler = HttpRouter.toWebHandler(
|
|
|
275
273
|
Effect v4 beta 版本组中,`HttpRouter.middleware(...)` 直接返回 `Layer`:
|
|
276
274
|
|
|
277
275
|
```ts
|
|
278
|
-
import
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
HttpRouter,
|
|
283
|
-
Layer,
|
|
284
|
-
} from '@modern-js/plugin-bff/effect-server';
|
|
276
|
+
import * as Effect from 'effect/Effect';
|
|
277
|
+
import * as Layer from 'effect/Layer';
|
|
278
|
+
import { HttpMiddleware, HttpRouter } from 'effect/unstable/http';
|
|
279
|
+
import { HttpApiBuilder } from 'effect/unstable/httpapi';
|
|
285
280
|
|
|
286
281
|
const corsLayer = HttpRouter.middleware(
|
|
287
282
|
Effect.succeed(
|
|
@@ -11,6 +11,8 @@ title: reactCompiler
|
|
|
11
11
|
|
|
12
12
|
Modern.js 基于 Rspack `builtin:swc-loader` 内置的 Rust 版 React Compiler 实现该能力(等价于设置 SWC 的 `jsc.transform.reactCompiler`),复用 Rspack 内置的 SWC 转换链,无需额外引入 Babel。
|
|
13
13
|
|
|
14
|
+
该编译仅作用于浏览器环境(`output.target: 'web'`)。服务端环境(`node`、`web-worker` SSR Worker 以及 BFF 代码)每次请求只渲染一次,无法从自动记忆化中获益,因此不会被编译。
|
|
15
|
+
|
|
14
16
|
:::tip
|
|
15
17
|
该配置默认关闭,任何 React 版本下都需要显式开启,包括 React 19。
|
|
16
18
|
:::
|
|
@@ -17,7 +17,7 @@ Modern.js 的 Effect BFF 已支持基于请求信封(request envelope)的数
|
|
|
17
17
|
|
|
18
18
|
## 运行时契约工具
|
|
19
19
|
|
|
20
|
-
可通过 `@modern-js/
|
|
20
|
+
可通过 `@modern-js/bff-effect/data-platform` 使用以下能力:
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
23
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
validateHydrationEnvelope,
|
|
31
31
|
createInvalidationEvent,
|
|
32
32
|
shouldApplyInvalidation,
|
|
33
|
-
} from '@modern-js/
|
|
33
|
+
} from '@modern-js/bff-effect/data-platform';
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Effect 运行时校验
|
|
@@ -5,10 +5,10 @@ title: 运行时框架
|
|
|
5
5
|
|
|
6
6
|
# 运行时框架
|
|
7
7
|
|
|
8
|
-
Modern.js
|
|
8
|
+
Modern.js 与 UltraModern BFF 扩展提供两种运行时框架:
|
|
9
9
|
|
|
10
|
-
- `
|
|
11
|
-
- `
|
|
10
|
+
- `hono` 是原生插件的默认运行时,使用 `api/lambda/**` 的文件约定处理函数。
|
|
11
|
+
- `effect` 是 UltraModern 扩展的默认运行时,使用 `api/index` 的 [Effect HttpApi](https://effect.website/) 运行时。
|
|
12
12
|
|
|
13
13
|
`effect` 与 `hono` 为严格模式,两者之间不会自动回退。
|
|
14
14
|
|
|
@@ -22,12 +22,16 @@ workspace 中加入 Hono/file-convention handler、原始 request parsing 或手
|
|
|
22
22
|
|
|
23
23
|
## 切换到 Effect 运行时
|
|
24
24
|
|
|
25
|
+
使用下面的 fork 构建插件,它会包含原生 BFF 插件并注册 Effect 适配器。应用需要将
|
|
26
|
+
同一框架版本组中的 `@modern-js/bff-effect` 和 `@modern-js/plugin-bff-extensions`
|
|
27
|
+
安装为生产依赖。Effect 的精确 peer 版本见 [`bff.effect`](/configure/app/bff/effect)。
|
|
28
|
+
|
|
25
29
|
```ts title="modern.config.ts"
|
|
26
|
-
import { bffPlugin } from '@modern-js/plugin-bff';
|
|
27
|
-
import { defineConfig } from '@modern-js/app-tools';
|
|
30
|
+
import { bffPlugin } from '@modern-js/plugin-bff-build-extensions';
|
|
31
|
+
import { appTools, defineConfig } from '@modern-js/app-tools';
|
|
28
32
|
|
|
29
33
|
export default defineConfig({
|
|
30
|
-
plugins: [bffPlugin()],
|
|
34
|
+
plugins: [appTools(), bffPlugin()],
|
|
31
35
|
bff: {
|
|
32
36
|
runtimeFramework: 'effect',
|
|
33
37
|
effect: {
|
|
@@ -49,7 +53,7 @@ import {
|
|
|
49
53
|
HttpApiEndpoint,
|
|
50
54
|
HttpApiGroup,
|
|
51
55
|
Schema,
|
|
52
|
-
} from '@modern-js/
|
|
56
|
+
} from '@modern-js/bff-effect/effect-client';
|
|
53
57
|
|
|
54
58
|
export const bffApi = HttpApi.make('MyApi').add(
|
|
55
59
|
HttpApiGroup.make('hello').add(
|
|
@@ -63,14 +67,12 @@ export const bffApi = HttpApi.make('MyApi').add(
|
|
|
63
67
|
然后在 `api/index.ts` 中实现 Effect BFF 入口:
|
|
64
68
|
|
|
65
69
|
```ts title="api/index.ts"
|
|
66
|
-
import {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
ServiceMap,
|
|
73
|
-
} from '@modern-js/plugin-bff/effect-server';
|
|
70
|
+
import { defineEffectBff } from '@modern-js/bff-effect/effect';
|
|
71
|
+
import * as Context from 'effect/Context';
|
|
72
|
+
import * as Effect from 'effect/Effect';
|
|
73
|
+
import * as Layer from 'effect/Layer';
|
|
74
|
+
import * as Schema from 'effect/Schema';
|
|
75
|
+
import { HttpApiBuilder } from 'effect/unstable/httpapi';
|
|
74
76
|
import { bffApi } from '../shared/api';
|
|
75
77
|
|
|
76
78
|
class GreetingUnavailableError extends Schema.TaggedError<GreetingUnavailableError>()(
|
|
@@ -80,7 +82,7 @@ class GreetingUnavailableError extends Schema.TaggedError<GreetingUnavailableErr
|
|
|
80
82
|
},
|
|
81
83
|
) {}
|
|
82
84
|
|
|
83
|
-
class GreetingService extends
|
|
85
|
+
class GreetingService extends Context.Service<GreetingService>()('GreetingService', {
|
|
84
86
|
make: Effect.succeed({
|
|
85
87
|
hello: Effect.fn('GreetingService.hello')(function* () {
|
|
86
88
|
if (Date.now() < 0) {
|
|
@@ -115,15 +117,24 @@ const layer = HttpApiBuilder.layer(bffApi).pipe(
|
|
|
115
117
|
export default defineEffectBff({ api: bffApi, layer });
|
|
116
118
|
```
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
从共享契约创建原生、完全类型推导的客户端:
|
|
119
121
|
|
|
120
122
|
```ts title="src/routes/page.tsx"
|
|
121
|
-
import
|
|
123
|
+
import { Effect, makeEffectHttpApiClient } from '@modern-js/bff-effect/effect-client';
|
|
124
|
+
import { bffApi } from '../../shared/api';
|
|
122
125
|
|
|
123
|
-
const response = await
|
|
126
|
+
const response = await Effect.runPromise(
|
|
127
|
+
makeEffectHttpApiClient(bffApi, { baseUrl: '/api' }).pipe(
|
|
128
|
+
Effect.flatMap(client => client.hello.ping({})),
|
|
129
|
+
),
|
|
130
|
+
);
|
|
124
131
|
```
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
请求、响应和声明的错误类型均从 `bffApi` 推导,无需生成客户端代码或导入服务端入口。
|
|
134
|
+
|
|
135
|
+
原生 Hono 应用从 `@modern-js/plugin-bff/server` 导入操作符。生成的 UltraModern
|
|
136
|
+
应用继续使用严格 Effect API。Worker handler 及其请求上下文使用
|
|
137
|
+
`@modern-js/bff-effect/effect-edge`;Node handler 使用上面示例中的 Effect 入口。
|
|
127
138
|
|
|
128
139
|
import Hono from '@site-docs/components/hono';
|
|
129
140
|
|
|
@@ -188,7 +188,7 @@ Dynamic Path 之后的参数是包含 querystring、request body 的对象 `Requ
|
|
|
188
188
|
在不存在动态路由的普通函数中,可以从第一个入参中获取传入的 `data` 和 `query`,例如:
|
|
189
189
|
|
|
190
190
|
```ts title="api/lambda/hello.ts"
|
|
191
|
-
import type { RequestOption } from '@modern-js/plugin-bff/
|
|
191
|
+
import type { RequestOption } from '@modern-js/plugin-bff/server';
|
|
192
192
|
|
|
193
193
|
export async function post({
|
|
194
194
|
query,
|
|
@@ -201,7 +201,7 @@ export async function post({
|
|
|
201
201
|
这里你也可以使用自定义类型:
|
|
202
202
|
|
|
203
203
|
```ts title="api/lambda/hello.ts"
|
|
204
|
-
import type { RequestOption } from '@modern-js/plugin-bff/
|
|
204
|
+
import type { RequestOption } from '@modern-js/plugin-bff/server';
|
|
205
205
|
|
|
206
206
|
type IQuery = {
|
|
207
207
|
// some types
|
|
@@ -36,7 +36,7 @@ import BFFOperatorCode from '@site-docs/components/bff-operator-code';
|
|
|
36
36
|
<BFFOperatorCode>
|
|
37
37
|
|
|
38
38
|
```typescript title="api/lambda/user.ts"
|
|
39
|
-
import { Api, Post, Query, Data } from '@modern-js/plugin-bff/
|
|
39
|
+
import { Api, Post, Query, Data } from '@modern-js/plugin-bff/server';
|
|
40
40
|
import { z } from 'zod';
|
|
41
41
|
|
|
42
42
|
const UserSchema = z.object({
|
|
@@ -89,7 +89,7 @@ addUser({
|
|
|
89
89
|
<BFFOperatorCode>
|
|
90
90
|
|
|
91
91
|
```typescript title="api/lambda/user.ts"
|
|
92
|
-
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/
|
|
92
|
+
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
|
|
93
93
|
|
|
94
94
|
// 指定接口路由,Modern.js 默认设置 `bff.prefix` 为 `/api`,
|
|
95
95
|
// 因此该接口路由为 `/api/user`,Http Method 为 GET。
|
|
@@ -107,7 +107,7 @@ export const getHello = Api(
|
|
|
107
107
|
<BFFOperatorCode>
|
|
108
108
|
|
|
109
109
|
```typescript title="api/lambda/user.ts"
|
|
110
|
-
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/
|
|
110
|
+
import { Api, Get, Query, Data } from '@modern-js/plugin-bff/server';
|
|
111
111
|
|
|
112
112
|
// 未指定接口路由,根据文件约定和函数名,该接口为 api/user,Http Method 为 get。
|
|
113
113
|
export const get = Api(Query(UserSchema), async ({ query }) => query);
|
|
@@ -144,7 +144,7 @@ Modern.js 推荐基于文件约定去定义接口,保持项目中路由清晰
|
|
|
144
144
|
|
|
145
145
|
```typescript title="api/lambda/user.ts"
|
|
146
146
|
// 服务端代码
|
|
147
|
-
import { Api, Query } from '@modern-js/plugin-bff/
|
|
147
|
+
import { Api, Query } from '@modern-js/plugin-bff/server';
|
|
148
148
|
import { z } from 'zod';
|
|
149
149
|
|
|
150
150
|
const UserSchema = z.object({
|
|
@@ -176,7 +176,7 @@ URL query 参数默认是字符串类型,如果需要数字类型,需要使
|
|
|
176
176
|
<BFFOperatorCode>
|
|
177
177
|
|
|
178
178
|
```typescript title="api/lambda/user.ts"
|
|
179
|
-
import { Api, Get, Query } from '@modern-js/plugin-bff/
|
|
179
|
+
import { Api, Get, Query } from '@modern-js/plugin-bff/server';
|
|
180
180
|
import { z } from 'zod';
|
|
181
181
|
|
|
182
182
|
const QuerySchema = z.object({
|
|
@@ -216,7 +216,7 @@ URL query 参数都是字符串类型,如果需要数字类型,需要使用
|
|
|
216
216
|
<BFFOperatorCode>
|
|
217
217
|
|
|
218
218
|
```typescript title="api/lambda/user.ts"
|
|
219
|
-
import { Api, Data } from '@modern-js/plugin-bff/
|
|
219
|
+
import { Api, Data } from '@modern-js/plugin-bff/server';
|
|
220
220
|
import { z } from 'zod';
|
|
221
221
|
|
|
222
222
|
const DataSchema = z.object({
|
|
@@ -249,7 +249,7 @@ post({
|
|
|
249
249
|
<BFFOperatorCode>
|
|
250
250
|
|
|
251
251
|
```typescript
|
|
252
|
-
import { Api, Get, Params } from '@modern-js/plugin-bff/
|
|
252
|
+
import { Api, Get, Params } from '@modern-js/plugin-bff/server';
|
|
253
253
|
import { z } from 'zod';
|
|
254
254
|
|
|
255
255
|
const UserSchema = z.object({
|
|
@@ -274,7 +274,7 @@ export const queryUser = Api(
|
|
|
274
274
|
<BFFOperatorCode>
|
|
275
275
|
|
|
276
276
|
```typescript
|
|
277
|
-
import { Api, Headers } from '@modern-js/plugin-bff/
|
|
277
|
+
import { Api, Headers } from '@modern-js/plugin-bff/server';
|
|
278
278
|
import { z } from 'zod';
|
|
279
279
|
|
|
280
280
|
const headerSchema = z.object({
|
|
@@ -336,7 +336,7 @@ try {
|
|
|
336
336
|
<BFFOperatorCode>
|
|
337
337
|
|
|
338
338
|
```typescript
|
|
339
|
-
import { Api, Query, Middleware } from '@modern-js/plugin-bff/
|
|
339
|
+
import { Api, Query, Middleware } from '@modern-js/plugin-bff/server';
|
|
340
340
|
import { z } from 'zod';
|
|
341
341
|
|
|
342
342
|
const UserSchema = z.object({
|
|
@@ -376,7 +376,7 @@ export const get = Api(
|
|
|
376
376
|
<BFFOperatorCode>
|
|
377
377
|
|
|
378
378
|
```typescript
|
|
379
|
-
import { Api, Query, Pipe } from '@modern-js/plugin-bff/
|
|
379
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
380
380
|
import { z } from 'zod';
|
|
381
381
|
|
|
382
382
|
const UserSchema = z.object({
|
|
@@ -408,7 +408,7 @@ export const get = Api(
|
|
|
408
408
|
<BFFOperatorCode>
|
|
409
409
|
|
|
410
410
|
```typescript
|
|
411
|
-
import { Api, Query, Pipe } from '@modern-js/plugin-bff/
|
|
411
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
412
412
|
import { z } from 'zod';
|
|
413
413
|
|
|
414
414
|
const UserSchema = z.object({
|
|
@@ -443,7 +443,7 @@ export const get = Api(
|
|
|
443
443
|
<BFFOperatorCode>
|
|
444
444
|
|
|
445
445
|
```typescript
|
|
446
|
-
import { Api, Query, Pipe } from '@modern-js/plugin-bff/
|
|
446
|
+
import { Api, Query, Pipe } from '@modern-js/plugin-bff/server';
|
|
447
447
|
import { z } from 'zod';
|
|
448
448
|
|
|
449
449
|
const UserSchema = z.object({
|
|
@@ -487,7 +487,7 @@ export const get = Api(
|
|
|
487
487
|
<BFFOperatorCode>
|
|
488
488
|
|
|
489
489
|
```typescript
|
|
490
|
-
import { Api, Query, Data, HttpCode } from '@modern-js/plugin-bff/
|
|
490
|
+
import { Api, Query, Data, HttpCode } from '@modern-js/plugin-bff/server';
|
|
491
491
|
import { z } from 'zod';
|
|
492
492
|
|
|
493
493
|
const UserSchema = z.object({
|
|
@@ -523,7 +523,7 @@ export const post = Api(
|
|
|
523
523
|
<BFFOperatorCode>
|
|
524
524
|
|
|
525
525
|
```typescript
|
|
526
|
-
import { Api, Get, SetHeaders } from '@modern-js/plugin-bff/
|
|
526
|
+
import { Api, Get, SetHeaders } from '@modern-js/plugin-bff/server';
|
|
527
527
|
|
|
528
528
|
export default Api(
|
|
529
529
|
Get('/hello'),
|
|
@@ -543,7 +543,7 @@ export default Api(
|
|
|
543
543
|
<BFFOperatorCode>
|
|
544
544
|
|
|
545
545
|
```typescript
|
|
546
|
-
import { Api, Get, Redirect } from '@modern-js/plugin-bff/
|
|
546
|
+
import { Api, Get, Redirect } from '@modern-js/plugin-bff/server';
|
|
547
547
|
|
|
548
548
|
export default Api(
|
|
549
549
|
Get('/hello'),
|
|
@@ -561,7 +561,7 @@ export default Api(
|
|
|
561
561
|
<BFFOperatorCode>
|
|
562
562
|
|
|
563
563
|
```typescript title="api/lambda/user.ts"
|
|
564
|
-
import { Api, Get, Query } from '@modern-js/plugin-bff/
|
|
564
|
+
import { Api, Get, Query } from '@modern-js/plugin-bff/server';
|
|
565
565
|
import { useHonoContext } from '@modern-js/server-runtime';
|
|
566
566
|
import { z } from 'zod';
|
|
567
567
|
|
|
@@ -610,7 +610,7 @@ export const queryUser = Api(
|
|
|
610
610
|
<BFFOperatorCode>
|
|
611
611
|
|
|
612
612
|
```typescript
|
|
613
|
-
import { Api, SetHeaders } from '@modern-js/plugin-bff/
|
|
613
|
+
import { Api, SetHeaders } from '@modern-js/plugin-bff/server';
|
|
614
614
|
|
|
615
615
|
export const get = Api(
|
|
616
616
|
// 缓存使用一体化调用或者 fetch 进行请求才会生效
|