@modern-js/main-doc 2.58.2 → 2.59.0

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.
Files changed (140) hide show
  1. package/docs/en/apis/app/runtime/core/use-loader.mdx +1 -1
  2. package/docs/en/community/blog/_meta.json +1 -6
  3. package/docs/en/components/deploy.mdx +1 -1
  4. package/docs/en/components/init-app.mdx +0 -1
  5. package/docs/en/components/init-rspack-app.mdx +0 -1
  6. package/docs/en/components/ssr-monitor.mdx +3 -0
  7. package/docs/en/configure/_meta.json +1 -1
  8. package/docs/en/configure/app/output/ssg.mdx +52 -141
  9. package/docs/en/configure/app/tools/swc.mdx +1 -1
  10. package/docs/en/configure/app/tools/tailwindcss.mdx +1 -1
  11. package/docs/en/guides/advanced-features/_meta.json +0 -8
  12. package/docs/en/guides/advanced-features/bff/_meta.json +1 -6
  13. package/docs/en/guides/advanced-features/rsbuild-plugin.mdx +2 -2
  14. package/docs/en/guides/advanced-features/rspack-start.mdx +7 -22
  15. package/docs/en/guides/basic-features/_meta.json +31 -9
  16. package/docs/en/guides/basic-features/css/_meta.json +1 -0
  17. package/docs/en/guides/basic-features/css/css-in-js.mdx +34 -0
  18. package/docs/en/guides/basic-features/{css-modules.mdx → css/css-modules.mdx} +0 -4
  19. package/docs/en/guides/basic-features/css/css.mdx +25 -0
  20. package/docs/en/guides/basic-features/{css.mdx → css/tailwindcss.mdx} +5 -66
  21. package/docs/en/guides/basic-features/data/_meta.json +1 -4
  22. package/docs/en/guides/basic-features/data/data-fetch.mdx +134 -235
  23. package/docs/en/guides/basic-features/data/data-write.mdx +66 -77
  24. package/docs/en/guides/basic-features/debug/_meta.json +1 -0
  25. package/docs/en/guides/basic-features/debug/rsdoctor.mdx +57 -0
  26. package/docs/en/guides/{advanced-features → basic-features/debug}/using-storybook.mdx +2 -0
  27. package/docs/en/guides/basic-features/render/_meta.json +1 -0
  28. package/docs/en/guides/basic-features/render/ssg.mdx +208 -0
  29. package/docs/en/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +38 -50
  30. package/docs/en/guides/basic-features/render/ssr.mdx +301 -0
  31. package/docs/en/guides/basic-features/render/streaming-ssr.mdx +230 -0
  32. package/docs/en/guides/basic-features/routes.mdx +275 -263
  33. package/docs/en/guides/basic-features/static-assets/_meta.json +1 -0
  34. package/docs/en/guides/basic-features/static-assets.mdx +1 -1
  35. package/docs/en/guides/basic-features/testing/_meta.json +1 -0
  36. package/docs/en/guides/basic-features/testing/cypress.mdx +95 -0
  37. package/docs/en/guides/basic-features/testing/jest.mdx +148 -0
  38. package/docs/en/guides/basic-features/testing/playwright.mdx +111 -0
  39. package/docs/en/guides/basic-features/testing/vitest.mdx +100 -0
  40. package/docs/en/guides/concept/_meta.json +1 -4
  41. package/docs/en/guides/concept/entries.mdx +78 -47
  42. package/docs/en/guides/get-started/_meta.json +1 -7
  43. package/docs/en/guides/get-started/introduction.mdx +1 -1
  44. package/docs/en/guides/get-started/quick-start.mdx +1 -2
  45. package/docs/en/guides/get-started/tech-stack.mdx +4 -6
  46. package/docs/en/guides/get-started/upgrade.mdx +16 -2
  47. package/docs/en/guides/topic-detail/framework-plugin/_meta.json +1 -1
  48. package/docs/en/guides/topic-detail/generator/_meta.json +1 -1
  49. package/docs/en/guides/topic-detail/generator/create/_meta.json +1 -5
  50. package/docs/en/guides/topic-detail/generator/create/config.mdx +0 -10
  51. package/docs/en/guides/topic-detail/generator/create/use.mdx +0 -1
  52. package/docs/en/guides/topic-detail/generator/new/_meta.json +1 -5
  53. package/docs/en/guides/topic-detail/generator/plugin/_meta.json +1 -1
  54. package/docs/en/guides/troubleshooting/_meta.json +1 -6
  55. package/docs/en/tutorials/first-app/c03-css.mdx +1 -1
  56. package/docs/zh/apis/app/runtime/core/use-loader.mdx +1 -1
  57. package/docs/zh/community/blog/_meta.json +1 -6
  58. package/docs/zh/components/deploy.mdx +1 -1
  59. package/docs/zh/components/init-app.mdx +0 -1
  60. package/docs/zh/components/init-rspack-app.mdx +0 -1
  61. package/docs/zh/components/ssr-monitor.mdx +3 -0
  62. package/docs/zh/configure/_meta.json +1 -1
  63. package/docs/zh/configure/app/output/ssg.mdx +49 -139
  64. package/docs/zh/configure/app/tools/swc.mdx +1 -1
  65. package/docs/zh/configure/app/tools/tailwindcss.mdx +1 -1
  66. package/docs/zh/guides/advanced-features/_meta.json +0 -8
  67. package/docs/zh/guides/advanced-features/bff/_meta.json +1 -6
  68. package/docs/zh/guides/advanced-features/rsbuild-plugin.mdx +2 -2
  69. package/docs/zh/guides/advanced-features/rspack-start.mdx +8 -24
  70. package/docs/zh/guides/basic-features/_meta.json +31 -9
  71. package/docs/zh/guides/basic-features/css/_meta.json +1 -0
  72. package/docs/zh/guides/basic-features/css/css-in-js.mdx +34 -0
  73. package/docs/zh/guides/basic-features/css/css.mdx +25 -0
  74. package/docs/zh/guides/basic-features/{css.mdx → css/tailwindcss.mdx} +3 -64
  75. package/docs/zh/guides/basic-features/data/_meta.json +1 -4
  76. package/docs/zh/guides/basic-features/data/data-fetch.mdx +98 -214
  77. package/docs/zh/guides/basic-features/data/data-write.mdx +54 -55
  78. package/docs/zh/guides/basic-features/debug/_meta.json +1 -0
  79. package/docs/zh/guides/basic-features/debug/rsdoctor.mdx +57 -0
  80. package/docs/zh/guides/{advanced-features → basic-features/debug}/using-storybook.mdx +1 -1
  81. package/docs/zh/guides/basic-features/render/_meta.json +1 -0
  82. package/docs/zh/guides/basic-features/render/ssg.mdx +210 -0
  83. package/docs/zh/guides/{advanced-features/ssr/cache.mdx → basic-features/render/ssr-cache.mdx} +16 -26
  84. package/docs/zh/guides/basic-features/render/ssr.mdx +309 -0
  85. package/docs/zh/guides/{advanced-features/ssr/stream.mdx → basic-features/render/streaming-ssr.mdx} +22 -37
  86. package/docs/zh/guides/basic-features/routes.mdx +252 -237
  87. package/docs/zh/guides/basic-features/static-assets/_meta.json +1 -0
  88. package/docs/zh/guides/basic-features/static-assets.mdx +2 -6
  89. package/docs/zh/guides/basic-features/testing/_meta.json +1 -0
  90. package/docs/zh/guides/basic-features/testing/cypress.mdx +95 -0
  91. package/docs/zh/guides/basic-features/testing/jest.mdx +148 -0
  92. package/docs/zh/guides/basic-features/testing/playwright.mdx +112 -0
  93. package/docs/zh/guides/basic-features/testing/vitest.mdx +100 -0
  94. package/docs/zh/guides/concept/_meta.json +1 -4
  95. package/docs/zh/guides/concept/entries.mdx +80 -58
  96. package/docs/zh/guides/get-started/_meta.json +1 -7
  97. package/docs/zh/guides/get-started/introduction.mdx +2 -2
  98. package/docs/zh/guides/get-started/quick-start.mdx +1 -2
  99. package/docs/zh/guides/get-started/tech-stack.mdx +8 -10
  100. package/docs/zh/guides/get-started/upgrade.mdx +15 -1
  101. package/docs/zh/guides/topic-detail/framework-plugin/_meta.json +1 -1
  102. package/docs/zh/guides/topic-detail/generator/_meta.json +1 -1
  103. package/docs/zh/guides/topic-detail/generator/create/_meta.json +1 -5
  104. package/docs/zh/guides/topic-detail/generator/create/config.mdx +0 -10
  105. package/docs/zh/guides/topic-detail/generator/create/use.mdx +0 -1
  106. package/docs/zh/guides/topic-detail/generator/new/_meta.json +1 -5
  107. package/docs/zh/guides/topic-detail/generator/plugin/_meta.json +1 -1
  108. package/docs/zh/guides/troubleshooting/_meta.json +1 -6
  109. package/docs/zh/tutorials/first-app/c03-css.mdx +1 -1
  110. package/i18n.json +16 -4
  111. package/package.json +6 -6
  112. package/rspress.config.ts +1 -1
  113. package/src/components/ContentCard/index.tsx +1 -1
  114. package/src/components/Sandpack/index.tsx +1 -1
  115. package/src/components/ShowcaseList/index.tsx +1 -1
  116. package/src/i18n/index.ts +1 -1
  117. package/src/pages/index.tsx +2 -2
  118. package/docs/en/apis/app/hooks/config/storybook.mdx +0 -37
  119. package/docs/en/guides/advanced-features/ssg.mdx +0 -116
  120. package/docs/en/guides/advanced-features/ssr/_meta.json +0 -5
  121. package/docs/en/guides/advanced-features/ssr/index.mdx +0 -23
  122. package/docs/en/guides/advanced-features/ssr/stream.mdx +0 -248
  123. package/docs/en/guides/advanced-features/ssr/usage.mdx +0 -341
  124. package/docs/en/guides/advanced-features/ssr.mdx +0 -555
  125. package/docs/zh/apis/app/hooks/config/storybook.mdx +0 -38
  126. package/docs/zh/guides/advanced-features/ssg.mdx +0 -116
  127. package/docs/zh/guides/advanced-features/ssr/_meta.json +0 -5
  128. package/docs/zh/guides/advanced-features/ssr/index.mdx +0 -23
  129. package/docs/zh/guides/advanced-features/ssr/usage.mdx +0 -329
  130. /package/docs/en/guides/basic-features/{mock.mdx → debug/mock.mdx} +0 -0
  131. /package/docs/en/guides/basic-features/{proxy.mdx → debug/proxy.mdx} +0 -0
  132. /package/docs/en/guides/basic-features/{json-files.mdx → static-assets/json-files.mdx} +0 -0
  133. /package/docs/en/guides/basic-features/{svg-assets.mdx → static-assets/svg-assets.mdx} +0 -0
  134. /package/docs/en/guides/basic-features/{wasm-assets.mdx → static-assets/wasm-assets.mdx} +0 -0
  135. /package/docs/zh/guides/basic-features/{css-modules.mdx → css/css-modules.mdx} +0 -0
  136. /package/docs/zh/guides/basic-features/{mock.mdx → debug/mock.mdx} +0 -0
  137. /package/docs/zh/guides/basic-features/{proxy.mdx → debug/proxy.mdx} +0 -0
  138. /package/docs/zh/guides/basic-features/{json-files.mdx → static-assets/json-files.mdx} +0 -0
  139. /package/docs/zh/guides/basic-features/{svg-assets.mdx → static-assets/svg-assets.mdx} +0 -0
  140. /package/docs/zh/guides/basic-features/{wasm-assets.mdx → static-assets/wasm-assets.mdx} +0 -0
