@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.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/dev.md +8 -3
  3. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/inspect.md +33 -8
  4. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/serve.md +32 -0
  5. package/en/docusaurus-plugin-content-docs/current/apis/app/hooks/src/server.md +31 -0
  6. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/bootstrap.md +4 -3
  7. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/create-app.md +2 -3
  8. package/en/docusaurus-plugin-content-docs/current/apis/app/runtime/core/use-module-apps.md +1 -1
  9. package/en/docusaurus-plugin-content-docs/current/components/init-app.md +1 -1
  10. package/en/docusaurus-plugin-content-docs/current/configure/app/builder-plugins.md +70 -0
  11. package/en/docusaurus-plugin-content-docs/current/configure/app/dev/with-master-app.md +0 -1
  12. package/en/docusaurus-plugin-content-docs/current/configure/app/plugins.md +11 -5
  13. package/en/docusaurus-plugin-content-docs/current/configure/app/source/disable-entry-dirs.md +38 -0
  14. package/en/docusaurus-plugin-content-docs/current/configure/app/source/entries.md +66 -2
  15. package/en/docusaurus-plugin-content-docs/current/configure/app/tools/esbuild.md +1 -1
  16. package/en/docusaurus-plugin-content-docs/current/guides/concept/entries.md +1 -1
  17. package/en/docusaurus-plugin-content-docs/current/guides/get-started/quick-start.md +3 -3
  18. package/en/docusaurus-plugin-content-docs/current/guides/topic-detail/framework-plugin/_category_.json +4 -0
  19. package/en/docusaurus-plugin-content-docs/current/guides/{concept → topic-detail/framework-plugin}/lifecycle.md +0 -0
  20. package/package.json +3 -3
  21. package/zh/apis/app/commands/dev.md +9 -4
  22. package/zh/apis/app/commands/inspect.md +34 -9
  23. package/zh/apis/app/commands/{start.md → serve.md} +3 -3
  24. package/zh/apis/app/hooks/src/index_.md +1 -1
  25. package/zh/apis/app/hooks/src/server.md +31 -0
  26. package/zh/apis/app/runtime/core/bootstrap.md +3 -4
  27. package/zh/apis/app/runtime/core/create-app.md +1 -18
  28. package/zh/apis/app/runtime/core/use-module-apps.md +64 -33
  29. package/zh/apis/app/runtime/web-server/hook.md +1 -1
  30. package/zh/apis/app/runtime/web-server/middleware.md +1 -0
  31. package/zh/components/default-mwa-generate.md +5 -0
  32. package/zh/components/deploy.md +1 -0
  33. package/zh/components/enable-micro-frontend.md +13 -0
  34. package/zh/components/init-app.md +2 -2
  35. package/zh/components/micro-runtime-config.md +18 -0
  36. package/zh/components/prerequisites.md +2 -2
  37. package/zh/components/release-note.md +1 -0
  38. package/zh/configure/app/builder-plugins.md +72 -0
  39. package/zh/configure/app/deploy/_category_.json +4 -0
  40. package/zh/configure/app/deploy/microFrontend.md +64 -0
  41. package/zh/configure/app/dev/with-master-app.md +0 -2
  42. package/zh/configure/app/plugins.md +10 -4
  43. package/zh/configure/app/runtime/master-app.md +33 -36
  44. package/zh/configure/app/source/disable-entry-dirs.md +37 -0
  45. package/zh/configure/app/source/entries-dir.md +0 -3
  46. package/zh/configure/app/source/entries.md +66 -3
  47. package/zh/guides/advanced-features/compatibility.md +12 -1
  48. package/zh/guides/advanced-features/eslint.md +21 -21
  49. package/zh/guides/advanced-features/ssg.md +14 -3
  50. package/zh/guides/advanced-features/ssr.md +1 -1
  51. package/zh/guides/advanced-features/testing.md +11 -0
  52. package/zh/guides/advanced-features/web-server.md +12 -1
  53. package/zh/guides/basic-features/css/tailwindcss.md +11 -0
  54. package/zh/guides/basic-features/data-fetch.md +398 -5
  55. package/zh/guides/basic-features/routes.md +35 -3
  56. package/zh/guides/concept/entries.md +104 -14
  57. package/zh/guides/get-started/quick-start.md +8 -5
  58. package/zh/guides/get-started/upgrade.md +3 -1
  59. package/zh/guides/{concept → topic-detail/framework-plugin}/lifecycle.md +0 -0
  60. package/zh/guides/topic-detail/micro-frontend/c01-introduction.md +29 -0
  61. package/zh/guides/topic-detail/micro-frontend/c02-development.md +191 -0
  62. package/zh/guides/topic-detail/micro-frontend/c03-main-app.md +246 -0
  63. package/zh/guides/topic-detail/micro-frontend/c04-communicate.md +54 -0
  64. package/zh/guides/topic-detail/micro-frontend/{mixed-stack.md → c05-mixed-stack.md} +3 -3
  65. package/zh/guides/topic-detail/monorepo/create-sub-project.md +2 -2
  66. package/zh/tutorials/first-app/c01-start.md +9 -4
  67. package/zh/tutorials/first-app/c03-css.md +19 -0
  68. package/zh/tutorials/first-app/c04-routes.md +4 -4
  69. package/zh/tutorials/first-app/c05-loader.md +3 -3
  70. package/zh/tutorials/first-app/c06-model.md +19 -19
  71. package/zh/tutorials/first-app/c07-container.md +38 -23
  72. package/zh/tutorials/first-app/c08-entries.md +4 -1
  73. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/start.md +0 -32
  74. package/zh/guides/advanced-features/custom-app.md +0 -70
  75. package/zh/guides/topic-detail/micro-frontend/communicate.md +0 -39
  76. package/zh/guides/topic-detail/micro-frontend/debugging.md +0 -168
  77. package/zh/guides/topic-detail/micro-frontend/introduction.md +0 -13
  78. 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
