@modern-js/main-doc 2.0.0-beta.4 → 2.0.0-beta.6
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/.turbo/turbo-build.log +1 -1
- package/en/docusaurus-plugin-content-docs/current/apis/app/commands/dev.md +8 -3
- package/en/docusaurus-plugin-content-docs/current/apis/app/commands/inspect.md +33 -8
- package/en/docusaurus-plugin-content-docs/current/apis/app/commands/serve.md +32 -0
- package/en/docusaurus-plugin-content-docs/current/apis/app/hooks/src/server.md +31 -0
- package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/bootstrap.md +4 -3
- package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/create-app.md +2 -3
- package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/use-module-apps.md +1 -1
- package/en/docusaurus-plugin-content-docs/current/components/init-app.md +1 -1
- package/en/docusaurus-plugin-content-docs/current/configure/app/builder-plugins.md +70 -0
- package/en/docusaurus-plugin-content-docs/current/configure/app/dev/with-master-app.md +0 -1
- package/en/docusaurus-plugin-content-docs/current/configure/app/plugins.md +11 -5
- package/en/docusaurus-plugin-content-docs/current/configure/app/source/disable-entry-dirs.md +38 -0
- package/en/docusaurus-plugin-content-docs/current/configure/app/source/entries.md +66 -2
- package/en/docusaurus-plugin-content-docs/current/configure/app/tools/esbuild.md +1 -1
- package/en/docusaurus-plugin-content-docs/current/guides/concept/entries.md +1 -1
- package/en/docusaurus-plugin-content-docs/current/guides/get-started/quick-start.md +3 -3
- package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/_category_.json +4 -0
- package/en/docusaurus-plugin-content-docs/current/guides/{concept → topic-detail/framework-plugin}/lifecycle.md +0 -0
- package/package.json +3 -3
- package/zh/apis/app/commands/dev.md +9 -4
- package/zh/apis/app/commands/inspect.md +34 -9
- package/zh/apis/app/commands/{start.md → serve.md} +3 -3
- package/zh/apis/app/hooks/src/index_.md +1 -1
- package/zh/apis/app/hooks/src/server.md +31 -0
- package/zh/apis/app/runtime/core/bootstrap.md +3 -4
- package/zh/apis/app/runtime/core/create-app.md +1 -18
- package/zh/apis/app/runtime/core/use-module-apps.md +64 -33
- package/zh/apis/app/runtime/web-server/hook.md +1 -1
- package/zh/apis/app/runtime/web-server/middleware.md +1 -0
- package/zh/components/default-mwa-generate.md +5 -0
- package/zh/components/deploy.md +1 -0
- package/zh/components/enable-micro-frontend.md +13 -0
- package/zh/components/init-app.md +2 -2
- package/zh/components/micro-runtime-config.md +18 -0
- package/zh/components/prerequisites.md +2 -2
- package/zh/components/release-note.md +1 -0
- package/zh/configure/app/builder-plugins.md +72 -0
- package/zh/configure/app/deploy/_category_.json +4 -0
- package/zh/configure/app/deploy/microFrontend.md +64 -0
- package/zh/configure/app/dev/with-master-app.md +0 -2
- package/zh/configure/app/plugins.md +10 -4
- package/zh/configure/app/runtime/master-app.md +33 -36
- package/zh/configure/app/source/disable-entry-dirs.md +37 -0
- package/zh/configure/app/source/entries-dir.md +0 -3
- package/zh/configure/app/source/entries.md +66 -3
- package/zh/guides/advanced-features/compatibility.md +12 -1
- package/zh/guides/advanced-features/eslint.md +21 -21
- package/zh/guides/advanced-features/ssg.md +14 -3
- package/zh/guides/advanced-features/ssr.md +1 -1
- package/zh/guides/advanced-features/testing.md +11 -0
- package/zh/guides/advanced-features/web-server.md +12 -1
- package/zh/guides/basic-features/css/tailwindcss.md +11 -0
- package/zh/guides/basic-features/data-fetch.md +398 -5
- package/zh/guides/basic-features/routes.md +35 -3
- package/zh/guides/concept/entries.md +104 -14
- package/zh/guides/get-started/quick-start.md +8 -5
- package/zh/guides/get-started/upgrade.md +3 -1
- package/zh/guides/{concept → topic-detail/framework-plugin}/lifecycle.md +0 -0
- package/zh/guides/topic-detail/micro-frontend/c01-introduction.md +29 -0
- package/zh/guides/topic-detail/micro-frontend/c02-development.md +191 -0
- package/zh/guides/topic-detail/micro-frontend/c03-main-app.md +246 -0
- package/zh/guides/topic-detail/micro-frontend/c04-communicate.md +54 -0
- package/zh/guides/topic-detail/micro-frontend/{mixed-stack.md → c05-mixed-stack.md} +3 -3
- package/zh/guides/topic-detail/monorepo/create-sub-project.md +2 -2
- package/zh/tutorials/first-app/c01-start.md +9 -4
- package/zh/tutorials/first-app/c03-css.md +19 -0
- package/zh/tutorials/first-app/c04-routes.md +4 -4
- package/zh/tutorials/first-app/c05-loader.md +3 -3
- package/zh/tutorials/first-app/c06-model.md +19 -19
- package/zh/tutorials/first-app/c07-container.md +38 -23
- package/zh/tutorials/first-app/c08-entries.md +4 -1
- package/en/docusaurus-plugin-content-docs/current/apis/app/commands/start.md +0 -32
- package/zh/guides/advanced-features/custom-app.md +0 -70
- package/zh/guides/topic-detail/micro-frontend/communicate.md +0 -39
- package/zh/guides/topic-detail/micro-frontend/debugging.md +0 -168
- package/zh/guides/topic-detail/micro-frontend/introduction.md +0 -13
- package/zh/guides/topic-detail/micro-frontend/route-mode.md +0 -110
|
@@ -5,7 +5,404 @@ sidebar_position: 3
|
|
|
5
5
|
|
|
6
6
|
Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过这些 API,在 CSR 和 SSR 环境同构的进行开发。
|
|
7
7
|
|
|
8
|
-
需要注意的是,这些 API
|
|
8
|
+
需要注意的是,这些 API 并不帮助应用发起请求,而是帮助开发者更好地管理数据,提升项目的性能。
|
|
9
|
+
|
|
10
|
+
## Data Loader(推荐)
|
|
11
|
+
|
|
12
|
+
Modern.js 推荐使用约定式路由做路由的管理,通过 Modern.js 的[约定式(嵌套)路由](/docs/guides/basic-features/routes#约定式路由),每个路由组件(`layout.ts` 或 `page.ts`)可以导出一个函数`loader`,该函数可以在组件渲染之前,为路由组件提供数据。
|
|
13
|
+
|
|
14
|
+
:::info
|
|
15
|
+
Modern.js 旧版支持通过 [useLoader](#useloader旧版) 获取数据,这已经不是我们推荐的用法,除迁移过程外,不推荐两者混用。
|
|
16
|
+
:::
|
|
17
|
+
|
|
18
|
+
### 基础示例
|
|
19
|
+
|
|
20
|
+
每个路由组件可以导出一个 `loader` 函数,然后通过 `useLoaderData` 函数获得相应的数据:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// routes/user/page.tsx
|
|
24
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
|
25
|
+
|
|
26
|
+
export const loader = async(): ProfileData => {
|
|
27
|
+
const res = await fetch('https://api/user/profile');
|
|
28
|
+
return await res.json();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function UserPage() {
|
|
32
|
+
const profileData = useLoaderData() as ProfileData;
|
|
33
|
+
return <div>{profileData}</div>;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
在 CSR 环境下,`loader` 函数会在客户端执行,`loader` 函数内可以使用浏览器的 API(但通常不需要,也不推荐)。
|
|
38
|
+
|
|
39
|
+
在 SSR 环境下,不管是首屏,还是在客户端的导航,`loader` 函数只会在服务端执行,这里可以调用任意的 Node.js API,同时这里使用的任何依赖和代码都不会包含在客户端的 bundle 中。
|
|
40
|
+
|
|
41
|
+
:::info
|
|
42
|
+
在以后的版本中,Modern.js 可能会支持在 CSR 环境下,`loader` 函数也在服务端运行,以提高性能和安全性,所以这里建议尽可能地保证 loader 的纯粹,只做数据获取的场景。
|
|
43
|
+
:::
|
|
44
|
+
|
|
45
|
+
当在客户端导航时,基于 Modern.js 的[约定式路由](/docs/guides/basic-features/routes),所有的 loader 函数会并行执行(请求),即当访问 `/user/profile` 时,`/user` 和 `/user/profile` 下的 loader 函数都会并行执行(请求),以提高客户端的性能。
|
|
46
|
+
|
|
47
|
+
### Loader 放在单独文件中
|
|
48
|
+
|
|
49
|
+
除了支持 `loader` 函数从前端组件中导出,Modern.js 也支持将 `loader` 函数放在单独的文件中, 将 `loader` 函数放在单独的文件中,可以无需留意[副作用](#副作用处理)相关注意事项,但可以要注意的是,路由组件如 `layout.ts` 和 loader 文件 `layout.loader.ts` 不能引入彼此的代码,如果需要相关类型可以使用 `import type`,可以见下面的示例:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
.
|
|
53
|
+
└── routes
|
|
54
|
+
├── layout.tsx
|
|
55
|
+
└── user
|
|
56
|
+
├── layout.tsx
|
|
57
|
+
├── layout.loader.ts
|
|
58
|
+
├── page.tsx
|
|
59
|
+
└── page.loader.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
如[基础示例](#基础示例)中的 `loader` 可以用以下代码替代:
|
|
63
|
+
```tsx
|
|
64
|
+
// routes/user/page.loader.tsx
|
|
65
|
+
type ProfileData = { /* some type declarations */ }
|
|
66
|
+
|
|
67
|
+
export const loader = async(): ProfileData => {
|
|
68
|
+
const res = await fetch('https://api/user/profile');
|
|
69
|
+
return await res.json();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// routes/user/page.tsx
|
|
73
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
|
74
|
+
// 这里只能使用 import type,导入类型
|
|
75
|
+
import type { ProfileData } from './page.loader';
|
|
76
|
+
|
|
77
|
+
export default function UserPage() {
|
|
78
|
+
const profileData = useLoaderData() as ProfileData;
|
|
79
|
+
return <div>{profileData}</div>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
### `loader` 函数
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
`loader` 函数有两个入参:
|
|
88
|
+
##### `Params`
|
|
89
|
+
|
|
90
|
+
当路由文件通过 `[]` 时,会作为[动态路由](/docs/guides/basic-features/routes#动态路由),动态路由片段会作为参数传入 loader 函数:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// routes/user/[id]/page.tsx
|
|
94
|
+
import { LoaderArgs } from '@modern-js/runtime/router';
|
|
95
|
+
|
|
96
|
+
export const loader = async({ params }: LoaderArgs) => {
|
|
97
|
+
const { id } = params;
|
|
98
|
+
const res = await fetch(`https://api/user/${id}`);
|
|
99
|
+
return res.json();
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
当访问 `/user/123` 时,`loader` 函数的参数为 `{ params: { id: '123' } }`。
|
|
104
|
+
|
|
105
|
+
##### `request`
|
|
106
|
+
|
|
107
|
+
request 是一个 [Fetch Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) 实例。
|
|
108
|
+
|
|
109
|
+
一个常见的使用场景是通过 `request` 获取查询参数:
|
|
110
|
+
```tsx
|
|
111
|
+
// routes/user/[id]/page.tsx
|
|
112
|
+
import { LoaderArgs } from '@modern-js/runtime/router';
|
|
113
|
+
|
|
114
|
+
export const loader = async({ request }: LoaderArgs) => {
|
|
115
|
+
const url = new URL(request.url);
|
|
116
|
+
const userId = url.searchParams.get("id");
|
|
117
|
+
return queryUser(userId);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### 返回值
|
|
122
|
+
|
|
123
|
+
`loader` 函数的返回值可以是任何可序列化的内容,也可以是一个 [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) 实例:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
export const loader = async(): ProfileData => {
|
|
127
|
+
return {
|
|
128
|
+
message: 'hello world',
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
默认情况下,`loader` 返回的响应 `Content-type` 是 `application/json`,`status` 为 200,你可以通过自定义 `Response` 来设置:
|
|
134
|
+
```tsx
|
|
135
|
+
export const loader = async(): ProfileData => {
|
|
136
|
+
const data = {message: 'hello world'};
|
|
137
|
+
return new Response(JSON.stringify(data), {
|
|
138
|
+
status: 200,
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json; utf-8",
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 请求 API
|
|
147
|
+
|
|
148
|
+
Modern.js 对 `fetch` API 做了 polyfill, 用于发起请求,该 API 与浏览器的 `fetch` API 一致,但是在服务端也能使用该 API 发起请求,这意味着不管是 CSR 还是 SSR,都可以使用统一的 `fetch` API 进行数据获取:
|
|
149
|
+
```tsx
|
|
150
|
+
export async function loader(){
|
|
151
|
+
const res = await fetch('https://api/user/profile');
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
### 错误处理
|
|
157
|
+
|
|
158
|
+
在 `loader` 函数中,可以通过 `throw error` 或者 `throw response` 的方式处理错误,当 `loader` 函数中有错误被抛出时,Modern.js 会停止执行当前 loader 中的代码,并将前端 UI 切换到定义的 [`ErrorBoundary`](/docs/guides/basic-features/routes#错误处理) 组件:
|
|
159
|
+
```tsx
|
|
160
|
+
// routes/user/profile/page.tsx
|
|
161
|
+
export async function loader(){
|
|
162
|
+
const res = await fetch('https://api/user/profile');
|
|
163
|
+
if(!res.ok){
|
|
164
|
+
throw res;
|
|
165
|
+
}
|
|
166
|
+
return res.json();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// routes/user/profile/error.tsx
|
|
170
|
+
import { useRouteError } from '@modern-js/runtime/router';
|
|
171
|
+
const ErrorBoundary = () => {
|
|
172
|
+
const error = useRouteError() as Response;
|
|
173
|
+
return (
|
|
174
|
+
<div>
|
|
175
|
+
<h1>{error.status}</h1>
|
|
176
|
+
<h2>{error.statusText}</h2>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default ErrorBoundary;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 获取上层组件数据
|
|
185
|
+
|
|
186
|
+
很多场景下,子组件需要获取到祖先组件 loader 中的数据,你可以通过 `useRouteLoaderData` 方便地获取到祖先组件的数据:
|
|
187
|
+
```tsx
|
|
188
|
+
// routes/user/profile/page.tsx
|
|
189
|
+
import { useRouteLoaderData } from '@modern-js/runtime/router';
|
|
190
|
+
|
|
191
|
+
export default function UserLayout() {
|
|
192
|
+
// 获取 routes/user/layout.tsx 中 loader 返回的数据
|
|
193
|
+
const data = useRouteLoaderData('user/layout');
|
|
194
|
+
return (
|
|
195
|
+
<div>
|
|
196
|
+
<h1>{data.name}</h1>
|
|
197
|
+
<h2>{data.age}</h2>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`userRouteLoaderData` 接受一个参数 `routeId`,在使用约定式路由时,Modern.js 会为你自动生成`routeId`,`routeId` 的值是对应组件相对于 `src/routes` 的路径,如上面的例子中,子组件想要获取 `routes/user/layout.tsx` 中 loader 返回的数据,`routeId` 的值就是 `user/layout`。
|
|
204
|
+
|
|
205
|
+
在多 entry(MPA) 场景下,`routeId` 的值需要加上对应 entry 的 name,entry name 非指定情况下一般是 entry 目录名,如以下目录结构:
|
|
206
|
+
```bash
|
|
207
|
+
.
|
|
208
|
+
└── src
|
|
209
|
+
├── entry1
|
|
210
|
+
│ ├── layout.tsx
|
|
211
|
+
└── entry2
|
|
212
|
+
└── layout.tsx
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
如果想获取 `entry1/layout.tsx` 中 loader 返回的数据,`routeId` 的值就是 `entry1_layout`。
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
### (WIP)Loading UI
|
|
219
|
+
|
|
220
|
+
:::info
|
|
221
|
+
此功能目前是实验性质,后续 API 可能有调整。
|
|
222
|
+
目前仅支持 CSR,敬请期待 Streaming SSR。
|
|
223
|
+
:::
|
|
224
|
+
|
|
225
|
+
因为获取数据是异步的,在数据获取完成之前,常常需要展示一个 Loading UI,以下是一个基本的示例,假设有以下目录:
|
|
226
|
+
```bash
|
|
227
|
+
.
|
|
228
|
+
└── routes
|
|
229
|
+
├── layout.tsx
|
|
230
|
+
└── user
|
|
231
|
+
├── layout.tsx
|
|
232
|
+
└── page.ts
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
我们在 `user/layout.tsx` 中获取用户的详细数据,在获取数据之前,希望展示一个 Loading UI。
|
|
236
|
+
|
|
237
|
+
首先,在项目中在 `routes` 目录下增加一个 `loading.tsx` 组件,该 `loading` 组件会对子目录下的所有路由生效(如 user):
|
|
238
|
+
```bash
|
|
239
|
+
.
|
|
240
|
+
└── routes
|
|
241
|
+
├── layout.tsx
|
|
242
|
+
└── user
|
|
243
|
+
├── layout.tsx
|
|
244
|
+
└── page.ts
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
:::info
|
|
248
|
+
loading 组件的具体用法,请查看[loading](/docs/guides/basic-features/routes#loading)
|
|
249
|
+
:::
|
|
250
|
+
|
|
251
|
+
然后,在 `user/layout.tsx` 中添加以下代码:
|
|
252
|
+
```tsx title="routes/user/layout.tsx"
|
|
253
|
+
import {
|
|
254
|
+
Await,
|
|
255
|
+
defer,
|
|
256
|
+
useLoaderData,
|
|
257
|
+
Outlet
|
|
258
|
+
} from '@modern-js/runtime/router';
|
|
259
|
+
|
|
260
|
+
export const loader = () => {
|
|
261
|
+
return defer({
|
|
262
|
+
// fetchUserInfo 是一个异步函数,返回用户信息
|
|
263
|
+
userInfo: fetchUserInfo(),
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export default function UserLayout() {
|
|
268
|
+
const { userInfo } = useLoaderData() as {userInfo: Promise<UserInfo>};
|
|
269
|
+
return (
|
|
270
|
+
<div>
|
|
271
|
+
<Await resolve={userInfo} children={userInfo => (
|
|
272
|
+
<div>
|
|
273
|
+
<span>{userInfo.name}</span>
|
|
274
|
+
<span>{userInfo.age}</span>
|
|
275
|
+
<Outlet>
|
|
276
|
+
</div>
|
|
277
|
+
)}>
|
|
278
|
+
</Await>
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
:::info
|
|
285
|
+
Await 组件的具体用法请查看 [Await](https://reactrouter.com/en/main/components/await)
|
|
286
|
+
|
|
287
|
+
defer 的具体用法请查看 [defer](https://reactrouter.com/en/main/guides/deferred)
|
|
288
|
+
:::
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
<!-- TODO 缓存相关 -->
|
|
292
|
+
|
|
293
|
+
### 副作用处理
|
|
294
|
+
|
|
295
|
+
如上面所述,Modern.js 会将服务端代码 `loader` 从客户端 bundle 中移除,有一些你需要注意的事项,如果不想受副作用的影响,可以使用[单独文件](#单独文件) 的方式定义 `loader`。
|
|
296
|
+
|
|
297
|
+
:::info
|
|
298
|
+
在 CSR 场景下,通常副作用对项目影响较小,但依然希望你能遵循以下约定。
|
|
299
|
+
:::
|
|
300
|
+
|
|
301
|
+
模块的副作用可以认为是在模块被加载时执行的代码,比如:
|
|
302
|
+
```tsx
|
|
303
|
+
// routes/user/page.tsx
|
|
304
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
|
305
|
+
// highlight-next-line
|
|
306
|
+
import { transformProfile } from "./utils";
|
|
307
|
+
// highlight-next-line
|
|
308
|
+
console.log(transformProfile);
|
|
309
|
+
|
|
310
|
+
export const loader = async(): ProfileData => {
|
|
311
|
+
const res = await fetch('https://api/user/profile');
|
|
312
|
+
const data = await res.json();
|
|
313
|
+
return transformProfile(data);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export default function UserPage() {
|
|
317
|
+
const profileData = useLoaderData() as ProfileData;
|
|
318
|
+
return <div>{profileData}</div>;
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
因为 `console.log` 在 `routes/user/page.tsx` 加载时,就会被执行,编译器不会移除它,`transformProfile` 就会打包到客户端的 bundle 中。
|
|
323
|
+
|
|
324
|
+
所以我们不推荐有任何代码在模块加载时执行,包括项目代码和使用的第三方模块,以下是一些我们不推荐的写法:
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// routes/user/page.tsx
|
|
328
|
+
export default function UserPage() {
|
|
329
|
+
return <div>profile</div>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
UserPage.config = {}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
// routes/init.ts
|
|
337
|
+
document.title = 'Modern.js';
|
|
338
|
+
|
|
339
|
+
// routes/layout.tsx
|
|
340
|
+
import "./init.ts";
|
|
341
|
+
|
|
342
|
+
export default function Layout() {
|
|
343
|
+
return <></>
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
在 SSR 场景,通常你需要在 `loader` 函数中使用 Node.js 核心包和依赖,对于非 Node.js 核心包,需要使用一个特殊约定的文件做重新导出,如使用 `fs-extra`:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
// routes/user/avatar.tsx
|
|
351
|
+
import { useLoaderData } from '@modern-js/runtime/router';
|
|
352
|
+
import { readFile } from './utils.server';
|
|
353
|
+
|
|
354
|
+
type ProfileData = { /* some type declarations */ }
|
|
355
|
+
|
|
356
|
+
export const loader = async(): ProfileData => {
|
|
357
|
+
const profile = await readFile('profile.json');
|
|
358
|
+
return profile;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export default function UserPage() {
|
|
362
|
+
const profileData = useLoaderData() as ProfileData;
|
|
363
|
+
return <div>{profileData}</div>;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// routes/user/utils.server.ts
|
|
367
|
+
export * from 'fs-extra';
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
### 错误用法
|
|
372
|
+
|
|
373
|
+
1. `loader` 中只能返回可序列化的数据,在 SSR 环境下,`loader` 函数的返回值会被序列化为 JSON 字符串,然后在客户端被反序列化为对象。因此,`loader` 函数中不能返回不可序列化的数据(如函数)。
|
|
374
|
+
|
|
375
|
+
:::warning
|
|
376
|
+
目前 CSR 下没有这个限制,但我们强烈推荐你遵循该限制,且未来我们可能在 CSR 下也加上该限制。
|
|
377
|
+
:::
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
// This won't work!
|
|
381
|
+
export const loader = () => {
|
|
382
|
+
return {
|
|
383
|
+
user: {},
|
|
384
|
+
method: () => {
|
|
385
|
+
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
2. Modern.js 会帮你调用 `loader` 函数,你不应该自己调用 `loader` 函数:
|
|
392
|
+
```ts
|
|
393
|
+
// This won't work!
|
|
394
|
+
export const loader = async () => {
|
|
395
|
+
const res = fetch('https://api/user/profile');
|
|
396
|
+
return res.json();
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export default function RouteComp() {
|
|
400
|
+
const data = loader();
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
3. 在服务端运行时,`loader` 函数会被打包为一个统一的 bundle,所以我们不推荐服务端的代码使用 `__filename` 和 `__dirname`。
|
|
405
|
+
|
|
9
406
|
|
|
10
407
|
## useLoader(旧版)
|
|
11
408
|
|
|
@@ -58,8 +455,4 @@ Modern.js 在新版本中,设计了全新的 Loader 方案。新方案解决
|
|
|
58
455
|
:::note
|
|
59
456
|
详细 API 可以查看 [useLoader](/docs/apis/app/runtime/core/use-loader)。
|
|
60
457
|
:::
|
|
61
|
-
## Route Loader
|
|
62
458
|
|
|
63
|
-
:::note
|
|
64
|
-
敬请期待
|
|
65
|
-
:::
|
|
@@ -177,7 +177,7 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
|
177
177
|
|
|
178
178
|
`routes/` 下每一层目录中,开发者可以创建 `loading.tsx` 文件,默认导出一个 `<Loading>` 组件。
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
当路由目录下存在该组件和 `layout` 组件时,这一级子路由下所有的路由切换时,都会以该 `<Loading>` 组件作为 JS Chunk 加载时的 Fallback UI。例如以下文件目录:
|
|
181
181
|
|
|
182
182
|
```bash
|
|
183
183
|
.
|
|
@@ -191,7 +191,38 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
|
191
191
|
└── page.tsx
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
当定义 `loading.tsx` 时,就相当于以下布局:
|
|
195
|
+
```tsx title="当路由为 / 时"
|
|
196
|
+
<Layout>
|
|
197
|
+
<Suspense fallback={<Loading/>}>
|
|
198
|
+
<Page><Page>
|
|
199
|
+
</Suspense>
|
|
200
|
+
</Layout>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```tsx title="当路由为 /blog 时"
|
|
204
|
+
<Layout>
|
|
205
|
+
<Suspense fallback={<Loading/>}>
|
|
206
|
+
<BlogPage />
|
|
207
|
+
</Suspense>
|
|
208
|
+
</Layout>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```tsx title="当路由为 /blog/123 时"
|
|
212
|
+
<Layout>
|
|
213
|
+
<Suspense fallback={<Loading/>}>
|
|
214
|
+
<BlogIdPage />
|
|
215
|
+
</Suspense>
|
|
216
|
+
</Layout>
|
|
217
|
+
```
|
|
218
|
+
:::info
|
|
219
|
+
当目录的 layout 组件不存在时,该目录下的 loading 组件也不会生效。
|
|
220
|
+
:::
|
|
221
|
+
|
|
222
|
+
当路由从 `/` 跳转到 `/blog` 时,如果 `blog/page` 组件的 JS Chunk 还未加载,则会先展示 `loading.tsx` 中导出的组件 UI。
|
|
223
|
+
|
|
224
|
+
同理,当路由从 `/` 或者 `/blog` 跳转到 `/blog/123` 时,如果 `blog/[id]/page` 组件的 JS Chunk 还未加载,也会先展示 `loading.tsx` 中导出的组件 UI。
|
|
225
|
+
|
|
195
226
|
|
|
196
227
|
### 错误处理
|
|
197
228
|
|
|
@@ -206,7 +237,7 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
|
|
|
206
237
|
|
|
207
238
|
```tsx
|
|
208
239
|
import { useRouteError } from '@modern-js/runtime/router';
|
|
209
|
-
|
|
240
|
+
const ErrorBoundary = () => {
|
|
210
241
|
const error = useRouteError();
|
|
211
242
|
return (
|
|
212
243
|
<div>
|
|
@@ -215,6 +246,7 @@ export default const ErrorBoundary = () => {
|
|
|
215
246
|
</div>
|
|
216
247
|
)
|
|
217
248
|
}
|
|
249
|
+
export default ErrorBoundary;
|
|
218
250
|
```
|
|
219
251
|
|
|
220
252
|
## 自控式路由
|
|
@@ -3,7 +3,7 @@ title: 入口
|
|
|
3
3
|
sidebar_position: 2
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
入口是 Modern.js
|
|
6
|
+
入口是 Modern.js 默认的文件约定,项目的每一个入口是一张独立的页面,对应一条**服务端路由**。
|
|
7
7
|
|
|
8
8
|
很多配置,如 HTML 模板、Meta 信息、是否开启 SSR、SSG、服务端路由规则都是以入口为维度划分的。
|
|
9
9
|
|
|
@@ -56,10 +56,13 @@ Modern.js 可以很方便的将单入口切换成多入口。可以在项目下
|
|
|
56
56
|
执行 `pnpm run dev` 后,可以看到新增一条 `/new-entry` 的路由,并且被迁移的代码路由并未发生变化。
|
|
57
57
|
|
|
58
58
|
:::note
|
|
59
|
-
Modern.js 会将和 `package.json` 中 `name`
|
|
59
|
+
Modern.js 会将和 `package.json` 中 `name` 字段同名的入口作为主入口,默认路由为 `/`,其他入口默认路由为 `/{entryName}`。
|
|
60
60
|
:::
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
|
|
63
|
+
## 入口类型
|
|
64
|
+
|
|
65
|
+
不同的入口类型具有不同的编译和运行时行为。在 Modern.js 创建项目时,开发者可以手动选择创建**框架模式**或是**构建模式**的项目。完成创建后,可以看到不同模式的项目样板文件是不同的。
|
|
63
66
|
|
|
64
67
|
默认情况下,Modern.js 启动项目前会对 `src/` 下的文件进行扫描,识别入口,并生成对应的服务端路由。
|
|
65
68
|
|
|
@@ -82,34 +85,121 @@ Modern.js 会将和 `package.json` 中 `name` 同名的目录作为主入口,
|
|
|
82
85
|
|
|
83
86
|
当项目不是单入口应用时,Modern.js 会进一步查看 `src/` 下的一级目录。
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
### 框架模式入口
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
框架模式指需要使用 Modern.js 框架能力,例如 Router、SSR、一体化调用等。这类入口约定下,开发者定义的入口并不是真正的 Webpack 编译入口。Modern.js 在启动时会生成一个封装过的入口,可以在 `node_modules/.modern/{entryName}/index.js` 找到真正的入口。
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
#### 约定式路由
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
如果入口中存在 `routes/` 目录,Modern.js 会在启动时扫描 `routes/` 下的文件,基于文件约定,自动生成客户端路由(react-router)。
|
|
92
95
|
|
|
93
96
|
详细内容可以参考[路由](/docs/guides/basic-features/routes)。
|
|
94
97
|
|
|
95
|
-
|
|
98
|
+
#### 自定义路由
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
如果入口中存在 `App.[jt]sx?` 文件,开发者可以在这个文件中自由的设置客户端路由,或者不设置客户端路由。
|
|
98
101
|
|
|
99
102
|
详细内容可以参考[路由](/docs/guides/basic-features/routes)。
|
|
100
103
|
|
|
101
|
-
|
|
104
|
+
#### 自定义 App
|
|
105
|
+
|
|
106
|
+
如果入口中存在 `index.[jt]sx` 文件,并且当文件默认导出函数时,Modern.js 还是会根据 runtime 的设置情况生成 createApp 包裹后的代码。在渲染过程中,将 createApp 包裹后的组件作为参数传递给 index 文件导出的函数,这样开发者可以自定义将组件挂载到 DOM 节点上,或在挂载前添加自定义行为。例如:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import ReactDOM from 'react-dom/client'
|
|
110
|
+
import { bootstrap } from '@modern-js/runtime';
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
export default (App: React.ComponentType) => {
|
|
114
|
+
// do something before bootstrap...
|
|
115
|
+
bootstrap(App, 'root', undefined, ReactDOM);
|
|
116
|
+
};
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
:::warning
|
|
120
|
+
由于 bootstrap 函数需要兼容 React17 和 React18 的用法,调用 bootstrap 函数时需要手动传入 ReactDOM 参数。
|
|
121
|
+
:::
|
|
122
|
+
|
|
123
|
+
Modern.js 生成的文件内容如下:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import React from 'react';
|
|
127
|
+
import ReactDOM from 'react-dom/client';
|
|
128
|
+
import customBootstrap from '@_edenx_src/index.tsx';
|
|
129
|
+
import App from '@_edenx_src/App';
|
|
130
|
+
import { router, state } from '@edenx/runtime/plugins';
|
|
131
|
+
|
|
132
|
+
const IS_BROWSER = typeof window !== 'undefined' && window.name !== 'nodejs';
|
|
133
|
+
const MOUNT_ID = 'root';
|
|
134
|
+
|
|
135
|
+
let AppWrapper = null;
|
|
136
|
+
|
|
137
|
+
function render() {
|
|
138
|
+
AppWrapper = createApp({
|
|
139
|
+
// runtime 的插件参数...
|
|
140
|
+
})(App)
|
|
141
|
+
if (IS_BROWSER) {
|
|
142
|
+
customBootstrap(AppWrapper);
|
|
143
|
+
}
|
|
144
|
+
return AppWrapper
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
AppWrapper = render();
|
|
148
|
+
|
|
149
|
+
export default AppWrapper;;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 构建模式入口
|
|
153
|
+
|
|
154
|
+
构建模式是指不使用任何 Modern.js 运行时的能力,完全由开发者自己定义项目 Webpack 的入口。
|
|
155
|
+
|
|
156
|
+
如果入口中存在 `index.[jt]sx` ,并且没有默认导出函数时,这时候该文件就是真正的 Webpack 入口文件。这里和 [Create React App](https://github.com/facebook/create-react-app) 类似,需要自己将组件挂载到 DOM 节点、添加热更新代码等。例如:
|
|
102
157
|
|
|
103
|
-
|
|
158
|
+
```js title=src/index.jsx
|
|
159
|
+
import React from 'react';
|
|
160
|
+
import ReactDOM from 'react-dom';
|
|
161
|
+
import App from './App';
|
|
104
162
|
|
|
105
|
-
|
|
163
|
+
ReactDOM.render(<App />, document.getElementById('root'));
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Modern.js **不推荐**使用这种方式,这种方式丧失了框架的一些能力,如 **`modern.config.js` 文件中的 `runtime` 配置将不会再生效**。但是在项目从其他框架迁移到 Modern.js,例如 CRA,或是自己手动搭建的 webpack 时,这种方式会非常有用。
|
|
106
167
|
|
|
107
|
-
|
|
168
|
+
## 使用配置文件定义入口
|
|
108
169
|
|
|
109
|
-
|
|
170
|
+
有些时候,已有的项目并不是按照 Modern.js 的目录结构来搭建的。如果强行要按照这种结构来工作,会有比较大的迁移成本。
|
|
110
171
|
|
|
111
172
|
在 Modern.js 中,除了使用文件约定生成入口外,还可以在 `modern.config.[jt]s` 中手动配置入口。
|
|
112
173
|
|
|
174
|
+
```ts
|
|
175
|
+
export default defineConfig({
|
|
176
|
+
source: {
|
|
177
|
+
entries: {
|
|
178
|
+
// 指定一个名称为 entry_customize 的新入口
|
|
179
|
+
entry_customize: './src/home/test/index.js',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
113
185
|
:::tip
|
|
114
186
|
详情可以查看 [source.entries](/docs/configure/app/source/entries)。
|
|
115
187
|
:::
|
|
188
|
+
|
|
189
|
+
## 禁用默认入口扫描
|
|
190
|
+
|
|
191
|
+
另外,项目的部分结构可能恰巧命中了 Modern.js 的约定,但实际上这部分并不是真实的入口。
|
|
192
|
+
|
|
193
|
+
Modern.js 提供了配置,来禁用默认的入口扫描。与配置的入口结合使用,大部分项目可以在不修改目录结构的情况下,快速的进行迁移。
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
export default defineConfig({
|
|
197
|
+
source: {
|
|
198
|
+
disableDefaultEntries: true,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
:::tip
|
|
204
|
+
详情可以查看 [source.disableDefaultEntries](/docs/configure/app/source/disable-default-entries)。
|
|
205
|
+
:::
|
|
@@ -46,7 +46,7 @@ import DebugApp from '@site-docs/components/debug-app.md'
|
|
|
46
46
|
可以通过配置文件来开启功能,或覆盖 Modern.js 的默认行为。例如添加如下配置,开启 SSR:
|
|
47
47
|
|
|
48
48
|
```ts
|
|
49
|
-
import { defineConfig } from '@modern-js/app-tools';
|
|
49
|
+
import AppToolsPlugin, { defineConfig } from '@modern-js/app-tools';
|
|
50
50
|
|
|
51
51
|
// https://modernjs.dev/docs/apis/app/config
|
|
52
52
|
export default defineConfig({
|
|
@@ -57,6 +57,7 @@ export default defineConfig({
|
|
|
57
57
|
server: {
|
|
58
58
|
ssr: true,
|
|
59
59
|
},
|
|
60
|
+
plugins: [AppToolsPlugin()],
|
|
60
61
|
});
|
|
61
62
|
```
|
|
62
63
|
|
|
@@ -108,12 +109,12 @@ info File sizes after production build:
|
|
|
108
109
|
|
|
109
110
|
## 本地验证
|
|
110
111
|
|
|
111
|
-
在项目中执行 `pnpm run
|
|
112
|
+
在项目中执行 `pnpm run serve` 即可在本地验证构建产物是否正常运行:
|
|
112
113
|
|
|
113
114
|
```bash
|
|
114
|
-
$ pnpm run
|
|
115
|
+
$ pnpm run serve
|
|
115
116
|
|
|
116
|
-
> modern
|
|
117
|
+
> modern serve
|
|
117
118
|
|
|
118
119
|
Starting the modern server...
|
|
119
120
|
info App running at:
|
|
@@ -127,4 +128,6 @@ info App running at:
|
|
|
127
128
|
|
|
128
129
|
## 部署
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
import Deploy from '@site-docs/components/deploy.md'
|
|
132
|
+
|
|
133
|
+
<Deploy />
|
|
File without changes
|