@modern-js/main-doc 0.0.0-next-20221125142437 → 0.0.0-next-20221126142204

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @modern-js/main-doc
2
2
 
3
- ## 0.0.0-next-20221125142437
3
+ ## 0.0.0-next-20221126142204
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -21,4 +21,4 @@
21
21
  - Updated dependencies [3fae2d03]
22
22
  - Updated dependencies [df41d71a]
23
23
  - Updated dependencies [14b712da]
24
- - @modern-js/builder-doc@0.0.0-next-20221125142437
24
+ - @modern-js/builder-doc@0.0.0-next-20221126142204
package/package.json CHANGED
@@ -11,20 +11,20 @@
11
11
  "modern",
12
12
  "modern.js"
13
13
  ],
14
- "version": "0.0.0-next-20221125142437",
14
+ "version": "0.0.0-next-20221126142204",
15
15
  "publishConfig": {
16
16
  "registry": "https://registry.npmjs.org/",
17
17
  "access": "public"
18
18
  },
19
19
  "peerDependencies": {
20
- "@modern-js/builder-doc": "0.0.0-next-20221125142437"
20
+ "@modern-js/builder-doc": "0.0.0-next-20221126142204"
21
21
  },
22
22
  "devDependencies": {
23
23
  "ts-node": "^10",
24
24
  "fs-extra": "^10",
25
25
  "@types/node": "^16",
26
26
  "@types/fs-extra": "^9",
27
- "@modern-js/builder-doc": "0.0.0-next-20221125142437"
27
+ "@modern-js/builder-doc": "0.0.0-next-20221126142204"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "npx ts-node ./scripts/sync.ts"
@@ -2,3 +2,64 @@
2
2
  title: 数据获取
3
3
  sidebar_position: 2
4
4
  ---
5
+
6
+ Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过这些 API,在 CSR 和 SSR 环境同构的进行开发。
7
+
8
+ 需要注意的是,这些 API 并不帮助应用发起请求,而是帮助开发者更好的管理数据与路由的关系。
9
+
10
+ ## useLoader(旧版)
11
+
12
+ **`useLoader`** 是 Modern.js 老版本中的 API。该 API 是一个 React Hook,专门提供给 SSR 应用使用,让开发者能同构的在组件中获取数据。
13
+
14
+ :::tip
15
+ CSR 的项目没有必要使用 `useLoader` 获取数据。
16
+ :::
17
+
18
+ 以下是一个最简单的例子:
19
+
20
+ ```tsx
21
+ import { useLoader } from '@modern-js/runtime';
22
+
23
+ export default () => {
24
+ const { data } = useLoader(async () => {
25
+ console.log('fetch in useLoader');
26
+
27
+ // 这里没有发送真实的请求,只是返回了一个写死的数据。
28
+ // 真实项目中,应该返回从远端获取的数据。
29
+ return {
30
+ name: 'modern.js',
31
+ };
32
+ });
33
+
34
+ return <div>Hello, {data?.name}</div>;
35
+ };
36
+ ```
37
+
38
+ 上述代码启动后,访问页面。可以看到在终端输出了日志,而在浏览器终端却没有打印日志。
39
+
40
+ 这是因为 Modern.js 在服务端渲染时,在会收集 `useLoader` 返回的数据,并将数据注入到响应的 HTML 中。如果 SSR 渲染成功,在 HTML 中可以看到如下代码片段:
41
+
42
+ ```html
43
+ <script>
44
+ window._SSR_DATA = {};
45
+ </script>
46
+ ```
47
+
48
+ 在这全局变量中,记录了每一份数据,而在浏览器端渲染的过程中,会优先使用这份数据。如果数据不存在,则会重新执行 `useLoader` 函数。
49
+
50
+ :::note
51
+ 在构建阶段,Modern.js 会自动为每个 `useLoader` 生成一个 Loader ID,并注入到 SSR 和 CSR 的 JS Bundle 中,用来关联 Loader 和数据。
52
+ :::
53
+
54
+ 相比于 Next.js 中的 `getServerSideProps`,在渲染前预先获取数据。使用 `useLoader`,可以在组件中获取局部 UI 所需要的数据,而不用将数据层层传递。同样,也不会因为不同路由需要不同数据请求,而在最外层的数据获取函数中添加冗余的逻辑。当然 `useLoader` 也存在一些问题,例如服务端代码 Treeshaking 困难,服务端需要多一次预渲染等。
55
+
56
+ Modern.js 在新版本中,设计了全新的 Loader 方案。新方案解决了这些问题,并能够配合**嵌套路由**,对页面性能做优化。
57
+
58
+ :::note
59
+ 详细 API 可以查看 [useLoader](/docs/apis/app/runtime/core/use-loader)。
60
+ :::
61
+ ## Route Loader
62
+
63
+ :::note
64
+ 敬请期待
65
+ :::
@@ -100,5 +100,30 @@ export default defineConfig({
100
100
  此时,在代码中的 `process.env.VERSION`,将会被替换为环境变量中 `VERSION` 的值。
101
101
 
102
102
  :::note
103
- `source.globalVar` 也支持将其他表达式或字符串替换为指定的值,不仅限于环境变量。
103
+ `source.globalVars` 也支持将其他表达式或字符串替换为指定的值,不仅限于环境变量。
104
104
  :::
105
+
106
+ ## 使用全局替换
107
+
108
+ 除了环境变量,Modern.js 也支持将代码中的变量替换成其它值或者表达式,可以用于在代码逻辑中区分开发环境与生产环境等场景。
109
+
110
+ 例如将代码中的 `TWO` 转换为 `1 + 1` 的表达式:
111
+
112
+ ```ts
113
+ export default {
114
+ source: {
115
+ define: {
116
+ TWO: '1 + 1',
117
+ },
118
+ },
119
+ };
120
+ ```
121
+
122
+ ```ts
123
+ const foo = TWO;
124
+
125
+ // ⬇️ Turn into being...
126
+ const foo = 1 + 1;
127
+ ```
128
+
129
+ 在大多数情况下,`source.globalVars` 已经能满足替换变量的需求。但 `source.globalVars` 传入的值都会默认被 JSON 序列化,因此无法做出像上面例子中 `1 + 1` 的替换,此时就需要使用 [`source.define`](/docs/configure/app/source/define)。
@@ -3,4 +3,269 @@ title: 路由
3
3
  sidebar_position: 1
4
4
  ---
5
5
 
6
- Modern.js 内置了 React Router v6 的支持
6
+ Modern.js 内置了对 [React Router 6](https://reactrouter.com/en/main) 的**部分**支持,并提供了多种类型的路由模式。根据不同[入口](/docs/guides/concept/entries)类型,将路由分为三种模式,分别是**约定式路由**,**自控式路由**和**配置式路由**。
7
+
8
+ :::note
9
+ 本小节提到的路由,都是客户端路由,即 SPA 路由。
10
+ :::
11
+
12
+ ## 约定式路由
13
+
14
+ 以 `routes/` 为约定的入口,Modern.js 会自动基于文件系统,生成对应的路由结构。
15
+
16
+ Modern.js 支持了业界流行的约定路由模式:**嵌套路由**,使用嵌套路由时,页面的路由 与 UI 结构是相呼应的,我们将会详细介绍这种路由模式。
17
+
18
+ ```
19
+ /user/johnny/profile /user/johnny/posts
20
+ +------------------+ +-----------------+
21
+ | User | | User |
22
+ | +--------------+ | | +-------------+ |
23
+ | | Profile | | +------------> | | Posts | |
24
+ | | | | | | | |
25
+ | +--------------+ | | +-------------+ |
26
+ +------------------+ +-----------------+
27
+ ```
28
+
29
+ ### 路由文件约定
30
+
31
+ `routes/` 目录下有两个文件约定 `layout.[jt]sx` 和 `page.[jt]sx`(后面简写为 `.tsx`)。这两个文件决定了应用的布局层次,其中 `layout.tsx` 中作为布局组件,`page.tsx` 作为内容组件,是整个路由表的叶子节点。
32
+
33
+ 例如,这里 `routes/layout.tsx` 会作为 `/` 路由下所有组件的布局组件使用:
34
+
35
+ ```bash
36
+ .
37
+ └── routes
38
+ ├── layout.tsx
39
+ ├── page.tsx
40
+ └── user
41
+ ├── layout.tsx
42
+ └── page.tsx
43
+ ```
44
+
45
+ 当路由为 `/` 时,会有以下 UI 布局:
46
+
47
+ ```tsx
48
+ <Layout>
49
+ <Page />
50
+ </Layout>
51
+ ```
52
+
53
+ 同样,`routes/user/layout.tsx` 会作为 `/user` 路由下所有组件的布局组件使用。当路由为 `/user` 时, 会有以下 UI 布局:
54
+
55
+ ```tsx
56
+ <Layout>
57
+ <UserLayout>
58
+ <UserPage>
59
+ <UserLayout>
60
+ </Layout>
61
+ ```
62
+
63
+ #### Layout
64
+
65
+ `<Layout>` 组件是指 `routes/` 目录下所有 `layout.tsx` 文件,它们表示对应路由片段的布局,使用 `<Outlet>` 表示子组件。
66
+
67
+ :::note
68
+ `<Outlet>` 是 React Router 6 中新的 API,详情可以查看 [Outlet](https://reactrouter.com/en/main/components/outlet#outlet).
69
+ :::
70
+
71
+ 为了方便介绍 `<Layout>` 与 `<Outlet>` 的关系,以下面的文件目录举例:
72
+
73
+ ```bash
74
+ .
75
+ └── routes
76
+ ├── blog
77
+ │   └── page.tsx
78
+ ├── layout.tsx
79
+ ├── page.tsx
80
+ └── user
81
+ ├── layout.tsx
82
+ └── page.tsx
83
+ ```
84
+
85
+ 1. 当路由为 `/` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/page.tsx` 中导出的组件,生成以下 UI 结构:
86
+
87
+ ```tsx
88
+ <Layout>
89
+ <Page />
90
+ </Layout>
91
+ ```
92
+
93
+ 2. 当路由为 `/blog` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/blog/page.tsx` 中导出的组件,生成以下 UI 结构:
94
+ ```tsx
95
+ <Layout>
96
+ <BlogPage />
97
+ </Layout>
98
+ ```
99
+
100
+ 3. 当路由为 `/user` 时,`routes/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/layout.tsx` 中导出的组件。`routes/user/layout.tsx` 中的 `<Outlet>` 代表的是 `routes/user/page.tsx` 中导出的组件。生成以下 UI 结构:
101
+
102
+ ```tsx
103
+ <Layout>
104
+ <UserLayout>
105
+ <UserPage>
106
+ <UserLayout>
107
+ </Layout>
108
+ ```
109
+
110
+ 总结而言,如果子路由的文件目录下存在 `layout.tsx`,上一级 `layout.tsx` 中的 `<Outlet>` 即为子路由文件目录下的 `layout.tsx` ,否则为子路由文件目录下的 `page.tsx`。
111
+
112
+ #### Page
113
+
114
+ 所有的路由,理论上都应该由 `<Page>` 组件结束。在 `page.tsx` 文件内,如果开发者引入 `<Outlet>` 组件,不会有任何效果。
115
+
116
+ ### 动态路由
117
+
118
+ 通过 `[]` 命名的文件目录,生成的路由会作为动态路由。例如以下文件目录:
119
+
120
+ ```
121
+ └── routes
122
+ ├── [id]
123
+ │   └── page.tsx
124
+ ├── blog
125
+ │   └── page.tsx
126
+ └── page.tsx
127
+ ```
128
+
129
+ `routes/[id]/page.tsx` 文件会转为 `/:id` 路由。除了可以确切匹配的 `/blog` 路由,其他所有 `/xxx` 都会匹配到该路由。
130
+
131
+ 在组件中,可以通过 [useParams](/docs/apis/app/runtime/router/#useparams) 获取对应命名的参数。
132
+
133
+ ### 无路径布局
134
+
135
+ 当目录名以 __ 开头时,对应的目录名不会转换为实际的路由路径,例如以下文件目录:
136
+
137
+ ```
138
+ .
139
+ └── routes
140
+ ├── __auth
141
+ │ ├── layout.tsx
142
+ │ ├── login
143
+ │ │ └── page.tsx
144
+ │ └── signup
145
+ │ └── page.tsx
146
+ ├── layout.tsx
147
+ └── page.tsx
148
+ ```
149
+
150
+ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组件会作为 `login/page.tsx` 和 `signup/page.tsx` 的布局组件,但__auth 不会作为路由路径片段。
151
+
152
+ 当需要为某些类型的路由,做独立的布局,或是想要将路由做归类时,这一功能非常有用。
153
+
154
+ ### 无布局路径
155
+
156
+ 有些情况下,项目需要较为复杂的路由,但这些路由又不存在独立的 UI 布局,如果像普通文件目录那边创建路由会导致目录层级较深。
157
+
158
+ 因此 Modern.js 支持了通过 `.` 来分割路由片段,代替文件目录。例如,当需要 `/user/profile/2022/edit` 时,可以直接创建如下文件:
159
+
160
+ ```
161
+ └── routes
162
+ ├── user.profile.[id].edit
163
+ │ └── page.tsx
164
+ ├── layout.tsx
165
+ └── page.tsx
166
+ ```
167
+
168
+ 访问路由时,将得到如下 UI 布局:
169
+
170
+ ```tsx
171
+ <RootLayout>
172
+ <UserProfileEdit /> // routes/user.profile.[id].edit/page.tsx
173
+ </RootLayout>
174
+ ```
175
+
176
+ ### Loading
177
+
178
+ `routes/` 下每一层目录中,开发者可以创建 `loading.tsx` 文件,默认导出一个 `<Loading>` 组件。
179
+
180
+ 当路由目录下存在该组件时,这一级子路由下所有的路由切换时,都会以该 `<Loading>` 组件作为 JS Chunk 加载时的 Fallback UI。当该目录下未定义 `layout.tsx` 文件时,`<Loading>` 组件不会生效。例如以下文件目录:
181
+
182
+ ```bash
183
+ .
184
+ └── routes
185
+ ├── blog
186
+ │   ├── [id]
187
+ │   │   └── page.tsx
188
+ │   └── page.tsx
189
+ ├── layout.tsx
190
+ ├── loading.tsx
191
+ └── page.tsx
192
+ ```
193
+
194
+ 当路由从 `/` 跳转到 `/blog` 时,如果 `<Blog>` 组件的 JS Chunk 还未加载,则会先展示 `loading.tsx` 中导出的组件 UI。但从 `/blog` 跳转到 `/blog/20220101` 时,则不会展示。
195
+
196
+ ### 错误处理
197
+
198
+ `routes/` 下每一层目录中,开发者同样可以定义一个 `error.tsx` 文件,默认导出一个 `<ErrorBoundary>` 组件。
199
+
200
+ 当有路由目录下存在该组件时,组件渲染出错会被 ErrorBoundary 组件捕获。当目录未定义 `layout.tsx` 文件时,`<ErrorBoundary>` 组件不会生效。
201
+
202
+ `<ErrorBoundary>` 可以返回出错时的 UI 视图,当前层级未声明 `<ErrorBoundary>` 组件时,错误会向上冒泡到更上层的组件,直到被捕获或抛出错误。同时,当组件出错时,只会影响捕获到该错误的路由组件及子组件,其他组件的状态和视图不受影响,可以继续交互。
203
+
204
+ <!-- Todo API 路由-->
205
+ 在 `<ErrorBoundary>` 组件内,可以使用 [useRouteError](/docs/apis/app/runtime/router/#useparams) 获取的错误的具体信息:
206
+
207
+ ```tsx
208
+ import { useRouteError } from '@modern-js/runtime/router';
209
+ export default const ErrorBoundary = () => {
210
+ const error = useRouteError();
211
+ return (
212
+ <div>
213
+ <h1>{error.status}</h1>
214
+ <h2>{error.message}</h2>
215
+ </div>
216
+ )
217
+ }
218
+ ```
219
+
220
+ ## 自控式路由
221
+
222
+ 以 `routes/` 为约定的入口,Modern.js 不会多路由做额外的操作,开发者可以自行使用 React Router 6 的 API 进行开发。
223
+
224
+ 例如:
225
+
226
+ ```tsx
227
+ import {
228
+ Route,
229
+ Routes,
230
+ BrowserRouter,
231
+ Outlet,
232
+ } from '@modern-js/runtime/router';
233
+
234
+ export default () => {
235
+ return (
236
+ <div>
237
+ <BrowserRouter>
238
+ <Routes>
239
+ <Route index element={<div>index</div>} />
240
+ <Route
241
+ path="user"
242
+ element={
243
+ <div>
244
+ User Layout
245
+ <Outlet />
246
+ </div>
247
+ }
248
+ >
249
+ <Route index element={<div>user</div>} />
250
+ <Route path="profile" element={<div>profile</div>} />
251
+ </Route>
252
+ <Route path="about" element={<div>about</div>} />
253
+ </Routes>
254
+ </BrowserRouter>
255
+ </div>
256
+ );
257
+ };
258
+ ```
259
+
260
+ :::note
261
+ 在自控式路由下,开发者如果希望在 SSR 中使用 React Router 6 中 [Loader API](https://reactrouter.com/en/main/hooks/use-loader-data#useloaderdata) 的能力会相对复杂,推荐直接使用约定式路由。Modern.js 已经为你封装好了一切。
262
+
263
+ <!-- Todo 嵌套路由带来的优化可以补充下文档-->
264
+ 如果项目只想升级到 React Router 6,而不希望使用嵌套路由带来的优化,那[useLoader](/docs/apis/app/runtime/core/use-loader) 在 SSR 下仍然可以工作。
265
+ :::
266
+
267
+ ## 配置式路由
268
+
269
+ :::note
270
+ 敬请期待
271
+ :::