@modern-js/main-doc 2.0.0-beta.3 → 2.0.0-beta.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/en/docusaurus-plugin-content-docs/current/apis/app/commands/inspect.md +0 -4
  3. package/en/docusaurus-plugin-content-docs/current/components/init-app.md +42 -0
  4. package/en/docusaurus-plugin-content-docs/current/configure/app/server/routes.md +2 -4
  5. package/en/docusaurus-plugin-content-docs/current/configure/app/tools/esbuild.md +16 -39
  6. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/css-in-js.md +38 -0
  7. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/css-modules.md +86 -0
  8. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/less-sass.md +17 -0
  9. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/postcss.md +81 -0
  10. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/css/tailwindcss.md +95 -0
  11. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/data-fetch.md +66 -0
  12. package/en/docusaurus-plugin-content-docs/current/guides/basic-features/routes.md +270 -0
  13. package/en/docusaurus-plugin-content-docs/current/guides/concept/entries.md +116 -0
  14. package/en/docusaurus-plugin-content-docs/current/guides/concept/lifecycle.md +15 -0
  15. package/en/docusaurus-plugin-content-docs/current/guides/get-started/quick-start.md +162 -0
  16. package/en/docusaurus-plugin-content-docs/current/guides/get-started/upgrade.md +78 -0
  17. package/{zh/tutorials/first-app → en/docusaurus-plugin-content-docs/current/guides}/overview.md +4 -4
  18. package/en/docusaurus-plugin-content-docs/current/tutorials/foundations/introduction.md +1 -1
  19. package/en/docusaurus-plugin-content-docs/current.json +11 -11
  20. package/package.json +3 -3
  21. package/zh/apis/app/commands/inspect.md +0 -4
  22. package/zh/apis/app/commands/new.md +1 -1
  23. package/zh/apis/app/hooks/src/index_.md +6 -5
  24. package/zh/components/debug-app.md +18 -0
  25. package/zh/components/global-proxy.md +28 -0
  26. package/zh/components/init-app.md +44 -0
  27. package/zh/components/prerequisites.md +19 -0
  28. package/zh/configure/app/server/routes.md +2 -4
  29. package/zh/configure/app/tools/esbuild.md +16 -39
  30. package/zh/guides/advanced-features/bff/bff-proxy.md +1 -1
  31. package/zh/guides/advanced-features/compatibility.md +2 -38
  32. package/zh/guides/advanced-features/custom-app.md +15 -17
  33. package/zh/guides/advanced-features/ssg.md +6 -6
  34. package/zh/guides/advanced-features/ssr.md +94 -51
  35. package/zh/guides/advanced-features/testing.md +33 -1
  36. package/zh/guides/advanced-features/web-server.md +2 -2
  37. package/zh/guides/basic-features/css/tailwindcss.md +2 -6
  38. package/zh/guides/basic-features/html.md +182 -0
  39. package/zh/guides/basic-features/mock.md +3 -9
  40. package/zh/guides/basic-features/proxy.md +2 -27
  41. package/zh/guides/concept/entries.md +4 -5
  42. package/zh/guides/get-started/quick-start.md +6 -78
  43. package/zh/guides/get-started/upgrade.md +8 -8
  44. package/zh/guides/topic-detail/model/quick-start.md +1 -1
  45. package/zh/guides/topic-detail/model/test-model.md +2 -2
  46. package/zh/guides/topic-detail/monorepo/intro.md +1 -1
  47. package/zh/guides/troubleshooting/dependencies.md +0 -69
  48. package/zh/tutorials/first-app/_category_.json +1 -1
  49. package/zh/tutorials/first-app/c01-start.md +94 -0
  50. package/zh/tutorials/first-app/{c05-component/5.1-use-ui-library.md → c02-component.md} +13 -15
  51. package/zh/tutorials/first-app/c03-css.md +305 -0
  52. package/zh/tutorials/first-app/{c08-client-side-routing/8.1-code-based-routing.md → c04-routes.md} +52 -39
  53. package/zh/tutorials/first-app/c05-loader.md +82 -0
  54. package/zh/tutorials/first-app/c06-model.md +256 -0
  55. package/zh/tutorials/first-app/c07-container.md +268 -0
  56. package/zh/tutorials/first-app/c08-entries.md +134 -0
  57. package/zh/tutorials/foundations/introduction.md +1 -1
  58. package/en/docusaurus-plugin-content-docs/current/configure/app/output/enable-modern-mode.md +0 -34
  59. package/zh/apis/generator/overview.md +0 -32
  60. package/zh/configure/app/output/enable-modern-mode.md +0 -34
  61. package/zh/guides/topic-detail/monorepo/deploy.md +0 -43
  62. package/zh/tutorials/first-app/c01-getting-started/1.1-prerequisites.md +0 -25
  63. package/zh/tutorials/first-app/c01-getting-started/1.2-minimal-mwa.md +0 -118
  64. package/zh/tutorials/first-app/c01-getting-started/1.3-dev-command.md +0 -29
  65. package/zh/tutorials/first-app/c01-getting-started/1.4-enable-ssr.md +0 -47
  66. package/zh/tutorials/first-app/c01-getting-started/1.5-start-command.md +0 -18
  67. package/zh/tutorials/first-app/c01-getting-started/1.6-create-repo.md +0 -31
  68. package/zh/tutorials/first-app/c01-getting-started/_category_.json +0 -3
  69. package/zh/tutorials/first-app/c02-generator-and-studio/2.1-generator.md +0 -79
  70. package/zh/tutorials/first-app/c02-generator-and-studio/2.2-boilerplates.md +0 -34
  71. package/zh/tutorials/first-app/c02-generator-and-studio/2.3-configuration.md +0 -19
  72. package/zh/tutorials/first-app/c02-generator-and-studio/_category_.json +0 -3
  73. package/zh/tutorials/first-app/c03-ide/3.1-setting-up.md +0 -55
  74. package/zh/tutorials/first-app/c03-ide/3.2-hints-in-ide.md +0 -60
  75. package/zh/tutorials/first-app/c03-ide/3.3-autofix-in-ide.md +0 -11
  76. package/zh/tutorials/first-app/c03-ide/3.4-autofix-in-cli.md +0 -63
  77. package/zh/tutorials/first-app/c03-ide/_category_.json +0 -3
  78. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.1-use-es6-plus.md +0 -54
  79. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.2-use-typescript.md +0 -135
  80. package/zh/tutorials/first-app/c04-es6-plus-and-ts/4.3-compatibility.md +0 -67
  81. package/zh/tutorials/first-app/c04-es6-plus-and-ts/_category_.json +0 -3
  82. package/zh/tutorials/first-app/c05-component/5.2-use-standalone-component.md +0 -72
  83. package/zh/tutorials/first-app/c05-component/_category_.json +0 -3
  84. package/zh/tutorials/first-app/c06-css-and-component/6.1-css-in-js.md +0 -110
  85. package/zh/tutorials/first-app/c06-css-and-component/6.2-utility-class.md +0 -143
  86. package/zh/tutorials/first-app/c06-css-and-component/6.3-postcss.md +0 -84
  87. package/zh/tutorials/first-app/c06-css-and-component/6.4-design-system.md +0 -83
  88. package/zh/tutorials/first-app/c06-css-and-component/6.5-storybook.md +0 -77
  89. package/zh/tutorials/first-app/c06-css-and-component/6.6-testing.md +0 -104
  90. package/zh/tutorials/first-app/c06-css-and-component/_category_.json +0 -3
  91. package/zh/tutorials/first-app/c07-app-entry/7.1-intro.md +0 -69
  92. package/zh/tutorials/first-app/c07-app-entry/7.2-add-entry-in-cli.md +0 -100
  93. package/zh/tutorials/first-app/c07-app-entry/7.3-manage-entries-by-hand.md +0 -69
  94. package/zh/tutorials/first-app/c07-app-entry/_category_.json +0 -3
  95. package/zh/tutorials/first-app/c08-client-side-routing/_category_.json +0 -3
  96. package/zh/tutorials/first-app/c09-bff/9.1-serverless.md +0 -30
  97. package/zh/tutorials/first-app/c09-bff/9.2-enable-bff.md +0 -95
  98. package/zh/tutorials/first-app/c09-bff/9.3-fetch-bff.md +0 -131
  99. package/zh/tutorials/first-app/c09-bff/_category_.json +0 -3
  100. package/zh/tutorials/first-app/c10-model/10.1-application-architecture.md +0 -21
  101. package/zh/tutorials/first-app/c10-model/10.2-add-model.md +0 -185
  102. package/zh/tutorials/first-app/c10-model/10.3-use-model.md +0 -55
  103. package/zh/tutorials/first-app/c10-model/10.4-testing.md +0 -69
  104. package/zh/tutorials/first-app/c10-model/_category_.json +0 -3
  105. package/zh/tutorials/first-app/c11-container/11.1-use-model-with-app-state.md +0 -240
  106. package/zh/tutorials/first-app/c11-container/11.2-add-container.md +0 -109
  107. package/zh/tutorials/first-app/c11-container/11.3-use-loader.md +0 -63
  108. package/zh/tutorials/first-app/c11-container/11.4-testing.md +0 -56
  109. package/zh/tutorials/first-app/c11-container/_category_.json +0 -3