@@ -1,116 +0,0 @@
1
- ---
2
- sidebar_position: 5
3
- ---
4
-
5
- # 静态站点生成
6
-
7
- SSG(Static Site Generation)是一种基于数据与模板,在构建时渲染完整静态网页的技术解决方案。
8
-
9
- 我们首先需要执行 `pnpm run new` 启用 SSG 功能:
10
-
11
- ```bash
12
- ? 请选择你想要的操作 启用可选功能
13
- ? 请选择功能名称 启用「SSG」功能
14
- ```
15
-
16
- 执行命令后,在 `modern.config.ts` 中注册 SSG 插件:
17
-
18
- ```ts title="modern.config.ts"
19
- import { ssgPlugin } from '@modern-js/plugin-ssg';
20
-
21
- export default defineConfig({
22
- output: {
23
- ssg: true,
24
- },
25
- plugins: [..., ssgPlugin()],
26
- });
27
- ```
28
-
29
- SSG 在**约定式路由**和**自控式路由**下的使用方式不同。
30
-
31
- ### 在约定式路由中使用
32
-
33
- **约定式路由**中, Modern.js 根据入口下的文件结构生成路由,因此框架能够收集完整的路由信息。
34
-
35
- 例如,以下是一个使用约定式路由的项目目录结构:
36
-
37
- ```
38
- .
39
- ├── src
40
- │ └── routes
41
- │ ├── layout.tsx
42
- │ ├── page.tsx
43
- │ └── user
44
- │ ├── layout.tsx
45
- │ ├── page.tsx
46
- │ └── profile
47
- │ └── page.tsx
48
- ```
49
-
50
- 上述文件目录将会生成以下三条路由:
51
-
52
- - `/`
53
- - `/user`
54
- - `/user/profile`
55
-
56
- :::note
57
- 如果还不了解约定式路由的规则,可以先查看[路由方案](/guides/basic-features/routes)。
58
-
59
- :::
60
-
61
- 在 `src/routes/page.tsx` 中添加组件代码:
62
-
63
- ```jsx title="src/routes/page.tsx"
64
- export default () => {
65
- return <div>Index Page</div>;
66
- };
67
- ```
68
-
69
- SSG 也是在 Node.js 环境渲染页面,因此我们可以在**开发阶段开启 SSR**,提前在暴露代码问题,验证 SSG 渲染效果:
70
-
71
- ```ts title="modern.config.ts"
72
- export default defineConfig({
73
- server: {
74
- ssr: process.env.NODE_ENV === 'development',
75
- }
76
- }
77
- ```
78
-
79
- 在项目根路径下执行 `pnpm run dev` 命令,查看 `dist/` 目录,此时只生成一个 HTML 文件 `main/index.html`。
80
-
81
- 在项目根路径下执行 `pnpm run build` 命令,构建完成后,查看 `dist/` 目录,此时生成 `main/index.html`、`main/user/index.html` 和 `main/user/profile/index.html` 三个 HTML 文件,内容分别对应上述三条路由。
82
-
83
- **约定式路由**中的每一条路由,都会生成一个单独的 HTML 文件。查看 `main/index.html`,可以发现包含 `Index Page` 的文本内容,这正是 SSG 的效果。
84
-
85
- 执行 `pnpm run serve` 启动项目后,访问页面,在浏览器我们工具的 Network 窗口,查看请求返回的文档,文档包含组件渲染后的完整页面内容。
86
-
87
- ### 在自控式路由中使用
88
-
89
- **自控式路由**是通过组件代码定义路由,需要应用运行起来才能获取准确的路由信息。因此,无法开箱即用的使用 SSG 功能。此时需要用户提前告知 Modern.js 框架,哪些路由需要开启 SSG 功能。
90
-
91
- 例如有以下代码,包含多条路由,设置 `output.ssg` 为 `true` 时,默认只会渲染入口路由即 `/`:
92
-
93
- import SelfRouteExample from '@site-docs/components/self-route-example';
94
-
95
- <SelfRouteExample />
96
-
97
- 如果我们希望同时开启 `/about` 的 SSG 功能,可以配置 `output.ssg`,告知 Modern.js 开启指定路由的 SSG 功能。
98
-
99
- ```ts title="modern.config.ts"
100
- export default defineConfig({
101
- output: {
102
- ssg: {
103
- routes: ['/', '/about'],
104
- },
105
- },
106
- });
107
- ```
108
-
109
- 执行 `pnpm run build` 与 `pnpm run serve` 后,访问 `http://localhost:8080/about`,在 Preview 视图中可以看到页面已经完成渲染。
110
-
111
- 查看构建产物文件,可以看到 `dist/` 目录中,新增了一个 `main/about/index.html` 文件。
112
-
113
- :::info
114
- 以上仅介绍了单入口的情况,更多相关内容可以查看 [API 文档](/configure/app/output/ssg)。
115
-
116
- :::
@@ -1,5 +0,0 @@
1
- [
2
- "usage",
3
- "stream",
4
- "cache"
5
- ]
@@ -1,23 +0,0 @@
1
- # 服务端渲染
2
-
3
- 通过在服务器端将网页的 HTML 内容渲染成完整的网页(Server-Side Rendering,简称 SSR),然后将生成的网页发送到客户端,客户端只需要显示网页即可,不需要再进行额外的渲染。
4
-
5
- 它主要的优势在于
6
-
7
- - 提高首屏加载速度:SSR 可以在服务器端生成完整的网页,客户端只需要下载网页内容即可,不需要再进行额外的渲染,从而提高了首屏加载速度。
8
- - 提高用户体验:SSR 可以提高网页的响应速度,从而提高用户体验。
9
- - 有利于 SEO:SSR 可以生成完整的 HTML 内容,搜索引擎可以直接索引 HTML 内容,从而提高网站的排名。
10
-
11
- 如果你有以下场景的需求,开发者可以考虑使用 SSR 来渲染你的页面:
12
-
13
- 1. 对首屏加载速度要求较高的网站,如电商网站、新闻网站等。
14
- 2. 对用户体验要求较高的网站,如社交网站、游戏网站等。
15
- 3. 对 SEO 要求较高的网站,如企业官网、博客等。
16
-
17
- 在 Modern.js 中,SSR 也是开箱即用的。开发者无需为 SSR 编写复杂的服务端逻辑,也无需关心 SSR 的运维,或是创建单独的服务。
18
-
19
- 除了开箱即用的 SSR 服务,为了保证开发者的开发体验,我们还具备:
20
-
21
- - 完备的 SSR 降级策略,保证页面能够安全运行。
22
- - 自动分割子路由,按需加载,减少首屏资源体积。
23
- - 内置缓存系统,解决服务端负载高的问题。
@@ -1,329 +0,0 @@
1
- ---
2
- sidebar_position: 1
3
- title: 基础使用
4
- ---
5
-
6
- # 基础使用
7
-
8
- 启用 SSR 非常简单,只需要设置 [`server.ssr`](/configure/app/server/ssr) 为 `true` 即可:
9
-
10
- ```ts title="modern.config.ts"
11
- import { defineConfig } from '@modern-js/app-tools';
12
-
13
- export default defineConfig({
14
- server: {
15
- ssr: true,
16
- },
17
- });
18
- ```
19
-
20
- ## SSR 时的数据获取
21
-
22
- Modern.js 中提供了 Data Loader,方便开发者在 SSR、CSR 下同构地获取数据。每个路由模块,如 `layout.tsx` 和 `page.tsx` 都可以定义自己的 Data Loader:
23
-
24
- ```ts title="src/routes/page.data.ts"
25
- export const loader = () => {
26
- return {
27
- message: 'Hello World',
28
- };
29
- };
30
- ```
31
-
32
- 在组件中可以通过 Hooks API 的方式获取 `loader` 函数返回的数据:
33
-
34
- ```tsx
35
- import { useLoaderData } from '@modern-js/runtime/router';
36
- export default () => {
37
- const data = useLoaderData();
38
- return <div>{data.message}</div>;
39
- };
40
- ```
41
-
42
- Modern.js 打破传统的 SSR 开发模式,提供了用户无感的 SSR 开发体验。并且提供了优雅的降级处理,一旦 SSR 请求失败,会自动降级在浏览器端重新发起请求。
43
-
44
- 不过,开发者仍然需要关注数据的兜底处理,例如 `null` 值或不符合预期的数据返回。避免在 SSR 时产生 React 渲染错误或是返回凌乱的渲染结果。
45
-
46
- :::info 补充信息
47
-
48
- 1. 当以客户端路由的方式请求页面时,Modern.js 会发送一个 HTTP 请求,服务端接收到请求后执行页面对应的 Data Loader 函数,然后将执行结果作为请求的响应返回浏览器。
49
-
50
- 2. 使用 Data Loader 时,数据获取发生在渲染前,Modern.js 也仍然支持在组件渲染时获取数据。更多相关内容可以查看[数据获取](/guides/basic-features/data/data-fetch)。
51
-
52
- :::
53
-
54
- ## 保持渲染一致
55
-
56
- 有些业务中,UI 展示会和用户设备有关,例如 [UA](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent) 信息。如果处理不够仔细,此时很有可能出现不符合预期的渲染结果。
57
-
58
- 这里通过一个例子,演示当 SSR 与 CSR 渲染不一致时出现的问题,在组件中添加以下代码:
59
-
60
- ```tsx
61
- {
62
- typeof window !== 'undefined' ? <div>browser content</div> : null;
63
- }
64
- ```
65
-
66
- 启动应用后,访问页面,会发现浏览器控制台抛出警告信息:
67
-
68
- ```sh
69
- Warning: Expected server HTML to contain a matching <div> in <div>.
70
- ```
71
-
72
- 这是 React hydrate 结果与 SSR 渲染结果不一致造成的。虽然当前页面表现正常,但在复杂应用中,很有可能因此出现 DOM 层级混乱、样式混乱等问题。
73
-
74
- :::info
75
- 关于 hydrate (注水)逻辑请参考[这里](https://zh-hans.react.dev/reference/react-dom/hydrate)。
76
-
77
- :::
78
-
79
- 应用需要保持 SSR 与 CSR 渲染结果的一致性,如果存在不一致的情况,说明这部分内容无需在 SSR 中进行渲染。Modern.js 为这类在 SSR 中不需要渲染的内容提供 [`<NoSSR>` 工具组件](/apis/app/runtime/core/use-runtime-context):
80
-
81
- ```ts
82
- import { NoSSR } from '@modern-js/runtime/ssr';
83
- ```
84
-
85
- 在不需要进行 SSR 的元素外部,用 `NoSSR` 组件包裹:
86
-
87
- ```tsx
88
- <NoSSR>
89
- <div>client content</div>
90
- </NoSSR>
91
- ```
92
-
93
- 修改代码后,刷新页发现之前的 Waring 消失。打开浏览器开发者工具的 Network 窗口,查看返回的 HTML 文档是不包含 `NoSSR` 组件包裹的内容的。
94
-
95
- :::info 补充信息
96
- [`useRuntimeContext`](/apis/app/runtime/core/use-runtime-context) 可以获取完整的请求信息,可以利用它保证 SSR 与 CSR 的渲染结果一致。
97
-
98
- :::
99
-
100
- ## 关注内存泄漏
101
-
102
- :::warning 警告
103
- 在 SSR 场景下,开发者需要特别关注内存泄露问题,即使是微小的内存泄露,在大量的访问后也会对服务造成影响。
104
-
105
- :::
106
-
107
- SSR 时,浏览器的每次请求,都会触发服务端重新执行一次组件渲染逻辑。所以,需要避免在全局定义任何可能不断增长的数据结构,或在全局进行事件订阅,或创建不会被销毁的流。
108
-
109
- 例如以下代码,使用 [redux-observable](https://redux-observable.js.org/) 时,习惯了 CSR 的开发者通常会在组件中这样编码:
110
-
111
- ```tsx
112
- /* 代码仅作为示例,不可运行 */
113
- import { createEpicMiddleware, combineEpics } from 'redux-observable';
114
-
115
- const epicMiddleware = createEpicMiddleware();
116
- const rootEpic = combineEpics();
117
-
118
- export default function Test() {
119
- epicMiddleware.run(rootEpic);
120
- return <div>Hello Modern.js</div>;
121
- }
122
- ```
123
-
124
- 在组件外层创建 Middleware 实例 `epicMiddleware`,并在组件内部调用 `epicMiddleware.run`。
125
-
126
- 在浏览器端,这段代码不会造成任何问题,但是在 SSR 时,Middleware 实例会一直无法被销毁。每次渲染组件,调用 `epicMiddleware.run(rootEpic)` 时,都会在内部添加新的事件绑定,导致整个对象不断变大,最终对应用性能造成影响。
127
-
128
- CSR 中这类问题不易被发觉,因此从 CSR 切换到 SSR 时,如果不确定应用是否存在这类隐患,可以对应用进行压测。
129
-
130
- ## 收敛服务端数据
131
-
132
- 为了使浏览器端能够直接使用 SSR 阶段请求的数据,Modern.js 会将渲染过程中收集的数据与状态注入到 HTML 内。但是,CSR 应用常常存在接口数据量大、组件状态未收敛的情况,这时如果直接使用 SSR,渲染得到的 HTML 体积可能会存在过大的问题。此时,SSR 不仅无法为应用带来用户体验上的提升,反而可能起到相反的作用。
133
-
134
- 因此,使用 SSR 时,**开发者需要为应用做合理的瘦身**:
135
-
136
- 1. 关注首屏,SSR 中可以只请求首屏需要的数据,并在浏览器端渲染剩余的部分。
137
- 2. 将与渲染无关的数据,从接口返回数据中剔除。
138
-
139
- ## Serverless Pre-render
140
-
141
- :::warning
142
- x.43.0+ 已废弃,请使用 [SSR Cache](guides/advanced-features/ssr/cache) 替代
143
- :::
144
-
145
- Modern.js 提供 Serverless Pre-rendering (SPR) 这一特性来提升 SSR 性能。
146
-
147
- SPR 利用预渲染与缓存技术,为 SSR 页面提供静态 Web 的响应性能。它让 SSR 应用拥有静态 Web 页面的响应速度与稳定性,同时还能保持数据的动态更新。
148
-
149
- 在 Modern.js 中使用 SPR 非常简单,只需要在组件中新增 `PreRender` 组件,该组件所在的页面就会自动开启 SPR。
150
-
151
- 这里模拟一个使用 `useLoaderData` API 的组件,Data Loader 中的请求需要消耗 2s 时间。
152
-
153
- ```tsx title="page.data.ts"
154
- export const loader = async () => {
155
- await new Promise((resolve, reject) => {
156
- setTimeout(() => {
157
- resolve(null);
158
- }, 2000);
159
- });
160
-
161
- return {
162
- message: 'Hello Modern.js',
163
- };
164
- };
165
- ```
166
-
167
- ```tsx title="page.tsx"
168
- import { useLoaderData } from '@modern-js/runtime/router';
169
-
170
- export default () => {
171
- const data = useLoaderData();
172
- return <div>{data?.message}</div>;
173
- };
174
- ```
175
-
176
- 执行 `dev` 命令后,打开页面,可以明显的察觉到页面需要等到 2s 后才返回。
177
-
178
- 接下来使用 `PreRender` 组件来进行优化,该组件可以直接从 `@modern-js/runtime/ssr` 中导出:
179
-
180
- ```ts
181
- import { PreRender } from '@modern-js/runtime/ssr';
182
- ```
183
-
184
- 在路由组件内使用 `PreRender` 组件,并设置参数 `interval`,用于表示该次渲染结果的过期时间为 5s:
185
-
186
- ```tsx
187
- <PreRender interval={5} />
188
- ```
189
-
190
- 修改后,执行 `pnpm run build && pnpm run serve` 启动应用,并打开页面。
191
-
192
- 首次打开时,和之前的渲染并没有什么不同,同样存在 2s 延迟。点击刷新,页面瞬间打开,但此时,页面数据并没有因为刷新发生变化,这是因为缓存还没有过期。
193
-
194
- 等待 5s,重新刷新页面,页面的数据仍然没有变化。再一次刷新页面数据发生变化,但是页面仍然几乎是瞬间响应的。
195
- 这是因为在之前的请求时,SPR 已经在后台异步获取了新的渲染结果,本次请求到的页面是已经缓存在服务器中的版本。
196
-
197
- 可以想象,当 `interval` 设置为 1 时,用户可以在感知到实时数据的同时,拥有静态页面的响应体验。
198
-
199
- :::info 补充信息
200
- `PreRender` 的详细使用可以参考[这里](/apis/app/runtime/ssr/pre-render)。
201
-
202
- :::
203
-
204
- ## Treeshaking
205
-
206
- 开启 SSR 时,Modern.js 会用相同的入口,构建出 SSR Bundle 和 CSR Bundle 两份产物。因此,在 SSR Bundle 中存在 Web API,或是在 CSR Bundle 中存在 Node API 时,都可能导致运行出错。
207
-
208
- 在组件中引入 Web API,通常情况下是要做一些全局监听,或是获取浏览器相关的数据,例如:
209
-
210
- ```tsx
211
- document.addEventListener('load', () => {
212
- console.log('document load');
213
- });
214
- const App = () => {
215
- return <div>Hello World</div>;
216
- };
217
- export default App;
218
- ```
219
-
220
- 在组件文件中引入 Node API,通常情况下是因为使用了 `useLoader`,例如:
221
-
222
- ```ts
223
- import fse from 'fs-extra';
224
- import { useLoader } from '@modern-js/runtime'
225
-
226
- const App = () => {
227
- const { data } = useLoader(async () => {
228
- const file = fse.readFileSync('./myfile');
229
- return {
230
- ...
231
- };
232
- })
233
-
234
- return <div>Hello World</div>;
235
- };
236
- export default App;
237
- ```
238
-
239
- ### 环境变量区分
240
-
241
- 对于第一种情况,我们可以直接使用 Modern.js 内置的环境变量 `MODERN_TARGET` 进行判断,在构建时删除无用代码:
242
-
243
- ```ts
244
- if (process.env.MODERN_TARGET === 'browser') {
245
- document.addEventListener('load', () => {
246
- console.log('document load');
247
- });
248
- }
249
- ```
250
-
251
- 开发环境打包后,SSR 产物和 CSR 产物会被编译成以下内容。因此 SSR 环境中不会再因为 Web API 报错:
252
-
253
- ```ts
254
- // SSR 产物
255
- if (false) {
256
- }
257
-
258
- // CSR 产物
259
- if (true) {
260
- document.addEventListener('load', () => {
261
- console.log('document load');
262
- });
263
- }
264
- ```
265
-
266
- :::note
267
- 更多内容可以查看[环境变量](/guides/basic-features/env-vars)。
268
- :::
269
-
270
- ### 文件后缀区分
271
-
272
- 但例如第二种情况,在代码中引入了 `fs-extra`,它内部有使用了 Node API 的副作用,如果直接引用到组件中,会造成 CSR 加载报错。
273
-
274
- 环境变量的方式并不能在这种情况下生效,Modern.js 也支持通过 `.node.` 后缀的文件来区分 SSR Bundle 和 CSR Bundle 产物的打包文件。
275
-
276
- 可以创建同名的 `.ts` 和 `.node.ts` 文件做一层代理:
277
-
278
- ```ts title="compat.ts"
279
- export const readFileSync: any = () => {};
280
- ```
281
-
282
- ```ts title="compat.node.ts"
283
- export { readFileSync } from 'fs-extra';
284
- ```
285
-
286
- 在文件中直接引入 `./compat`,此时 SSR 环境下会优先使用 `.node.ts` 后缀的文件,CSR 环境下会使用 `.ts` 后缀的文件。
287
-
288
- ```ts title="App.tsx"
289
- import { readFileSync } from './compat'
290
-
291
- export const loader = () => {
292
- const file = readFileSync('./myfile');
293
- return {
294
- ...
295
- };
296
- };
297
- ```
298
-
299
- ### 独立文件
300
-
301
- 上述两种方式,都会为开发者带来一些心智负担。在真实的业务中,我们发现大多数的 Node / Web 代码混用都出现在数据请求中。
302
-
303
- 因此,Modern.js 基于[嵌套路由](/guides/basic-features/routes)开发设计了[更简单的方案](/guides/basic-features/data/data-fetch)来分离 CSR 和 SSR 的代码。
304
-
305
- 我们可以通过独立文件来分离**数据请求**与**组件代码**。在 `routes/page.tsx` 中编写组件逻辑,在 `routes/page.data.ts` 中编写数据请求逻辑。
306
-
307
- ```ts title="routes/page.tsx"
308
- export default Page = () => {
309
- return <div>Hello World<div>
310
- }
311
- ```
312
-
313
- ```ts title="routes/page.data.tsx"
314
- import fse from 'fs-extra';
315
- export const loader = () => {
316
- const file = fse.readFileSync('./myfile');
317
- return {
318
- ...
319
- };
320
- }
321
- ```
322
-
323
- ## 接口请求
324
-
325
- 在 SSR 中发起接口请求时,开发者有时自己封装了同构的请求工具。部分接口需要传递用户 Cookie,开发者可以通过 [`useRuntimeContext`](/guides/basic-features/data/data-fetch#route-loader) API 获取到请求头来实现。
326
-
327
- 需要注意的是,此时获取到的是 HTML 请求的请求头,不一定适用于接口请求,因此**千万不能**透传所有请求头。并且,一些后端接口,或是通用网关,会根据请求头中的信息做校验,全量透传容易出现各种难以排查的问题,推荐**按需透传**。
328
-
329
- 如果实在需要透传所有请求头,请务必过滤 `host` 字段。