@bleedingdev/modern-js-main-doc 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121
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 +41 -51
- package/docs/en/community/blog/2022-0708-updates.md +1 -1
- package/docs/en/community/blog/2022-0910-updates.md +2 -2
- package/docs/en/community/contributing-guide.mdx +2 -3
- package/docs/en/community/releases.mdx +1 -5
- package/docs/en/components/init-app.mdx +40 -66
- package/docs/en/components/init-rspack-app.mdx +1 -1
- package/docs/en/components/prerequisites.mdx +1 -2
- package/docs/en/components/serve-command.mdx +1 -1
- package/docs/en/configure/app/bff/effect.mdx +27 -3
- package/docs/en/configure/app/bff/runtime-framework.mdx +1 -1
- package/docs/en/configure/app/performance/rsdoctor.mdx +7 -4
- package/docs/en/configure/app/tools/ts-checker.mdx +30 -2
- package/docs/en/guides/advanced-features/bff/data-platform.mdx +3 -1
- package/docs/en/guides/advanced-features/bff/frameworks.mdx +3 -1
- package/docs/en/guides/advanced-features/international/api.mdx +4 -1
- package/docs/en/guides/advanced-features/international/configuration.mdx +1 -0
- package/docs/en/guides/advanced-features/international/locale-detection.mdx +1 -1
- package/docs/en/guides/advanced-features/international/routing.mdx +45 -2
- package/docs/en/guides/basic-features/debug/rsdoctor.mdx +2 -3
- package/docs/en/guides/basic-features/deploy.mdx +1 -1
- package/docs/en/guides/basic-features/render/_meta.json +1 -10
- package/docs/en/guides/basic-features/render/overview.mdx +0 -1
- package/docs/en/guides/basic-features/render/rsc.mdx +0 -1
- package/docs/en/guides/basic-features/routes/routes.mdx +24 -9
- package/docs/en/guides/basic-features/testing/_meta.json +1 -1
- package/docs/en/guides/concept/server.mdx +2 -2
- package/docs/en/guides/get-started/quick-start.mdx +1 -1
- package/docs/en/guides/get-started/tech-stack.mdx +10 -4
- package/docs/en/guides/get-started/ultramodern.mdx +94 -20
- package/docs/en/guides/upgrade/config.mdx +1 -2
- package/docs/en/guides/upgrade/other.mdx +4 -4
- package/docs/zh/apis/app/commands.mdx +38 -48
- package/docs/zh/community/blog/2022-0708-updates.md +1 -1
- package/docs/zh/community/blog/2022-0910-updates.md +2 -2
- package/docs/zh/community/contributing-guide.mdx +2 -3
- package/docs/zh/community/releases.mdx +1 -5
- package/docs/zh/components/init-app.mdx +33 -62
- package/docs/zh/components/init-rspack-app.mdx +1 -1
- package/docs/zh/components/prerequisites.mdx +1 -2
- package/docs/zh/components/serve-command.mdx +1 -1
- package/docs/zh/configure/app/bff/effect.mdx +26 -2
- package/docs/zh/configure/app/bff/runtime-framework.mdx +1 -1
- package/docs/zh/configure/app/performance/rsdoctor.mdx +7 -4
- package/docs/zh/configure/app/tools/ts-checker.mdx +30 -2
- package/docs/zh/configure/app/usage.mdx +1 -1
- package/docs/zh/guides/advanced-features/bff/data-platform.mdx +3 -1
- package/docs/zh/guides/advanced-features/bff/frameworks.mdx +3 -1
- package/docs/zh/guides/advanced-features/international/api.mdx +4 -1
- package/docs/zh/guides/advanced-features/international/configuration.mdx +1 -0
- package/docs/zh/guides/advanced-features/international/locale-detection.mdx +1 -1
- package/docs/zh/guides/advanced-features/international/routing.mdx +45 -2
- package/docs/zh/guides/basic-features/debug/rsdoctor.mdx +2 -3
- package/docs/zh/guides/basic-features/deploy.mdx +2 -2
- package/docs/zh/guides/basic-features/render/_meta.json +1 -10
- package/docs/zh/guides/basic-features/render/overview.mdx +0 -1
- package/docs/zh/guides/basic-features/render/rsc.mdx +0 -1
- package/docs/zh/guides/basic-features/routes/routes.mdx +24 -9
- package/docs/zh/guides/basic-features/testing/_meta.json +1 -1
- package/docs/zh/guides/concept/server.mdx +2 -2
- package/docs/zh/guides/get-started/quick-start.mdx +1 -1
- package/docs/zh/guides/get-started/tech-stack.mdx +10 -4
- package/docs/zh/guides/get-started/ultramodern.mdx +88 -4
- package/docs/zh/guides/upgrade/config.mdx +1 -2
- package/docs/zh/guides/upgrade/other.md +4 -5
- package/package.json +15 -14
- package/rspress.config.ts +17 -5
- package/src/components/Footer/index.tsx +3 -3
- package/src/components/SecondaryTitle/index.module.css +7 -2
- package/src/components/ShowcaseList/useShowcases.ts +23 -65
- package/src/i18n/enUS.ts +0 -9
- package/src/i18n/zhCN.ts +0 -9
- package/src/sandbox/csr-auth/src/routes/page-tsx.txt +1 -1
- package/static/img/logo.svg +7 -0
- package/static/img/social-card.svg +12 -0
- package/builder-doc/docs/en/config/performance/rsdoctor.md +0 -37
- package/builder-doc/docs/zh/config/performance/rsdoctor.md +0 -37
- package/docs/en/guides/basic-features/render/tanstack-rsc.mdx +0 -226
- package/docs/en/guides/basic-features/testing/cypress.mdx +0 -95
- package/docs/en/guides/basic-features/testing/jest.mdx +0 -148
- package/docs/en/guides/basic-features/testing/vitest.mdx +0 -100
- package/docs/zh/guides/basic-features/render/tanstack-rsc.mdx +0 -226
- package/docs/zh/guides/basic-features/testing/cypress.mdx +0 -95
- package/docs/zh/guides/basic-features/testing/jest.mdx +0 -148
- package/docs/zh/guides/basic-features/testing/vitest.mdx +0 -100
- package/main-doc/docs/en/guides/get-started/ultramodern.mdx +0 -320
- package/main-doc/docs/zh/guides/get-started/ultramodern.mdx +0 -304
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: TanStack Router RSC
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# TanStack Router RSC
|
|
6
|
-
|
|
7
|
-
Modern.js 支持在 TanStack Router 项目中使用 React Server Components(RSC)。这套集成会将 RSC 输出作为路由数据处理:Loader 返回序列化后的 Flight stream,由 TanStack Router 管理数据生命周期,Modern.js 负责提供 Flight 渲染、解码与序列化适配器。
|
|
8
|
-
|
|
9
|
-
这套集成遵循 TanStack Router 的数据模型,不会使用 React Router 的 RSC 渲染器或 `RSCStaticRouter`。
|
|
10
|
-
|
|
11
|
-
## 适用场景
|
|
12
|
-
|
|
13
|
-
当你希望实现以下能力时,可以使用 TanStack Router RSC:
|
|
14
|
-
|
|
15
|
-
- 在服务端渲染数据密集型 UI,同时继续由 TanStack Router 管理路由与缓存。
|
|
16
|
-
- 从路由 Loader 返回服务端渲染的 UI,并通过 `Route.useLoaderData()` 消费。
|
|
17
|
-
- 将交互 UI 保留在 Client Component 中,将复杂的数据访问、格式化与标记生成放在服务端。
|
|
18
|
-
- 在数据写入后,通过 TanStack Router 的 invalidation 刷新服务端渲染的 UI。
|
|
19
|
-
|
|
20
|
-
如果路由组件本身可以直接作为普通 Server Component 使用,请先阅读通用的 [React Server Components(RSC)](/guides/basic-features/render/rsc) 文档。当服务端 UI 需要通过 TanStack Router Loader 数据传递时,再使用本文介绍的能力。
|
|
21
|
-
|
|
22
|
-
## 前置条件
|
|
23
|
-
|
|
24
|
-
使用 TanStack Router RSC 前,请确保项目满足以下条件:
|
|
25
|
-
|
|
26
|
-
1. React 与 React DOM 已升级到 19 或更高版本。
|
|
27
|
-
2. 通过 [`server.rsc`](/configure/app/server/rsc) 开启 RSC。
|
|
28
|
-
3. 已选择 TanStack Router 作为 Modern.js 路由运行时。
|
|
29
|
-
4. 已完成 [React Server Components(RSC)](/guides/basic-features/render/rsc) 中的基础配置,包括必要的 RSC 运行时依赖。
|
|
30
|
-
|
|
31
|
-
:::tip
|
|
32
|
-
如果项目通过 `--router tanstack` 创建,请在路由代码中使用 `@modern-js/plugin-tanstack/runtime`,这样可以让路由代码与生成它的 TanStack 插件保持一致。
|
|
33
|
-
:::
|
|
34
|
-
|
|
35
|
-
## 开启 RSC
|
|
36
|
-
|
|
37
|
-
设置 `server.rsc` 为 `true`,并开启 TanStack Router 运行时:
|
|
38
|
-
|
|
39
|
-
```ts title="modern.config.ts"
|
|
40
|
-
import { defineConfig } from '@modern-js/app-tools';
|
|
41
|
-
|
|
42
|
-
export default defineConfig({
|
|
43
|
-
runtime: {
|
|
44
|
-
router: true,
|
|
45
|
-
},
|
|
46
|
-
server: {
|
|
47
|
-
rsc: true,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## 导入路径
|
|
53
|
-
|
|
54
|
-
在应用代码中使用 TanStack 插件的运行时导出:
|
|
55
|
-
|
|
56
|
-
- 运行时 API:`@modern-js/plugin-tanstack/runtime`
|
|
57
|
-
- 仅服务端 RSC 辅助函数:`@modern-js/plugin-tanstack/runtime/rsc/server`
|
|
58
|
-
- 客户端 RSC 辅助函数:`@modern-js/plugin-tanstack/runtime/rsc/client`
|
|
59
|
-
|
|
60
|
-
下面的示例都使用这些路径。
|
|
61
|
-
|
|
62
|
-
## Payload Router 行为
|
|
63
|
-
|
|
64
|
-
开启 `server.rsc` 后,TanStack RSC 导航会使用原生的 payload-router 路径,而不是 React Router 的 `RSCStaticRouter` 路径:
|
|
65
|
-
|
|
66
|
-
- 需要服务端 Loader 数据的浏览器导航会携带 `x-rsc-tree: true` 请求当前 URL。
|
|
67
|
-
- 服务端会执行匹配到的 TanStack 路由 Loader,并序列化包含匹配路由 id、params、loader data、errors、location 与路由元信息的 `ServerPayload`,再通过 Modern.js RSC renderer 以流式方式返回。
|
|
68
|
-
- Loader 重定向会以 `X-Modernjs-Redirect` 与 `X-Modernjs-BaseUrl` 响应头返回,让客户端继续交给 TanStack Router 完成导航。
|
|
69
|
-
- `404` 响应和 TanStack `notFound()` 结果会作为路由错误保留,从而渲染最近的 TanStack not-found boundary。
|
|
70
|
-
- 标记了 client loader 的路由会在 RSC 导航期间继续执行自己的 client loader。对应的服务端 loader data 会从 RSC payload 中有意省略,避免用服务端数据覆盖 client-loader 结果。
|
|
71
|
-
|
|
72
|
-
首次 SSR hydration 仍然使用 TanStack Router 现有的 SSR bootstrap scripts。payload-router 请求路径只用于后续 RSC 导航和 invalidation,因此同一套路由树可以同时使用普通 TanStack SSR loader data 与 RSC loader data。
|
|
73
|
-
|
|
74
|
-
## 从 Loader 渲染服务端 UI
|
|
75
|
-
|
|
76
|
-
当服务端输出不需要客户端提供插槽时,使用 `renderServerComponent`。Loader 在服务端执行,将 Server Component 渲染成 Flight stream,并返回可以被客户端路由组件渲染的值。
|
|
77
|
-
|
|
78
|
-
```tsx title="src/routes/products/page.data.tsx"
|
|
79
|
-
import { renderServerComponent } from '@modern-js/plugin-tanstack/runtime/rsc/server';
|
|
80
|
-
import { ProductGrid } from './ProductGrid';
|
|
81
|
-
|
|
82
|
-
export async function loader() {
|
|
83
|
-
const products = await fetchProducts();
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
ProductGrid: await renderServerComponent(
|
|
87
|
-
<ProductGrid products={products} />,
|
|
88
|
-
),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
```tsx title="src/routes/products/page.tsx"
|
|
94
|
-
import { createFileRoute } from '@modern-js/plugin-tanstack/runtime';
|
|
95
|
-
|
|
96
|
-
export const Route = createFileRoute('/products')({
|
|
97
|
-
component: ProductsPage,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
function ProductsPage() {
|
|
101
|
-
const { ProductGrid } = Route.useLoaderData();
|
|
102
|
-
|
|
103
|
-
return <main>{ProductGrid}</main>;
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
:::warning
|
|
108
|
-
当客户端路由会消费 Loader 返回值时,不要从 TanStack Router Loader 直接返回原始 JSX。请使用 `renderServerComponent` 或 `createCompositeComponent` 包装服务端渲染的 UI,使其能够通过 TanStack Router 的 SSR 数据管线序列化。
|
|
109
|
-
:::
|
|
110
|
-
|
|
111
|
-
## 渲染带客户端插槽的服务端 UI
|
|
112
|
-
|
|
113
|
-
当服务端渲染的标记需要接收客户端内容、render props 或交互组件时,使用 `createCompositeComponent`。服务端组件会接收插槽占位符,客户端路由通过 `<CompositeComponent>` 提供插槽实现。
|
|
114
|
-
|
|
115
|
-
```tsx title="src/routes/products/page.data.tsx"
|
|
116
|
-
import { createCompositeComponent } from '@modern-js/plugin-tanstack/runtime/rsc/server';
|
|
117
|
-
import type React from 'react';
|
|
118
|
-
|
|
119
|
-
export async function loader() {
|
|
120
|
-
return {
|
|
121
|
-
ProductCard: await createCompositeComponent<{
|
|
122
|
-
AddToCart?: (id: string) => React.ReactNode;
|
|
123
|
-
children?: React.ReactNode;
|
|
124
|
-
}>(async props => {
|
|
125
|
-
const product = await fetchFeaturedProduct();
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<article>
|
|
129
|
-
<h2>{product.name}</h2>
|
|
130
|
-
{props.children}
|
|
131
|
-
{props.AddToCart?.(product.id)}
|
|
132
|
-
</article>
|
|
133
|
-
);
|
|
134
|
-
}),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
```tsx title="src/routes/products/page.tsx"
|
|
140
|
-
import {
|
|
141
|
-
CompositeComponent,
|
|
142
|
-
createFileRoute,
|
|
143
|
-
} from '@modern-js/plugin-tanstack/runtime';
|
|
144
|
-
import { AddToCartButton } from '../../components/AddToCartButton';
|
|
145
|
-
|
|
146
|
-
export const Route = createFileRoute('/products')({
|
|
147
|
-
component: ProductsPage,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
function ProductsPage() {
|
|
151
|
-
const { ProductCard } = Route.useLoaderData();
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<CompositeComponent
|
|
155
|
-
src={ProductCard}
|
|
156
|
-
AddToCart={id => <AddToCartButton productId={id} />}
|
|
157
|
-
>
|
|
158
|
-
<p>Ships today.</p>
|
|
159
|
-
</CompositeComponent>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
如果希望缺失的插槽实现在开发阶段尽早报错,可以使用 `strict`:
|
|
165
|
-
|
|
166
|
-
```tsx
|
|
167
|
-
<CompositeComponent src={ProductCard} strict />
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## 刷新 RSC 数据
|
|
171
|
-
|
|
172
|
-
由于 RSC 值属于 TanStack Router 数据,应通过 TanStack Router invalidation 刷新:
|
|
173
|
-
|
|
174
|
-
```tsx
|
|
175
|
-
import { useRouter } from '@modern-js/plugin-tanstack/runtime';
|
|
176
|
-
|
|
177
|
-
function SaveButton() {
|
|
178
|
-
const router = useRouter();
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<button
|
|
182
|
-
onClick={async () => {
|
|
183
|
-
await saveProduct();
|
|
184
|
-
await router.invalidate();
|
|
185
|
-
}}
|
|
186
|
-
>
|
|
187
|
-
Save
|
|
188
|
-
</button>
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
如果将 RSC 值存入 TanStack Query,请为对应 query 关闭 structural sharing。RSC 值是 Flight stream 外层的代理对象,对其进行深度结构比较没有实际意义。
|
|
194
|
-
|
|
195
|
-
## 注意事项
|
|
196
|
-
|
|
197
|
-
- `*/rsc/server` 中的服务端辅助函数只能被 Loader 或服务端模块导入。
|
|
198
|
-
- 客户端路由可以将 `renderServerComponent` 的返回值直接作为 React node 渲染。
|
|
199
|
-
- `<CompositeComponent>` 只应接收 `createCompositeComponent` 返回的值。
|
|
200
|
-
- 插槽函数应返回 React node,并只接收可序列化参数。不可序列化对象、请求对象、数据库句柄等应保留在服务端代码中。
|
|
201
|
-
- TanStack Router invalidation 是刷新边界。除非应用有明确的缓存策略,否则不要在 Router 之外再构建一套并行的 RSC 缓存。
|
|
202
|
-
|
|
203
|
-
## 最佳实践
|
|
204
|
-
|
|
205
|
-
### 优先由 Router 管理数据
|
|
206
|
-
|
|
207
|
-
将 RSC 值保存在 Loader 数据中,并让 TanStack Router 管理缓存、水合、失效与导航。这样可以保持实现与 TanStack Router 对齐,避免引入并行的 RSC 路由层。
|
|
208
|
-
|
|
209
|
-
### 使用能满足需求的最小辅助函数
|
|
210
|
-
|
|
211
|
-
没有客户端插槽的 UI 使用 `renderServerComponent`。只有当服务端渲染的标记需要客户端插槽或 render props 时,才使用 `createCompositeComponent`。
|
|
212
|
-
|
|
213
|
-
### 保持服务端与客户端边界清晰
|
|
214
|
-
|
|
215
|
-
不要在客户端组件中导入服务端辅助函数。`CompositeComponent` 应从运行时导出或 `*/rsc/client` 导入;`renderServerComponent` 与 `createCompositeComponent` 只应从 `*/rsc/server` 导入。
|
|
216
|
-
|
|
217
|
-
### 自动化修改
|
|
218
|
-
|
|
219
|
-
使用 coding agent 或 codemod 修改相关代码时,应将 TanStack RSC 的改动限制在 TanStack Router 插件及其 RSC 辅助导出内。不要为了该能力修改 Modern.js 核心路由,也不要替换 TanStack Router 的 SSR 管线。
|
|
220
|
-
|
|
221
|
-
## 相关链接
|
|
222
|
-
|
|
223
|
-
- [React Server Components(RSC)](/guides/basic-features/render/rsc)
|
|
224
|
-
- [路由基础](/guides/basic-features/routes/routes)
|
|
225
|
-
- [数据缓存](/guides/basic-features/data/data-cache)
|
|
226
|
-
- [TanStack Router 文档](https://tanstack.com/router)
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Cypress
|
|
2
|
-
|
|
3
|
-
Cypress 是一个用于 E2E 测试和组件测试的框架。
|
|
4
|
-
|
|
5
|
-
在 Modern.js 中使用 Cypress 需要先安装依赖,可以执行以下命令:
|
|
6
|
-
|
|
7
|
-
import { PackageManagerTabs } from '@theme';
|
|
8
|
-
|
|
9
|
-
<PackageManagerTabs command={{ npm: "npm install -D cypress", yarn: "yarn add -D cypress", pnpm: "pnpm install -D cypress" }} />
|
|
10
|
-
|
|
11
|
-
接下来,创建 `cypress.config.ts` 文件,并添加以下内容:
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { defineConfig } from 'cypress'
|
|
15
|
-
|
|
16
|
-
export default defineConfig({
|
|
17
|
-
e2e: {
|
|
18
|
-
setupNodeEvents(on, config) {},
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## 编写测试用例
|
|
24
|
-
|
|
25
|
-
现在,使用 Cypress 来编写一个 E2E 用例,首先创建两张 Modern.js 的页面。
|
|
26
|
-
|
|
27
|
-
```tsx title="routes/page.tsx"
|
|
28
|
-
import { Link } from '@modern-js/runtime/router';
|
|
29
|
-
|
|
30
|
-
const Index = () => (
|
|
31
|
-
<div>
|
|
32
|
-
<h1>Home</h1>
|
|
33
|
-
<Link to="/about">About</Link>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
export default Index;
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```tsx title="routes/about/page.tsx"
|
|
41
|
-
import { Link } from '@modern-js/runtime/router';
|
|
42
|
-
|
|
43
|
-
const Index = () => (
|
|
44
|
-
<div>
|
|
45
|
-
<h1>About</h1>
|
|
46
|
-
<Link to="/">Home</Link>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
export default Index;
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
接下来,创建测试用例文件:
|
|
54
|
-
|
|
55
|
-
```ts title="cypress/e2e/app.cy.ts"
|
|
56
|
-
describe('Navigation', () => {
|
|
57
|
-
it('should navigate to the about page', () => {
|
|
58
|
-
// Start from the index page
|
|
59
|
-
cy.visit('http://localhost:8080/')
|
|
60
|
-
|
|
61
|
-
// Find a link with an href attribute containing "about" and click it
|
|
62
|
-
cy.get('a[href*="about"]').click()
|
|
63
|
-
|
|
64
|
-
// The new url should include "/about"
|
|
65
|
-
cy.url().should('include', '/about')
|
|
66
|
-
|
|
67
|
-
// The new page should contain an h1 with "About"
|
|
68
|
-
cy.get('h1').contains('About')
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
测试文件可能会缺少 API 的类型,你可以参考 [Cypress - Typescript](https://docs.cypress.io/guides/tooling/typescript-support#Configure-tsconfigjson) 文档解决。
|
|
74
|
-
|
|
75
|
-
你可以将命令添加到 `package.json` 中:
|
|
76
|
-
|
|
77
|
-
```json title="package.json"
|
|
78
|
-
{
|
|
79
|
-
"scripts": {
|
|
80
|
-
"test": "cypress open"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## 运行测试用例
|
|
86
|
-
|
|
87
|
-
执行上述 `test` 命令,运行测试用例:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
DevTools listening on ws://127.0.0.1:55203/devtools/browser/xxxxx
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Cypress 会打开一个无头浏览器,按照提示你可以找到对应的测试文件,并自动运行 E2E 测试:
|
|
94
|
-
|
|
95
|
-

|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
# Jest
|
|
2
|
-
|
|
3
|
-
Jest 是一个 JavaScript 测试框架,它主要和 React Testing Library 一起用于单元测试和 Snapshot 测试。
|
|
4
|
-
|
|
5
|
-
在 Modern.js 中使用 Jest 需要先安装依赖,可以执行以下命令:
|
|
6
|
-
|
|
7
|
-
import { PackageManagerTabs } from '@theme';
|
|
8
|
-
|
|
9
|
-
<PackageManagerTabs command={{
|
|
10
|
-
npm: "npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom",
|
|
11
|
-
yarn: "yarn add -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom",
|
|
12
|
-
pnpm: "pnpm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom"
|
|
13
|
-
}} />
|
|
14
|
-
|
|
15
|
-
随后,你可以运行以下命令,这将自动在项目中初始化 Jest,并生成一个基础的 `jest.config.ts` 配置:
|
|
16
|
-
|
|
17
|
-
<PackageManagerTabs command={{
|
|
18
|
-
npm: "npm init jest@latest",
|
|
19
|
-
yarn: "yarn create jest@latest",
|
|
20
|
-
pnpm: "pnpm create jest@latest"
|
|
21
|
-
}} />
|
|
22
|
-
|
|
23
|
-
## 配置文件
|
|
24
|
-
|
|
25
|
-
:::note
|
|
26
|
-
本章节会使用 `.ts` 文件来完成 Jest 测试。
|
|
27
|
-
:::
|
|
28
|
-
|
|
29
|
-
相比于其他的测试框架,Jest 在构建层面需要更多的配置,例如处理 JSX 和 ESM 语法,因此首先需要安装一些额外的依赖:
|
|
30
|
-
|
|
31
|
-
<PackageManagerTabs command={{
|
|
32
|
-
npm: "npm install -D babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript",
|
|
33
|
-
yarn: "yarn add -D babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript",
|
|
34
|
-
pnpm: "pnpm install -D babel-jest @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript"
|
|
35
|
-
}} />
|
|
36
|
-
|
|
37
|
-
### 配置 Jest
|
|
38
|
-
|
|
39
|
-
你需要进一步配置 `jest.config.ts` 文件,以便让 Jest 能够正确地编译和运行测试用例。下面是一个最基本的配置:
|
|
40
|
-
|
|
41
|
-
```ts title="jest.config.ts"
|
|
42
|
-
import type { Config } from 'jest';
|
|
43
|
-
|
|
44
|
-
const config: Config = {
|
|
45
|
-
coverageProvider: 'babel',
|
|
46
|
-
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
|
47
|
-
testEnvironment: 'jsdom',
|
|
48
|
-
transform: {
|
|
49
|
-
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
|
|
50
|
-
},
|
|
51
|
-
transformIgnorePatterns: [],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export default config;
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
配置中,将 `transformIgnorePatterns` 设置为了空数组,意味着所有的文件都会经过编译,如果你希望提升测试运行的速度,可以按需配置。
|
|
58
|
-
|
|
59
|
-
`setupFilesAfterEnv` 会在启动时执行,在 `jest.setup.ts` 中,可以引入 `@testing-library/jest-dom`。它包含了一组便捷的自定义匹配器,例如 `.toBeInTheDocument()`,使编写测试变得更容易:
|
|
60
|
-
|
|
61
|
-
```ts title="jest.setup.ts"
|
|
62
|
-
import '@testing-library/jest-dom';
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### 配置 Babel
|
|
66
|
-
|
|
67
|
-
你需要配置 Babel 让 Jest 能够自动编译 JSX 等语法,下面是一个基本的配置:
|
|
68
|
-
|
|
69
|
-
```js title="babel.config.js"
|
|
70
|
-
module.exports = {
|
|
71
|
-
presets: [
|
|
72
|
-
['@babel/preset-env', { targets: { node: 'current' } }],
|
|
73
|
-
['@babel/preset-react', { runtime: 'automatic' }],
|
|
74
|
-
'@babel/preset-typescript',
|
|
75
|
-
],
|
|
76
|
-
};
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## 编写测试用例
|
|
80
|
-
|
|
81
|
-
现在,你可以开始编写测试用例了,首先在 `package.json` 中添加一个 `test` 命令:
|
|
82
|
-
|
|
83
|
-
```json title="package.json"
|
|
84
|
-
{
|
|
85
|
-
"scripts": {
|
|
86
|
-
"test": "jest"
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
创建一个简单的页面用于测试:
|
|
92
|
-
|
|
93
|
-
```tsx title="routes/page.tsx"
|
|
94
|
-
import { Link } from '@modern-js/runtime/router';
|
|
95
|
-
|
|
96
|
-
const Index = () => (
|
|
97
|
-
<div>
|
|
98
|
-
<h1>Home</h1>
|
|
99
|
-
<Link to="/about">About</Link>
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
export default Index;
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
添加测试用例,检测页面中是否有预期的文本:
|
|
107
|
-
|
|
108
|
-
```tsx title="__tests__/page.test.tsx"
|
|
109
|
-
import '@testing-library/jest-dom';
|
|
110
|
-
import { render, screen } from '@testing-library/react';
|
|
111
|
-
import { BrowserRouter as Router } from '@modern-js/runtime/router';
|
|
112
|
-
import Page from '../routes/page';
|
|
113
|
-
|
|
114
|
-
describe('Page', () => {
|
|
115
|
-
it('renders a heading', () => {
|
|
116
|
-
render(
|
|
117
|
-
<Router>
|
|
118
|
-
<Page />
|
|
119
|
-
</Router>,
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const heading = screen.getByRole('heading', { level: 1 });
|
|
123
|
-
|
|
124
|
-
expect(heading).toBeInTheDocument();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
上述用例中,我们从 `@modern-js/runtime/router` 引入了 `<Router>` 组件,这是因为 React Router 在渲染部分路由相关组件时,必须要有对应的上下文。
|
|
130
|
-
|
|
131
|
-
:::note
|
|
132
|
-
直接在 Modern.js 应用中运行时,`<Router>` 组件会自动注入。
|
|
133
|
-
:::
|
|
134
|
-
|
|
135
|
-
## 运行测试用例
|
|
136
|
-
|
|
137
|
-
执行上述 `test` 命令,运行测试用例:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
PASS src/__tests__/page.test.tsx
|
|
141
|
-
Page
|
|
142
|
-
✓ renders a heading (31 ms)
|
|
143
|
-
|
|
144
|
-
Test Suites: 1 passed, 1 total
|
|
145
|
-
Tests: 1 passed, 1 total
|
|
146
|
-
Snapshots: 0 total
|
|
147
|
-
Time: 0.959 s, estimated 1 s
|
|
148
|
-
```
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Vitest
|
|
2
|
-
|
|
3
|
-
Vitest 是由 Vite 驱动的测试框架,和 React Testing Library 配合可以用于单元测试。
|
|
4
|
-
|
|
5
|
-
在 Modern.js 中使用 Vitest 需要先安装依赖,可以执行以下命令:
|
|
6
|
-
|
|
7
|
-
import { PackageManagerTabs } from '@theme';
|
|
8
|
-
|
|
9
|
-
<PackageManagerTabs command={{
|
|
10
|
-
npm: "npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom",
|
|
11
|
-
yarn: "yarn add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom",
|
|
12
|
-
pnpm: "pnpm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom",
|
|
13
|
-
bun: "bun add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom"
|
|
14
|
-
}} />
|
|
15
|
-
|
|
16
|
-
接下来,你需要创建一个 Vitest 配置文件 `vitest.config.ts`,内容如下:
|
|
17
|
-
|
|
18
|
-
```ts title="vitest.config.ts"
|
|
19
|
-
import { defineConfig } from 'vitest/config'
|
|
20
|
-
import react from '@vitejs/plugin-react'
|
|
21
|
-
|
|
22
|
-
export default defineConfig({
|
|
23
|
-
plugins: [react()],
|
|
24
|
-
test: {
|
|
25
|
-
environment: 'jsdom',
|
|
26
|
-
},
|
|
27
|
-
})
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
更多关于 Vitest 配置的信息,可以参考 [Vitest 配置文档](https://vitest.dev/config/#configuration)。
|
|
31
|
-
|
|
32
|
-
你可以选择性的将 `vitest` 命令添加到 `package.json` 中:
|
|
33
|
-
|
|
34
|
-
```json title="package.json"
|
|
35
|
-
{
|
|
36
|
-
"scripts": {
|
|
37
|
-
"test": "vitest"
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
运行该命令后,Vitest 会自动监听你的文件变化,并重新运行用例。
|
|
43
|
-
|
|
44
|
-
## 创建单元测试
|
|
45
|
-
|
|
46
|
-
首先,创建一个简单的页面用于测试:
|
|
47
|
-
|
|
48
|
-
```tsx title="routes/page.tsx"
|
|
49
|
-
import { Link } from '@modern-js/runtime/router';
|
|
50
|
-
|
|
51
|
-
const Index = () => (
|
|
52
|
-
<div>
|
|
53
|
-
<h1>Home</h1>
|
|
54
|
-
<Link to="/about">About</Link>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
export default Index;
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
添加测试用例,检测页面中是否有预期的文本:
|
|
62
|
-
|
|
63
|
-
```tsx title="__tests__/page.test.tsx"
|
|
64
|
-
import { expect, test } from 'vitest';
|
|
65
|
-
import { render, screen } from '@testing-library/react';
|
|
66
|
-
import { BrowserRouter as Router } from '@modern-js/runtime/router';
|
|
67
|
-
import Page from '../routes/page';
|
|
68
|
-
|
|
69
|
-
test('Page', () => {
|
|
70
|
-
render(
|
|
71
|
-
<Router>
|
|
72
|
-
<Page />
|
|
73
|
-
</Router>,
|
|
74
|
-
);
|
|
75
|
-
expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined();
|
|
76
|
-
});
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
上述用例中,我们从 `@modern-js/runtime/router` 引入了 `<Router>` 组件,这是因为 React Router 在渲染部分路由相关组件时,必须要有对应的上下文。
|
|
80
|
-
|
|
81
|
-
:::note
|
|
82
|
-
直接在 Modern.js 应用中运行时,`<Router>` 组件会自动注入。
|
|
83
|
-
:::
|
|
84
|
-
|
|
85
|
-
## 运行测试用例
|
|
86
|
-
|
|
87
|
-
执行上述 `test` 命令,运行测试用例:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
✓ src/__tests__/page.test.tsx (1)
|
|
91
|
-
✓ Page
|
|
92
|
-
|
|
93
|
-
Test Files 1 passed (1)
|
|
94
|
-
Tests 1 passed (1)
|
|
95
|
-
Start at 15:37:12
|
|
96
|
-
Duration 999ms (transform 119ms, setup 0ms, collect 365ms, tests 33ms, environment 421ms, prepare 44ms)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
PASS Waiting for file changes...
|
|
100
|
-
```
|