@@ -0,0 +1,256 @@
1
+ ---
2
+ title: 添加业务模型(状态管理)
3
+ ---
4
+
5
+ 上一章节中,我们把硬编码的 `mockData` 改成从 Data Loader 中加载。
6
+
7
+ 这一章节中,我们会进一步实现项目的功能,例如实现 **Archive** 按钮的功能,把联系人归档。
8
+
9
+ 因此会开始编写一些跟 UI 完全无关的业务逻辑,如果继续写在组件代码中,会产生越来越多的面条式代码。为此,我们引入了一种叫做**业务模型(Model)**的代码模块,将这些业务逻辑和 UI 解耦。
10
+
11
+ ## 实现 Model
12
+
13
+ 创建一个完整的 Model 首先需要定义**状态(state)**,包括状态中数据的名称和初始值。
14
+
15
+ 我们使用 Model 来管理联系人列表的数据,因此定义如下数据状态:
16
+
17
+ ```js
18
+ const state = {
19
+ items: [],
20
+ };
21
+ ```
22
+
23
+ 使用 TS 语法,可以定义更完整的类型信息,比如 items 里每个对象都应该有 `name`、`email` 字段。为了实现归档功能,还需要创建 `archived` 字段保存这个联系人是否已被归档的状态。
24
+
25
+ 我们还需要一个字段用来访问所有已归档的联系人,可以定义 **computed** 类型的字段,对已有的数据做转换:
26
+
27
+ ```js
28
+ const computed = {
29
+ archived: ({ items }) => {
30
+ return items.filter(item => item.archived);
31
+ },
32
+ };
33
+ ```
34
+
35
+ computed 类型字段的定义方式是函数,但使用时可以像普通字段一样通过 state 访问。
36
+
37
+ :::info
38
+ Modern.js 集成了 [Immer](https://immerjs.github.io/immer/),能够像操作 JS 中常规的可变数据一样,去写这种状态转移的逻辑。
39
+ :::
40
+
41
+ 实现 Archive 按钮时,我们需要一个 `archive` 函数,负责修改指定联系人的 `archived` 字段,我们把这种函数都叫作 **action**:
42
+
43
+ ```js
44
+ const actions = {
45
+ archive(draft, payload) {
46
+ const target = draft.items.find(item => item.email === payload);
47
+ if (target) {
48
+ target.archived = true;
49
+ }
50
+ },
51
+ };
52
+ ```
53
+
54
+ action 函数是一种**纯函数**,确定的输入得到确定的输出(转移后的状态),不应该有任何副作用。
55
+
56
+ 函数的第一个参数是 Immer 提供的 Draft State,第二个参数是 action 被调用时传入的参数(后面会介绍怎么调用)。
57
+
58
+ 我们尝试完整实现它们:
59
+
60
+ ```js
61
+ const state = {
62
+ items: [],
63
+ pending: false,
64
+ error: null,
65
+ };
66
+
67
+ const computed = {
68
+ archived: ({ items }) => {
69
+ return items.filter(item => item.archived);
70
+ },
71
+ };
72
+
73
+ const actions = {
74
+ archive(draft, payload) {
75
+ const target = draft.items.find(item => item.email === payload);
76
+ if (target) {
77
+ target.archived = true;
78
+ }
79
+ },
80
+ };
81
+ ```
82
+
83
+ 接下来我们把上面的代码连起来,放在同一个 Model 文件里。首先执行以下命令,创建新的文件目录:
84
+
85
+ ```bash
86
+ mkdir -p src/models/
87
+ touch src/models/contacts.ts
88
+ ```
89
+
90
+ 添加 `src/models/contacts.ts` 的内容:
91
+
92
+ ```tsx
93
+ import { model } from "@modern-js/runtime/model";
94
+
95
+ type State = {
96
+ items: {
97
+ avatar: string;
98
+ name: string;
99
+ email: string;
100
+ archived?: boolean;
101
+ }[];
102
+ pending: boolean;
103
+ error: null | Error;
104
+ };
105
+
106
+ export default model<State>("contacts").define({
107
+ state: {
108
+ items: [],
109
+ pending: false,
110
+ error: null,
111
+ },
112
+ computed: {
113
+ archived: ({ items }: State) => items.filter((item) => item.archived),
114
+ },
115
+ actions: {
116
+ archive(draft, payload) {
117
+ const target = draft.items.find((item) => item.email === payload)!;
118
+ if (target) {
119
+ target.archived = true;
120
+ }
121
+ },
122
+ },
123
+ });
124
+ ```
125
+
126
+ 我们把一个包含 state,action 等要素的 plain object 称作 **Model Spec**,Modern.js 提供了 [Model API](/docs/apis/app/runtime/model/model_),可以根据 Model Spec 生成 **Model**。
127
+
128
+ ## 使用 Model
129
+
130
+ 现在我们直接使用这个 Model,把项目的逻辑补充起来。
131
+
132
+ 首先修改 `src/components/Item/index.tsx`,添加 **Archive 按钮**的 UI 和交互,内容如下:
133
+
134
+ ```tsx
135
+ import Avatar from "../Avatar";
136
+
137
+ type InfoProps = {
138
+ avatar: string;
139
+ name: string;
140
+ email: string;
141
+ archived?: boolean;
142
+ };
143
+
144
+ const Item = ({
145
+ info,
146
+ onArchive,
147
+ }: {
148
+ info: InfoProps;
149
+ onArchive?: () => void;
150
+ }) => {
151
+ const { avatar, name, email, archived } = info;
152
+ return (
153
+ <div className="flex p-4 items-center border-gray-200 border-b">
154
+ <Avatar src={avatar} />
155
+ <div className="ml-4 custom-text-gray flex-1 flex justify-between">
156
+ <div className="flex-1">
157
+ <p>{name}</p>
158
+ <p>{email}</p>
159
+ </div>
160
+ <button
161
+ type="button"
162
+ disabled={archived}
163
+ onClick={onArchive}
164
+ className={`text-white font-bold py-2 px-4 rounded-full ${
165
+ archived
166
+ ? "bg-gray-400 cursor-default"
167
+ : "bg-blue-500 hover:bg-blue-700"
168
+ }`}
169
+ >
170
+ {archived ? "Archived" : "Archive"}
171
+ </button>
172
+ </div>
173
+ </div>
174
+ );
175
+ };
176
+
177
+ export default Item;
178
+ ```
179
+
180
+ 接下来,我们修改 `src/routes/page.tsx`,为 `<Item>` 组件传递更多参数:
181
+
182
+ ```tsx
183
+ import { Helmet } from "@modern-js/runtime/head";
184
+ import { useModel } from "@modern-js/runtime/model";
185
+ import { useLoaderData } from "@modern-js/runtime/router";
186
+ import { List } from "antd";
187
+ import { name, internet } from "faker";
188
+ import Item from "../components/Item";
189
+ import contacts from "../models/contacts";
190
+
191
+ type LoaderData = {
192
+ code: number;
193
+ data: {
194
+ name: string;
195
+ avatar: string;
196
+ email: string;
197
+ }[];
198
+ };
199
+
200
+ export const loader = async (): Promise<LoaderData> => {
201
+ const data = new Array(20).fill(0).map(() => {
202
+ const firstName = name.firstName();
203
+ return {
204
+ name: firstName,
205
+ avatar: `https://avatars.dicebear.com/api/identicon/${firstName}.svg`,
206
+ email: internet.email(),
207
+ archived: false,
208
+ };
209
+ });
210
+
211
+ return {
212
+ code: 200,
213
+ data,
214
+ };
215
+ };
216
+
217
+ function Index() {
218
+ const { data } = useLoaderData() as LoaderData;
219
+ const [{ items }, { archive, setItem }] = useModel(contacts);
220
+ if (items.length === 0) {
221
+ setItem(data);
222
+ }
223
+
224
+ return (
225
+ <div className="container lg mx-auto">
226
+ <Helmet>
227
+ <title>All</title>
228
+ </Helmet>
229
+ <List
230
+ dataSource={items}
231
+ renderItem={(info) => (
232
+ <Item
233
+ key={info.name}
234
+ info={info}
235
+ onArchive={() => {
236
+ archive(info.email);
237
+ }}
238
+ />
239
+ )}
240
+ />
241
+ </div>
242
+ );
243
+ }
244
+
245
+ export default Index;
246
+ ```
247
+
248
+ `useModel` 是 Modern.js 提供的 hooks API。可以在组件中提供 Model 中定义的 state,或通过 actions 调用 Model 中定义的 side effect 与 action,从而改变 Model 的 state。
249
+
250
+ Model 是业务逻辑,是计算过程,本身不创建也不持有状态。只有在被组件用 hooks API 使用后,才在指定的地方创建状态。
251
+
252
+ 执行 `pnpm run dev`,点击 **Archive 按钮**,可以看到页面 UI 发生了变化。
253
+
254
+ :::note
255
+ 上述例子中,`useLoaderData` 其实在每次切换路由时都会执行。因为我们在 Data Loader 里使用了 fake 数据,每次返回的数据是不同的。但我们优先使用了 Model 中的数据,因此切换路由时数据没有发生改变。
256
+ :::
@@ -0,0 +1,268 @@
1
+ ---
2
+ title: 添加容器组件
3
+ ---
4
+
5
+ import Tabs from '@theme/Tabs';
6
+ import TabItem from '@theme/TabItem';
7
+
8
+ 上一章节中,我们初步引入**业务模型**,从 UI 组件中拆分出这部分逻辑。`page.tsx` 中不再包含 UI 无关的业务逻辑实现细节,只需要使用 Model,就能实现同样的功能。
9
+
10
+ 这一章节中,我们要进一步利用 Model 中实现的业务逻辑,让 `page.tsx` 和 `archived/page.tsx` 获取同一份数据。并实现 Archive 按钮,点击按钮能把联系人归档,只显示在 Archives 列表里,不显示在 All 列表里。
11
+
12
+ ## 使用完整 Model
13
+
14
+ 因为两个页面需要共用同一套状态(联系人列表数据、联系人是否被归档),都需要包含加载初始数据的逻辑,因此我们需要在更上一层完成数据获取。
15
+
16
+ Modern.js 支持在 `layout.tsx` 通过 Data Loader 获取数据,我们先数据获取这部分代码移动到 `src/routes/layout.tsx` 中:
17
+
18
+ ```tsx
19
+ import { name, internet } from "faker";
20
+ import { useModel } from "@modern-js/runtime/model";
21
+ import contacts from "../models/contacts";
22
+
23
+ type LoaderData = {
24
+ code: number;
25
+ data: {
26
+ name: string;
27
+ avatar: string;
28
+ email: string;
29
+ }[];
30
+ };
31
+
32
+ export const loader = async (): Promise<LoaderData> => {
33
+ const data = new Array(20).fill(0).map(() => {
34
+ const firstName = name.firstName();
35
+ return {
36
+ name: firstName,
37
+ avatar: `https://avatars.dicebear.com/api/identicon/${firstName}.svg`,
38
+ email: internet.email(),
39
+ archived: false,
40
+ };
41
+ });
42
+
43
+ return {
44
+ code: 200,
45
+ data,
46
+ };
47
+ };
48
+
49
+ export default function Layout() {
50
+ const { data } = useLoaderData() as LoaderData;
51
+ const [{ items }, { setItem }] = useModel(contacts);
52
+ if (items.length === 0) {
53
+ setItem(data);
54
+ }
55
+
56
+ const navigate = useNavigate();
57
+ ...
58
+ }
59
+ ```
60
+
61
+ 在 `src/routes/page.tsx` 中,直接使用 Model,获取数据:
62
+
63
+ ```tsx
64
+ import { Helmet } from "@modern-js/runtime/head";
65
+ import { useModel } from "@modern-js/runtime/model";
66
+ import { List } from "antd";
67
+ import Item from "../components/Item";
68
+ import contacts from "../models/contacts";
69
+
70
+ function Index() {
71
+ const [{ items }, { archive }] = useModel(contacts);
72
+
73
+ return (
74
+ <div className="container lg mx-auto">
75
+ <Helmet>
76
+ <title>All</title>
77
+ </Helmet>
78
+ <List
79
+ dataSource={items}
80
+ renderItem={(info) => (
81
+ <Item
82
+ key={info.name}
83
+ info={info}
84
+ onArchive={() => {
85
+ archive(info.email);
86
+ }}
87
+ />
88
+ )}
89
+ />
90
+ </div>
91
+ );
92
+ }
93
+
94
+ export default Index;
95
+ ```
96
+
97
+ 同样在 `archived/page.tsx` 中,删除原本的 `mockData` 逻辑,使用 Model 中 computed 的 `archived` 值作为数据源:
98
+
99
+ ```tsx
100
+ import { Helmet } from "@modern-js/runtime/head";
101
+ import { useModel } from "@modern-js/runtime/model";
102
+ import { List } from "antd";
103
+ import Item from "../../components/Item";
104
+ import contacts from "../../models/contacts";
105
+
106
+ function Index() {
107
+ const [{ archived }, { archive }] = useModel(contacts);
108
+
109
+ return (
110
+ <div className="container lg mx-auto">
111
+ <Helmet>
112
+ <title>Archives</title>
113
+ </Helmet>
114
+ <List
115
+ dataSource={archived}
116
+ renderItem={(info) => (
117
+ <Item
118
+ key={info.name}
119
+ info={info}
120
+ onArchive={() => {
121
+ archive(info.email);
122
+ }}
123
+ />
124
+ )}
125
+ />
126
+ </div>
127
+ );
128
+ }
129
+
130
+ export default Index;
131
+ ```
132
+
133
+ 执行 `pnpm run dev`,访问 `http://localhost:8080/`,点击 Archive 按钮后,可以看到按钮置灰:
134
+
135
+ ![display](https://lf3-static.bytednsdoc.com/obj/eden-cn/nuvjhpqnuvr/modern-website/tutorials/c07-contacts-all.png)
136
+
137
+ 接下来点击顶部导航,切换到 Archives 列表,可以发现刚才 **Archive** 的联系人已经出现在列表当中:
138
+
139
+ ![display](https://lf3-static.bytednsdoc.com/obj/eden-cn/nuvjhpqnuvr/modern-website/tutorials/c07-contacts-archives.png)
140
+
141
+ ## 抽离容器组件
142
+
143
+ 前面章节中,我们把项目中的业务逻辑拆分成了两个 layer,一个是**视图组件**,另一个是**业务模块**。前者负责 UI 展示、交互等,后者负责实现 UI 无关的业务逻辑,专门管理状态。
144
+
145
+ 像 `src/routes/page.tsx` 和 `src/routes/archives/page.tsx` 这样使用了 `useModel` API 的组件,负责把 View 和 Model 这两个 layer 连接起来,类似传统 MVC 架构中 Controller 的角色,在 Modern.js 里我们沿用习惯,把它们称作**容器组件(Container)**。
146
+
147
+ 容器组件推荐放在专门的 `containers/` 目录里,我们执行以下命令,创建新的文件:
148
+
149
+ <Tabs>
150
+ <TabItem value="macOS" label="macOS" default>
151
+
152
+ ```bash
153
+ mkdir -p src/containers
154
+ touch src/containers/Contacts.tsx
155
+ ```
156
+
157
+ </TabItem>
158
+ <TabItem value="Windows" label="Windows">
159
+
160
+ ```powershell
161
+ mkdir -p src/containers
162
+ ni src/containers/Contacts.tsx
163
+ ```
164
+
165
+ </TabItem>
166
+ </Tabs>
167
+
168
+ 我们将原本两个 `page.tsx` 中公共的部分抽离出来,`src/containers/Contacts.tsx` 的代码如下:
169
+
170
+ ```tsx
171
+ import { Helmet } from "@modern-js/runtime/head";
172
+ import { useModel } from "@modern-js/runtime/model";
173
+ import { List } from "antd";
174
+ import Item from "../components/Item";
175
+ import contacts from "../models/contacts";
176
+
177
+ function Contacts({
178
+ title,
179
+ source,
180
+ }: {
181
+ title: string;
182
+ source: "items" | "archived";
183
+ }) {
184
+ const [state, { archive }] = useModel(contacts);
185
+
186
+ return (
187
+ <div className="container lg mx-auto">
188
+ <Helmet>
189
+ <title>{title}</title>
190
+ </Helmet>
191
+ <List
192
+ dataSource={state[source]}
193
+ renderItem={(info) => (
194
+ <Item
195
+ key={info.name}
196
+ info={info}
197
+ onArchive={() => {
198
+ archive(info.email);
199
+ }}
200
+ />
201
+ )}
202
+ />
203
+ </div>
204
+ );
205
+ }
206
+
207
+ export default Contacts;
208
+ ```
209
+
210
+ 修改 `src/routes/page.tsx` 和 `src/routes/archives/page.tsx` 的代码:
211
+
212
+ ```tsx title="src/routes/page.tsx"
213
+ import Contacts from "../containers/Contacts";
214
+
215
+ function Index() {
216
+ return <Contacts title="All" source="items" />;
217
+ }
218
+
219
+ export default Index;
220
+ ```
221
+
222
+ ```tsx title="src/routes/archives/page.tsx"
223
+ import Contacts from "../../containers/Contacts";
224
+
225
+ function Index() {
226
+ return <Contacts title="Archives" source="archived" />;
227
+ }
228
+
229
+ export default Index;
230
+ ```
231
+
232
+ 重构完成,现在的项目结构是:
233
+
234
+ ```md
235
+ .
236
+ ├── README.md
237
+ ├── dist
238
+ ├── modern.config.ts
239
+ ├── node_modules
240
+ ├── package.json
241
+ ├── pnpm-lock.yaml
242
+ ├── src
243
+ │   ├── components
244
+ │   │   ├── Avatar
245
+ │   │   │   └── index.tsx
246
+ │   │   └── Item
247
+ │   │   └── index.tsx
248
+ │   ├── containers
249
+ │   │   └── Contacts.tsx
250
+ │   ├── models
251
+ │   │   └── contacts.ts
252
+ │   ├── modern-app-env.d.ts
253
+ │   ├── routes
254
+ │   │   ├── archives
255
+ │   │   │   └── page.tsx
256
+ │   │   ├── layout.tsx
257
+ │   │   └── page.tsx
258
+ │   └── styles
259
+ │   └── utils.css
260
+ └── tsconfig.json
261
+ ```
262
+
263
+ `components/` 里的**视图组件**,都是目录形式,如 `Avatar/index.tsx`。而 `containers/` 里的**容器组件**,都是单文件形式,如 `contacts.tsx`。**这是我们推荐的一种最佳实践**。
264
+
265
+ 在​ [添加 UI 组件(Component)](./c02-component.md) 章节提到过,视图组件用目录形式,是因为视图组件负责实现 UI 展示和交互细节,可以演变的复杂。用目录形式,可以方便增加子文件,包括专用的资源(图片等)、专用子组件、CSS 文件等。在这个目录内部可以随意重构,只考虑最小局部。
266
+
267
+ 而容器组件只负责连接,是一个胶水层,复杂的业务逻辑和实现细节都交给 View 层和 Model 层去实现。容器组件自己应该保持简单清晰,不应该包含复杂实现细节,所以不应该有内部结构。采用单文件形式不但更简洁,也能起到约束作用,提醒开发者不要把容器组件写复杂。
268
+
@@ -0,0 +1,134 @@
1
+ ---
2
+ title: 添加应用入口
3
+ ---
4
+
5
+ 上一个章节中,我们基本完成了联系人列表应用的开发,介绍了 Modern.js 中部分功能的用法,以及推荐的最佳实践。
6
+
7
+ 这一章节中,我们将介绍如何为应用添加新的入口。
8
+
9
+ ## 新建入口
10
+
11
+ 一个完整的项目可能需要多个入口,Modern.js 支持自动创建新入口,前面的章节中提到过,`pnpm run new` 可以启用可选功能。
12
+
13
+ 我们也可以通过它来创建新的工程元素,在项目根目录下执行 `pnpm run new`:
14
+
15
+ ```bash
16
+ ? 请选择你想要的操作 创建工程元素
17
+ ? 创建工程元素 新建「应用入口」
18
+ ? 请填写入口名称 (entry) landing-page
19
+ ```
20
+
21
+ 创建完成,项目会变成这样:
22
+
23
+ ```md
24
+ .
25
+ ├── README.md
26
+ ├── modern.config.ts
27
+ ├── node_modules
28
+ ├── package.json
29
+ ├── pnpm-lock.yaml
30
+ ├── src
31
+ │   ├── modern-app-env.d.ts
32
+ │   ├── landing-page
33
+ │   │   └── routes
34
+ │   │   ├── index.css
35
+ │   │   ├── layout.tsx
36
+ │   │   └── page.tsx
37
+ │   └── myapp
38
+ │   ├── components
39
+ │   │   ├── Avatar
40
+ │   │   │   └── index.tsx
41
+ │   │   └── Item
42
+ │   │   └── index.tsx
43
+ │   ├── containers
44
+ │   │   └── Contacts.tsx
45
+ │   ├── models
46
+ │   │   └── contacts.ts
47
+ │   ├── routes
48
+ │   │   ├── archives
49
+ │   │   │   └── page.tsx
50
+ │   │   ├── layout.tsx
51
+ │   │   └── page.tsx
52
+ │   └── styles
53
+ │   └── utils.css
54
+ └── tsconfig.json
55
+
56
+ ```
57
+
58
+ 可以看到联系人列表应用的文件,都被自动重构到 `src/myapp/` 里。
59
+
60
+ 同时新建了一个 `src/landing-page/`,里面同样有 `routes/*`(`pnpm run new` 命令只做了这些事,所以你也可以很容易的手动创建新入口或修改入口)
61
+
62
+ 执行 `pnpm run dev`,显示:
63
+
64
+ ![design](https://lf3-static.bytednsdoc.com/obj/eden-cn/nuvjhpqnuvr/modern-website/tutorials/c08-entries-myapp.png)
65
+
66
+ 访问 `http://localhost:8080/`,可以像之前一样看到应用程序。
67
+
68
+ 访问 `http://localhost:8080/landing-page`,可以看到刚创建的新入口 `landing-page` 的页面(Modern.js 自动生成的默认页面)。
69
+
70
+ Modern.js 框架的设计原则之一是【[约定优于配置(Convention over Configuration)](https://en.wikipedia.org/wiki/Convention_over_configuration)】,多数情况下可以按约定直接写代码,不需要做任何配置,这里 `src/` 中的目录结构就是一种约定:
71
+
72
+ `src/myapp/` 和 `src/landing-page/` 被自动识别为两个应用入口:myapp 和 landing-page。
73
+
74
+ 其中 `src/myapp/` 的目录名跟项目名(`package.json` 里的 `name`)一致,会被认为是项目**主入口**,项目 URL 的根路径(开发环境里默认是 `http://localhost:8080/`)会自动指向主入口。
75
+
76
+ 其他入口的 URL,是在根路径后追加入口名,比如 `http://localhost:8080/landing-page`。
77
+
78
+ 接下来,我们把 `src/myapp/` 重命名为 `src/contacts/`:
79
+
80
+ ```bash
81
+ mv src/myapp src/contacts
82
+ ```
83
+
84
+ 再次执行 `pnpm run dev`,结果变成:
85
+
86
+ ![design](https://lf3-static.bytednsdoc.com/obj/eden-cn/nuvjhpqnuvr/modern-website/tutorials/c08-entries-contacts.png)
87
+
88
+ 现在不再有主入口,联系人列表现在是一个普通入口,需要用 `http://localhost:8080/contacts` 访问。
89
+
90
+
91
+ ## 按入口修改配置
92
+
93
+ 我们可以在 Modern.js 配置文件里,自己写代码来控制项目的配置。
94
+
95
+ 现在,修改 `modern.config.ts` 里面添加内容:
96
+
97
+ ```typescript
98
+ import { defineConfig } from '@modern-js/app-tools';
99
+
100
+ export default defineConfig({
101
+ runtime: {
102
+ router: true,
103
+ state: true,
104
+ },
105
+ server: {
106
+ ssr: true,
107
+ ssrByEntries: {
108
+ 'landing-page': false,
109
+ },
110
+ },
111
+ });
112
+ ```
113
+
114
+ 执行 `pnpm run dev`,再用浏览器打开 `view-source:http://localhost:8080/landing-page`,可以看到 `landing-page` 网页内容是通过 js 动态加载的,且此页面的 SSR 功能被关闭。
115
+
116
+ 如果注释掉 `ssrByEntries` 和它的值,landing-page 的 SSR 功能就恢复开启了。
117
+
118
+ 还有一些时候,需要一些更复杂的逻辑来做设置,比如需要 JS 变量、表达式、导入模块等,例如在只在开发环境里开启 SSR:
119
+
120
+ ```js
121
+ export default defineConfig({
122
+ server: {
123
+ ssrByEntries: {
124
+ 'landing-page': process.env.NODE_ENV !== 'production',
125
+ },
126
+ },
127
+ };
128
+ ```
129
+
130
+ 到底为止,我们的联系人列表应用的雏形就基本完成了 👏👏👏。
131
+
132
+ ## 下一步
133
+
134
+ 接下来你可以通过了解[指南](/docs/guides/overview)、[配置](/docs/configure/app/usage) 等更多教程,进一步完善你的应用。
@@ -38,4 +38,4 @@ Modern.js 能为开发者提供极致的**开发体验(Development Experience
38
38
 
39
39
  如果你是前端初学者,可能会觉得这些概念有些复杂。我们提供了一些 [JavaScript 和 React](/docs/tutorials/foundations/basic) 的学习资料,你可以先对它们做些了解。
40
40
 
41
- 如果你是有经验的开发者,希望了解如何使用 Modern.js,你可以尝试[创建第一个应用](/docs/tutorials/first-app/overview),或是阅读[指南](/docs/guides/overview)。
41
+ 如果你是有经验的开发者,希望了解如何使用 Modern.js,你可以尝试[创建第一个应用](/docs/tutorials/first-app/c01-start),或是阅读[指南](/docs/guides/overview)。
@@ -1,34 +0,0 @@
1
- ---
2
- sidebar_label: enableModernMode
3
- ---
4
-
5
- # output.enableModernMode
6
-
7
- * Type: `boolean`
8
- * Default: `false`
9
-
10
- Modern.js default value to build JS products with Polyfill for older browsers. After opening this configuration, you can automatically build JS products that are not degraded for modern browser syntax in the production environment. The product filename format is `[name].[hash]-es6/js`.
11
-
12
- For example, configure the following configuration:
13
-
14
- ```ts title="modern.config.ts"
15
- import { defineConfig } from '@modern-js/app-tools';
16
-
17
- export default defineConfig({
18
- output: {
19
- enableModernMode: true,
20
- },
21
- });
22
- ```
23
-
24
- After executing the `build` command, modern is packaged in addition to the normal Client packaging, and the `dist/static/js` directory will generate es6 related products.
25
-
26
- ![](https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/output-enable-modern-build.jpeg)
27
-
28
- After executing the `start` command, use the latest version Chrome browser access, and observe that the requested JS resource in the Network is an es6 product.
29
-
30
- ![](https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/output-enable-modern-network.jpeg)
31
-
32
- :::info
33
- For more information, see [Client side compatibility](/docs/guides/advanced-features/compatibility)。
34
- :::