- 当路由目录下存在该组件时,这一级子路由下所有的路由切换时,都会以该 `<Loading>` 组件作为 JS Chunk 加载时的 Fallback UI。当该目录下未定义 `layout.tsx` 文件时,`<Loading>` 组件不会生效。例如以下文件目录:
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
- 当路由从 `/` 跳转到 `/blog` 时,如果 `<Blog>` 组件的 JS Chunk 还未加载,则会先展示 `loading.tsx` 中导出的组件 UI。但从 `/blog` 跳转到 `/blog/20220101` 时,则不会展示。
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
- export default const ErrorBoundary = () => {
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` 同名的目录作为主入口,默认路由为 `/`,其他入口默认路由为 `/{entryName}`。
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
- ### routes 入口
92
+ #### 约定式路由
90
93
 
91
- 如果入口为 `routes/` 约定,Modern.js 会在启动时扫描 `routes` 下的文件,基于文件约定,自动生成客户端路由(react-router)。
94
+ 如果入口中存在 `routes/` 目录,Modern.js 会在启动时扫描 `routes/` 下的文件,基于文件约定,自动生成客户端路由(react-router)。
92
95
 
93
96
  详细内容可以参考[路由](/docs/guides/basic-features/routes)。
94
97
 
95
- ### App 入口
98
+ #### 自定义路由
96
99
 
97
- 如果入口为 `App.[jt]sx?` 约定,开发者可以在这个文件中自由的设置客户端路由,或者不设置客户端路由。
100
+ 如果入口中存在 `App.[jt]sx?` 文件,开发者可以在这个文件中自由的设置客户端路由,或者不设置客户端路由。
98
101
 
99
102
  详细内容可以参考[路由](/docs/guides/basic-features/routes)。
100
103
 
101
- ### Index 入口
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
- 通常情况下,上面两种模式已经能满足需求,但当开发者需要自己接管 React 挂载逻辑,或完全接管 Webpack 入口时,可以使用 `index.[jt]sx?` 约定。
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
- 如果入口为 `index.[jt]sx?` 约定,Modern.js 会根据该文件是否存在默认的组件导出,来决定构建行为。
163
+ ReactDOM.render(<App />, document.getElementById('root'));
164
+ ```
165
+
166
+ Modern.js **不推荐**使用这种方式,这种方式丧失了框架的一些能力,如 **`modern.config.js` 文件中的 `runtime` 配置将不会再生效**。但是在项目从其他框架迁移到 Modern.js,例如 CRA,或是自己手动搭建的 webpack 时,这种方式会非常有用。
106
167
 
107
- 详细内容可以参考[自定义 App](/docs/guides/advanced-features/custom-app)。
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 start` 即可在本地验证构建产物是否正常运行:
112
+ 在项目中执行 `pnpm run serve` 即可在本地验证构建产物是否正常运行:
112
113
 
113
114
  ```bash
114
- $ pnpm run start
115
+ $ pnpm run serve
115
116
 
116
- > modern start
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
- 本地验证完成后,可以将 `dist/` 下的产物整理成服务器需要的结构,进行部署。
131
+ import Deploy from '@site-docs/components/deploy.md'
132
+
133
+ <Deploy />
@@ -25,7 +25,9 @@ $ pnpm run upgrade
25
25
 
26
26
  Modern.js 所有的官方包目前都使用**统一版本号**进行发布。
27
27
 
28
- 根据官网 Release Note,开发者也可以手动将项目升级到想要的版本。
28
+ import ReleaseNote from '@site-docs/components/release-note.md'
29
+
30
+ <ReleaseNote />
29
31
 
30
32
  :::tip
31
33
  当升级时,需要对 Modern.js 官方提供的所有包做统一升级,而不是升级单个依赖。