@modern-js/main-doc 0.0.0-next-20221218140542 → 0.0.0-next-20221219034957

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-20221218140542
3
+ ## 0.0.0-next-20221219034957
4
4
 
5
5
  ### Patch Changes
6
6
 
@@ -30,4 +30,4 @@
30
30
  - Updated dependencies [3fae2d03b]
31
31
  - Updated dependencies [df41d71ad]
32
32
  - Updated dependencies [14b712da8]
33
- - @modern-js/builder-doc@0.0.0-next-20221218140542
33
+ - @modern-js/builder-doc@0.0.0-next-20221219034957
package/package.json CHANGED
@@ -11,20 +11,20 @@
11
11
  "modern",
12
12
  "modern.js"
13
13
  ],
14
- "version": "0.0.0-next-20221218140542",
14
+ "version": "0.0.0-next-20221219034957",
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-20221218140542"
20
+ "@modern-js/builder-doc": "0.0.0-next-20221219034957"
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-20221218140542"
27
+ "@modern-js/builder-doc": "0.0.0-next-20221219034957"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "npx ts-node ./scripts/sync.ts"
@@ -46,7 +46,6 @@ type HookContext = {
46
46
  ) => void;
47
47
  };
48
48
  request: {
49
- url: string;
50
49
  host: string;
51
50
  pathname: string;
52
51
  query: Record<string, any>;
@@ -83,6 +82,7 @@ type AfterRenderContext = {
83
82
  };
84
83
  ```
85
84
 
85
+
86
86
  ### 参数
87
87
 
88
88
  - `context`:提供当前 Hook 上下文。
@@ -53,7 +53,6 @@ type MiddlewareContext = {
53
53
  locals: Record<string, any>;
54
54
  };
55
55
  request: {
56
- url: string;
57
56
  host: string;
58
57
  pathname: string;
59
58
  query: Record<string, any>;
@@ -5,404 +5,7 @@ sidebar_position: 3
5
5
 
6
6
  Modern.js 中提供了开箱即用的数据获取能力,开发者可以通过这些 API,在 CSR 和 SSR 环境同构的进行开发。
7
7
 
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
-
8
+ 需要注意的是,这些 API 并不帮助应用发起请求,而是帮助开发者更好的管理数据与路由的关系。
406
9
 
407
10
  ## useLoader(旧版)
408
11
 
@@ -455,4 +58,8 @@ Modern.js 在新版本中,设计了全新的 Loader 方案。新方案解决
455
58
  :::note
456
59
  详细 API 可以查看 [useLoader](/docs/apis/app/runtime/core/use-loader)。
457
60
  :::
61
+ ## Route Loader
458
62
 
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>` 组件作为静态资源和数据获取时的 Fallback UI。当该目录下未定义 `layout.tsx` 文件时,`<Loading>` 组件不会生效。例如以下文件目录:
180
+ 当路由目录下存在该组件时,这一级子路由下所有的路由切换时,都会以该 `<Loading>` 组件作为 JS Chunk 加载时的 Fallback UI。当该目录下未定义 `layout.tsx` 文件时,`<Loading>` 组件不会生效。例如以下文件目录:
181
181
 
182
182
  ```bash
183
183
  .
@@ -206,7 +206,7 @@ Modern.js 会生成 `/login` 和 `/sign` 两条路由,`__auth/layout.tsx` 组
206
206
 
207
207
  ```tsx
208
208
  import { useRouteError } from '@modern-js/runtime/router';
209
- const ErrorBoundary = () => {
209
+ export default const ErrorBoundary = () => {
210
210
  const error = useRouteError();
211
211
  return (
212
212
  <div>
@@ -215,7 +215,6 @@ const ErrorBoundary = () => {
215
215
  </div>
216
216
  )
217
217
  }
218
- export default ErrorBoundary;
219
218
  ```
220
219
 
221
220
  ## 自控式路由
@@ -64,19 +64,15 @@ function App() {
64
64
  export default App;
65
65
  ```
66
66
 
67
+ 因为框架默认支持 [HMR](https://webpack.js.org/concepts/hot-module-replacement/),可以看到 `http://localhost:8080/` 里的内容会自动变成 Hello Modern.js。
68
+
67
69
  删除多余的 css 文件,保持目录没有多余的文件:
68
70
 
69
71
  ```bash
70
72
  rm src/routes/index.css
71
73
  ```
72
74
 
73
- 因为框架默认支持 [HMR](https://webpack.js.org/concepts/hot-module-replacement/),可以看到 `http://localhost:8080/` 里的内容会自动更新为:
74
-
75
- ![result](https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvt/ljhwZthlaukjlkulzlp/screenshot-20221214-141909.png)
76
-
77
- 此刻的页面还没有样式。下一章节将展开这部分的内容。
78
-
79
- ## 开启 SSR
75
+ ## 使用配置
80
76
 
81
77
  接下来,我们修改项目中的 `modern.config.ts`,开启 SSR 能力:
82
78
 
@@ -4,7 +4,7 @@ title: 添加 Loader
4
4
 
5
5
  上一章节中,我们学习了如何添加客户端路由。
6
6
 
7
- 这一章节中,我们将会学习如何为**路由组件添加 Loader**。
7
+ 这一章节中,我们将会学习如何**为路由组件添加 Loader**。
8
8
 
9
9
  到目前为止,我们都是通过硬编码的方式,为组件提供数据。如果要从远端获取数据,通常情况下会使用 `useEffect` 来做。但在启用 SSR 的情况下,`useEffect` 是不会在服务端执行的,所以这种 SSR 只能渲染很有限的 UI。
10
10
 
@@ -216,9 +216,9 @@ export const loader = async (): Promise<LoaderData> => {
216
216
 
217
217
  function Index() {
218
218
  const { data } = useLoaderData() as LoaderData;
219
- const [{ items }, { archive, setItems }] = useModel(contacts);
219
+ const [{ items }, { archive, setItem }] = useModel(contacts);
220
220
  if (items.length === 0) {
221
- setItems(data);
221
+ setItem(data);
222
222
  }
223
223
 
224
224
  return (
@@ -1,31 +0,0 @@
1
- ---
2
- title: "*.[server|node].[tj]sx"
3
- sidebar_position: 8
4
- ---
5
-
6
- Used in application projects to place server side code, it generally has the following two functions:
7
-
8
- 1. When `*.tsx` and `*. [server|node].tsx` coexist, rendering on the server side will give preference to the `*. [server|node].tsx` file instead of the `*.tsx` file.
9
-
10
- 2. When using [data loader](/docs/guides/basic-features/data-fetch), the server-side dependencies need to be re-exported from this file
11
-
12
- ```tsx
13
- // routes/user/avatar.tsx
14
- import { useLoaderData } from '@modern-js/runtime/router';
15
- import { readFile } from './utils.server';
16
-
17
- type ProfileData = { /* some type declarations */ }
18
-
19
- export const loader = async(): ProfileData => {
20
- const profile = await readFile('profile.json');
21
- return profile;
22
- }
23
-
24
- export default function UserPage() {
25
- const profileData = useLoaderData() as ProfileData;
26
- return <div>{profileData}</div>;
27
- }
28
-
29
- // routes/user/utils.server.ts
30
- export * from 'fs-extra';
31
- ```
@@ -1,31 +0,0 @@
1
- ---
2
- title: "*.[server|node].[tj]sx"
3
- sidebar_position: 8
4
- ---
5
-
6
- 应用项目中使用,用于放置服务端代码,一般有以下两个作用:
7
-
8
- 1. 当 `*.tsx` 和 `*.[server|node].tsx` 共存时,SSR 在服务端执行渲染时,会优先使用 `*.[server|node].tsx` 文件,而不是 `*.tsx` 文件。
9
-
10
- 2. 在使用 [data loader](/docs/guides/basic-features/data-fetch) 时,需要将服务端的依赖从该文件中做重导出
11
-
12
- ```tsx
13
- // routes/user/avatar.tsx
14
- import { useLoaderData } from '@modern-js/runtime/router';
15
- import { readFile } from './utils.server';
16
-
17
- type ProfileData = { /* some type declarations */ }
18
-
19
- export const loader = async(): ProfileData => {
20
- const profile = await readFile('profile.json');
21
- return profile;
22
- }
23
-
24
- export default function UserPage() {
25
- const profileData = useLoaderData() as ProfileData;
26
- return <div>{profileData}</div>;
27
- }
28
-
29
- // routes/user/utils.server.ts
30
- export * from 'fs-extra';
31
- ```