@modern-js/main-doc 2.0.0-beta.4 → 2.0.0-beta.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|