@modern-js/main-doc 2.60.5 → 2.61.0
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/en/apis/app/runtime/core/use-runtime-context.mdx +86 -33
- package/docs/en/components/bff-upload.mdx +95 -0
- package/docs/en/components/init-app.mdx +0 -1
- package/docs/en/components/init-rspack-app.mdx +0 -1
- package/docs/en/configure/app/dev/https.mdx +3 -0
- package/docs/en/configure/app/tools/html-plugin.mdx +13 -0
- package/docs/en/configure/app/tools/terser.mdx +1 -1
- package/docs/en/configure/app/tools/ts-loader.mdx +6 -3
- package/docs/en/configure/app/tools/webpack-chain.mdx +3 -3
- package/docs/en/guides/advanced-features/bff/_meta.json +1 -1
- package/docs/en/guides/advanced-features/bff/upload.mdx +5 -0
- package/docs/en/guides/advanced-features/source-build.mdx +11 -4
- package/docs/en/guides/basic-features/deploy.mdx +104 -2
- package/docs/en/guides/concept/entries.mdx +58 -5
- package/docs/en/guides/get-started/introduction.mdx +1 -38
- package/docs/zh/apis/app/runtime/core/use-runtime-context.mdx +86 -33
- package/docs/zh/components/bff-upload.mdx +97 -0
- package/docs/zh/components/default-mwa-generate.mdx +0 -1
- package/docs/zh/components/init-app.mdx +0 -1
- package/docs/zh/components/init-rspack-app.mdx +0 -1
- package/docs/zh/configure/app/dev/https.mdx +3 -0
- package/docs/zh/configure/app/tools/html-plugin.mdx +13 -0
- package/docs/zh/configure/app/tools/terser.mdx +1 -1
- package/docs/zh/configure/app/tools/ts-loader.mdx +6 -3
- package/docs/zh/configure/app/tools/webpack-chain.mdx +3 -3
- package/docs/zh/guides/advanced-features/bff/_meta.json +1 -1
- package/docs/zh/guides/advanced-features/bff/upload.mdx +5 -0
- package/docs/zh/guides/advanced-features/source-build.mdx +11 -4
- package/docs/zh/guides/basic-features/deploy.mdx +103 -5
- package/docs/zh/guides/concept/entries.mdx +52 -4
- package/docs/zh/guides/get-started/introduction.mdx +1 -36
- package/package.json +4 -4
- package/src/i18n/enUS.ts +0 -7
- package/src/i18n/zhCN.ts +0 -7
- package/src/pages/index.tsx +3 -32
- package/docs/en/guides/topic-detail/generator/_meta.json +0 -17
- package/docs/en/guides/topic-detail/generator/create/_meta.json +0 -1
- package/docs/en/guides/topic-detail/generator/create/config.mdx +0 -59
- package/docs/en/guides/topic-detail/generator/create/option.md +0 -146
- package/docs/en/guides/topic-detail/generator/create/use.mdx +0 -48
- package/docs/en/guides/topic-detail/generator/new/_meta.json +0 -1
- package/docs/en/guides/topic-detail/generator/new/config.md +0 -115
- package/docs/en/guides/topic-detail/generator/new/option.md +0 -67
- package/docs/en/guides/topic-detail/generator/new/use.md +0 -75
- package/docs/en/guides/topic-detail/generator/plugin/_meta.json +0 -11
- package/docs/en/guides/topic-detail/generator/plugin/api/afterForged.md +0 -49
- package/docs/en/guides/topic-detail/generator/plugin/api/context.md +0 -184
- package/docs/en/guides/topic-detail/generator/plugin/api/input.md +0 -124
- package/docs/en/guides/topic-detail/generator/plugin/api/onForged.md +0 -310
- package/docs/en/guides/topic-detail/generator/plugin/category.md +0 -88
- package/docs/en/guides/topic-detail/generator/plugin/context.md +0 -104
- package/docs/en/guides/topic-detail/generator/plugin/structure.md +0 -106
- package/docs/en/guides/topic-detail/generator/plugin/use.md +0 -33
- package/docs/zh/guides/topic-detail/generator/_meta.json +0 -17
- package/docs/zh/guides/topic-detail/generator/create/_meta.json +0 -1
- package/docs/zh/guides/topic-detail/generator/create/config.mdx +0 -60
- package/docs/zh/guides/topic-detail/generator/create/option.md +0 -146
- package/docs/zh/guides/topic-detail/generator/create/use.mdx +0 -48
- package/docs/zh/guides/topic-detail/generator/new/_meta.json +0 -1
- package/docs/zh/guides/topic-detail/generator/new/config.md +0 -116
- package/docs/zh/guides/topic-detail/generator/new/option.md +0 -67
- package/docs/zh/guides/topic-detail/generator/new/use.md +0 -74
- package/docs/zh/guides/topic-detail/generator/plugin/_meta.json +0 -11
- package/docs/zh/guides/topic-detail/generator/plugin/api/afterForged.md +0 -50
- package/docs/zh/guides/topic-detail/generator/plugin/api/context.md +0 -184
- package/docs/zh/guides/topic-detail/generator/plugin/api/input.md +0 -124
- package/docs/zh/guides/topic-detail/generator/plugin/api/onForged.md +0 -310
- package/docs/zh/guides/topic-detail/generator/plugin/category.md +0 -93
- package/docs/zh/guides/topic-detail/generator/plugin/context.md +0 -105
- package/docs/zh/guides/topic-detail/generator/plugin/structure.md +0 -106
- package/docs/zh/guides/topic-detail/generator/plugin/use.md +0 -33
@@ -1,6 +1,3 @@
|
|
1
|
-
---
|
2
|
-
title: useRuntimeContext
|
3
|
-
---
|
4
1
|
# useRuntimeContext
|
5
2
|
|
6
3
|
该函数主要用于获取 Runtime 上下文,只能在函数组件中使用。
|
@@ -12,7 +9,7 @@ import { useRuntimeContext } from '@modern-js/runtime';
|
|
12
9
|
|
13
10
|
export function App() {
|
14
11
|
const runtimeContext = useRuntimeContext();
|
15
|
-
return <div>Hello World</div
|
12
|
+
return <div>Hello World</div>
|
16
13
|
}
|
17
14
|
```
|
18
15
|
|
@@ -20,44 +17,100 @@ export function App() {
|
|
20
17
|
|
21
18
|
```ts
|
22
19
|
type RuntimeContext = {
|
23
|
-
|
24
|
-
params: Record<string, string>;
|
25
|
-
pathname: string;
|
26
|
-
query: Record<string, string>;
|
27
|
-
headers: IncomingHttpHeaders;
|
28
|
-
cookie: string;
|
29
|
-
};
|
30
|
-
store: ReduckStore;
|
31
|
-
router: RemixRouter;
|
20
|
+
context: RequestContext;
|
32
21
|
};
|
33
|
-
|
34
|
-
function useRuntimeContext(): RuntimeContext;
|
35
22
|
```
|
36
23
|
|
37
|
-
###
|
24
|
+
### context
|
38
25
|
|
39
|
-
|
40
|
-
- `params`:请求路径中的动态参数。
|
41
|
-
- `pathname`:请求的 pathname。
|
42
|
-
- `query`:请求的查询字符串对象。
|
43
|
-
- `headers`:请求头信息。
|
44
|
-
- `cookie`:请求的 cookie 信息。
|
45
|
-
- `store`:在开启了 state 插件的时候,该值为 Reduck 全局 `store`。
|
46
|
-
- `router`:在开启 router 插件的时候存在。
|
47
|
-
- `location`:当前路由对应的位置信息。同 [`useLocation`](/apis/app/runtime/router/router.html#uselocation) 返回值。
|
48
|
-
- `navigate`:导航到给定路径。同 [`useNavigate`](/apis/app/runtime/router/router.html#usenavigate) 返回值。
|
26
|
+
用于获取[请求上下文](#请求上下文)。
|
49
27
|
|
50
|
-
##
|
28
|
+
## 使用示例
|
51
29
|
|
52
|
-
|
53
|
-
import { useRuntimeContext } from '@modern-js/runtime';
|
54
|
-
import { fooModel } from '@/common/models';
|
30
|
+
### 区分运行环境
|
55
31
|
|
32
|
+
```ts
|
56
33
|
function App() {
|
57
|
-
const {
|
34
|
+
const { context } = useRuntimeContext();
|
35
|
+
|
36
|
+
if (context.isBrowser === true) {
|
37
|
+
// 浏览器端执行逻辑
|
38
|
+
console.log('browser render')
|
39
|
+
} else {
|
40
|
+
// 服务器端执行逻辑 logger 功能需开启
|
41
|
+
context.logger.info('server render')
|
42
|
+
}
|
43
|
+
}
|
44
|
+
```
|
45
|
+
|
46
|
+
### 请求上下文
|
47
|
+
|
48
|
+
开启 SSR 时,在 Node 环境和浏览器端环境可以获取到同构的请求上下文。
|
49
|
+
|
50
|
+
稍有不同的是 Node 环境还支持设置响应头、响应码,并提供了 Logger 日志与 Metrics 打点。
|
51
|
+
|
52
|
+
:::tip
|
53
|
+
当 SSR 未开启时,仅包含可在浏览器端获取的部分信息。
|
54
|
+
|
55
|
+
:::
|
56
|
+
|
57
|
+
import { Tabs, Tab as TabItem } from "@theme";
|
58
|
+
|
59
|
+
<Tabs
|
60
|
+
defaultValue="RequestContext"
|
61
|
+
values={[
|
62
|
+
{ label: 'RequestContext', value: 'RequestContext', },
|
63
|
+
{ label: 'ServerContext', value: 'ServerContext', },
|
64
|
+
{ label: 'ClientContext', value: 'ClientContext', },
|
65
|
+
]
|
66
|
+
}>
|
67
|
+
<TabItem value="RequestContext">
|
58
68
|
|
59
|
-
|
69
|
+
```ts
|
70
|
+
type RequestContext = ServerContext | ClientContext;
|
71
|
+
```
|
72
|
+
|
73
|
+
</TabItem>
|
74
|
+
<TabItem value="ServerContext">
|
60
75
|
|
61
|
-
|
76
|
+
```ts
|
77
|
+
interface ServerContext {
|
78
|
+
isBrowser: false;
|
79
|
+
request: {
|
80
|
+
userAgent: string;
|
81
|
+
cookie: string;
|
82
|
+
cookieMap: Record<string, any>;
|
83
|
+
query: Record<string, any>;
|
84
|
+
url: string;
|
85
|
+
host: string;
|
86
|
+
headers?: IncomingHttpHeaders;
|
87
|
+
};
|
88
|
+
response: {
|
89
|
+
setHeader: (key: string, value: string) => void;
|
90
|
+
status: (code: number) => void;
|
91
|
+
};
|
92
|
+
logger: Logger;
|
93
|
+
metrics: Metrics;
|
62
94
|
}
|
63
95
|
```
|
96
|
+
|
97
|
+
</TabItem>
|
98
|
+
<TabItem value="ClientContext">
|
99
|
+
|
100
|
+
```ts
|
101
|
+
interface ClientContext {
|
102
|
+
isBrowser: true;
|
103
|
+
request: {
|
104
|
+
userAgent: string;
|
105
|
+
cookie: string;
|
106
|
+
cookieMap: Record<string, any>;
|
107
|
+
query: Record<string, any>;
|
108
|
+
url: string;
|
109
|
+
host: string;
|
110
|
+
headers?: IncomingHttpHeaders;
|
111
|
+
};
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
</TabItem>
|
116
|
+
</Tabs>
|
@@ -0,0 +1,97 @@
|
|
1
|
+
BFF 搭配运行时框架提供了文件上传能力,支持一体化调用及纯函数手动调用。
|
2
|
+
|
3
|
+
### BFF 函数
|
4
|
+
|
5
|
+
首先创建 `api/lambda/upload.ts` 文件:
|
6
|
+
|
7
|
+
```ts title="api/lambda/upload.ts"
|
8
|
+
export const post = async ({ formData }: {formData: Record<string, any>}) => {
|
9
|
+
console.info('formData:', formData);
|
10
|
+
// do somethings
|
11
|
+
return {
|
12
|
+
data: {
|
13
|
+
code: 0,
|
14
|
+
},
|
15
|
+
};
|
16
|
+
};
|
17
|
+
```
|
18
|
+
:::tip
|
19
|
+
通过接口处理函数入参中的 `formData` 可以获取客户端上传的文件。值为 `Object`,key 为上传时的字段名。
|
20
|
+
:::
|
21
|
+
|
22
|
+
### 一体化调用
|
23
|
+
|
24
|
+
接着在 `src/routes/upload/page.tsx` 中直接引入函数并调用:
|
25
|
+
```tsx title="routes/upload/page.tsx"
|
26
|
+
import { post } from '@api/upload';
|
27
|
+
import React from 'react';
|
28
|
+
|
29
|
+
export default (): JSX.Element => {
|
30
|
+
const [file, setFile] = React.useState<FileList | null>();
|
31
|
+
|
32
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
33
|
+
setFile(e.target.files);
|
34
|
+
};
|
35
|
+
|
36
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
37
|
+
e.preventDefault();
|
38
|
+
const formData = new FormData();
|
39
|
+
if (file) {
|
40
|
+
for (let i = 0; i < file.length; i++) {
|
41
|
+
formData.append('images', file[i]);
|
42
|
+
}
|
43
|
+
post({
|
44
|
+
formData,
|
45
|
+
});
|
46
|
+
}
|
47
|
+
};
|
48
|
+
|
49
|
+
return (
|
50
|
+
<div>
|
51
|
+
<input multiple type="file" onChange={handleChange} />
|
52
|
+
<button onClick={handleUpload}>upload</button>
|
53
|
+
</div>
|
54
|
+
);
|
55
|
+
};
|
56
|
+
```
|
57
|
+
:::tip
|
58
|
+
注意:入参类型必须为:`{ formData: FormData }` 才会正确上传。
|
59
|
+
|
60
|
+
:::
|
61
|
+
|
62
|
+
### 手动上传
|
63
|
+
可以基于 `fetch API` 手动上传文件,需要在调用 `fetch` 时,将 `body` 设置为 `FormData` 类型并提交 `post` 请求。
|
64
|
+
|
65
|
+
```tsx title="routes/upload/page.tsx"
|
66
|
+
import React from 'react';
|
67
|
+
|
68
|
+
export default (): JSX.Element => {
|
69
|
+
const [file, setFile] = React.useState<FileList | null>();
|
70
|
+
|
71
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
72
|
+
setFile(e.target.files);
|
73
|
+
};
|
74
|
+
|
75
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
76
|
+
e.preventDefault();
|
77
|
+
const formData = new FormData();
|
78
|
+
if (file) {
|
79
|
+
for (let i = 0; i < file.length; i++) {
|
80
|
+
formData.append('images', file[i]);
|
81
|
+
}
|
82
|
+
await fetch('/api/upload', {
|
83
|
+
method: 'POST',
|
84
|
+
body: formData,
|
85
|
+
});
|
86
|
+
}
|
87
|
+
};
|
88
|
+
|
89
|
+
return (
|
90
|
+
<form onSubmit={handleSubmit}>
|
91
|
+
<input multiple type="file" onChange={handleChange} />
|
92
|
+
<button type="submit">upload</button>
|
93
|
+
</form>
|
94
|
+
);
|
95
|
+
};
|
96
|
+
|
97
|
+
```
|
@@ -18,6 +18,19 @@ const defaultOptions = {
|
|
18
18
|
filename, // 基于 `output.distPath` 和 `entryName` 生成
|
19
19
|
templateParameters, // 对应 `html.templateParameters` 配置
|
20
20
|
chunks: [entryName],
|
21
|
+
minify: { // 基于 `output.minify` 和 `output.disableMinimize` 生成
|
22
|
+
removeComments: false,
|
23
|
+
useShortDoctype: true,
|
24
|
+
keepClosingSlash: true,
|
25
|
+
collapseWhitespace: true,
|
26
|
+
removeRedundantAttributes: true,
|
27
|
+
removeScriptTypeAttributes: true,
|
28
|
+
removeStyleLinkTypeAttributes: true,
|
29
|
+
removeEmptyAttributes: true,
|
30
|
+
minifyJS, // 基于 `output.charset`、`output.legalComments` 和 `performance.removeConsole` 生成
|
31
|
+
minifyCSS: true,
|
32
|
+
minifyURLs: true,
|
33
|
+
},
|
21
34
|
};
|
22
35
|
```
|
23
36
|
|
@@ -8,10 +8,13 @@ title: tsLoader
|
|
8
8
|
- **默认值:** `undefined`
|
9
9
|
- **打包工具:** `仅支持 webpack`
|
10
10
|
|
11
|
-
:::warning
|
12
|
-
|
11
|
+
:::warning 废弃提示
|
12
|
+
|
13
|
+
不推荐在项目中使用 ts-loader,原因如下:
|
14
|
+
|
15
|
+
- 启用 ts-loader 时将无法使用 [source.transformImport](/configure/app/source/transform-import) 和 [tools.styledComponents](/configure/app/tools/styled-components) 等由 Babel 和 SWC 提供支持的能力。
|
16
|
+
- Rspack 不支持使用 ts-loader。
|
13
17
|
|
14
|
-
启用 ts-loader 时将无法使用 [source.transformImport](https://modernjs.dev/configure/app/source/transform-import.html) 和 [tools.styledComponents](https://modernjs.dev/configure/app/tools/styled-components.html) 等由 Babel 和 SWC 提供支持的能力。
|
15
18
|
:::
|
16
19
|
|
17
20
|
项目中默认不开启 ts-loader,当 `tools.tsLoader` 不为 undefined 则表示开启 ts-loader,同时禁用 babel-loader 对 TypeScript 的编译。
|
@@ -231,8 +231,8 @@ Modern.js 中预先定义了一些常用的 Chain ID,你可以通过这些 ID
|
|
231
231
|
| `PLUGIN.REACT_FAST_REFRESH` | 对应 `ReactFastRefreshPlugin` |
|
232
232
|
| `PLUGIN.NODE_POLYFILL_PROVIDE` | 对应处理 node polyfill 的 `ProvidePlugin` |
|
233
233
|
| `PLUGIN.SUBRESOURCE_INTEGRITY` | 对应 `webpack-subresource-integrity` |
|
234
|
-
| `PLUGIN.ASSETS_RETRY` | 对应 webpack 静态资源重试插件 `WebpackAssetsRetryPlugin`
|
235
|
-
| `PLUGIN.AUTO_SET_ROOT_SIZE` | 对应自动设置根字体大小插件 `AutoSetRootSizePlugin`
|
234
|
+
| `PLUGIN.ASSETS_RETRY` | 对应 webpack 静态资源重试插件 `WebpackAssetsRetryPlugin` |
|
235
|
+
| `PLUGIN.AUTO_SET_ROOT_SIZE` | 对应自动设置根字体大小插件 `AutoSetRootSizePlugin` |
|
236
236
|
|
237
237
|
#### CHAIN_ID.MINIMIZER
|
238
238
|
|
@@ -247,4 +247,4 @@ Modern.js 中预先定义了一些常用的 Chain ID,你可以通过这些 ID
|
|
247
247
|
|
248
248
|
### 使用示例
|
249
249
|
|
250
|
-
使用示例可参考:[
|
250
|
+
使用示例可参考:[Rsbuild - bundlerChain 使用示例](https://rsbuild.dev/zh/guide/basic/configure-rspack#%E7%A4%BA%E4%BE%8B)。
|
@@ -1 +1 @@
|
|
1
|
-
["function", "frameworks", "extend-server", "sdk"]
|
1
|
+
["function", "frameworks", "extend-server", "sdk", "upload"]
|
@@ -124,13 +124,10 @@ Project reference 提供了以下能力:
|
|
124
124
|
|
125
125
|
### 示例
|
126
126
|
|
127
|
-
在上文的例子中,由于 app 引用了 lib 子项目,我们需要在 app 的 `tsconfig.json` 内配置 `
|
127
|
+
在上文的例子中,由于 app 引用了 lib 子项目,我们需要在 app 的 `tsconfig.json` 内配置 `references`,并指向 lib 对应的相对目录:
|
128
128
|
|
129
129
|
```json title="app/tsconfig.json"
|
130
130
|
{
|
131
|
-
"compilerOptions": {
|
132
|
-
"composite": true
|
133
|
-
},
|
134
131
|
"references": [
|
135
132
|
{
|
136
133
|
"path": "../lib"
|
@@ -139,6 +136,16 @@ Project reference 提供了以下能力:
|
|
139
136
|
}
|
140
137
|
```
|
141
138
|
|
139
|
+
同时,需要在 lib 子项目的 `tsconfig.json` 内配置 `composite` 为 `true`:
|
140
|
+
|
141
|
+
```json title="lib/A/tsconfig.json"
|
142
|
+
{
|
143
|
+
"compilerOptions": {
|
144
|
+
"composite": true
|
145
|
+
},
|
146
|
+
}
|
147
|
+
```
|
148
|
+
|
142
149
|
添加以上两个选项后,project reference 就已经配置完成了,你可以重新启动 VS Code 来查看配置以后的效果。
|
143
150
|
|
144
151
|
注意以上只是一个最简单的例子,在实际的 monorepo 项目中,可能会有更复杂的依赖关系,你需要添加完整的 `references` 配置,才能使上述功能正确运作。
|
@@ -13,11 +13,10 @@ sidebar_position: 15
|
|
13
13
|
目前 Modern.js 仅支持在 Node.js 环境中运行,未来将提供更多运行时环境的支持。
|
14
14
|
:::
|
15
15
|
|
16
|
-
|
17
16
|
## 构建部署产物
|
18
17
|
|
19
18
|
执行 `modern deploy` 命令将自动输出部署产物。此过程包括优化 Bundler 构建产物及产物依赖,检测当前部署平台,并自动生成可以在该平台运行的产物。
|
20
|
-
|
19
|
+
如果你希望在本地生成并测试特定部署平台的产物,可以通过设置环境变量来指定平台:
|
21
20
|
|
22
21
|
```bash
|
23
22
|
MODERNJS_DEPLOY=netlify npx modern deploy
|
@@ -27,8 +26,7 @@ MODERNJS_DEPLOY=netlify npx modern deploy
|
|
27
26
|
在 Modern.js 官方支持的部署平台中部署时,无需指定环境变量。
|
28
27
|
:::
|
29
28
|
|
30
|
-
|
31
|
-
## Node.js
|
29
|
+
## ModernJS 内置 Node.js 服务器
|
32
30
|
|
33
31
|
### 单仓库项目
|
34
32
|
|
@@ -223,7 +221,6 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
|
|
223
221
|
}
|
224
222
|
```
|
225
223
|
|
226
|
-
|
227
224
|
### Monorepo 项目
|
228
225
|
|
229
226
|
:::info
|
@@ -277,5 +274,106 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
|
|
277
274
|
提交你的代码,使用 Vercel 平台部署即可。
|
278
275
|
|
279
276
|
|
277
|
+
## 自建 Node.js 服务器
|
278
|
+
|
279
|
+
通常情况下,我们推荐使用 Modern.js 内置的 Node.js 服务器来部署应用,它支持托管纯前端项目或者全栈项目,并且保证在开发和生产环境下的表现一致。
|
280
|
+
|
281
|
+
如果你的项目是纯前端项目,也可以通过自建 Node.js 服务器来部署应用,以下用一个 Koa 服务器的示例来演示如何托管一个纯前端项目的产物。
|
282
|
+
|
283
|
+
例如你有一个 Node.js 服务器的仓库,你可以将项目的产物复制到该仓库下,现在结构如下:
|
284
|
+
|
285
|
+
```bash
|
286
|
+
.
|
287
|
+
├── .output
|
288
|
+
│ ├── html
|
289
|
+
│ └── static
|
290
|
+
└── server.js
|
291
|
+
```
|
292
|
+
|
293
|
+
在 `server.js` 中,假定有如下代码:
|
294
|
+
|
295
|
+
```ts title="server.js"
|
296
|
+
import Koa from 'koa';
|
297
|
+
|
298
|
+
const app = new Koa();
|
299
|
+
app.use(async (ctx, next) => {
|
300
|
+
ctx.body = 'Hello Modern.js';
|
301
|
+
});
|
302
|
+
app.listen(3000);
|
303
|
+
```
|
304
|
+
|
305
|
+
现在,你可以新增部分代码,将静态资源和 HTML 文件的访问逻辑添加到 `server.js` 中。这里需要通过 `mime-types` 包来获取静态资源的 MIME 类型,因此我们先安装依赖:
|
306
|
+
|
307
|
+
import { PackageManagerTabs } from '@theme';
|
308
|
+
|
309
|
+
<PackageManagerTabs command="add mime-types" />
|
310
|
+
|
311
|
+
```ts title="server.js"
|
312
|
+
const Koa = require('koa');
|
313
|
+
const fs = require('fs');
|
314
|
+
const path = require('path');
|
315
|
+
const mime = require('mime-types');
|
316
|
+
|
317
|
+
const app = new Koa();
|
318
|
+
app.use(async (ctx, next) => {
|
319
|
+
if (ctx.path.startsWith('/static')) {
|
320
|
+
ctx.type = mime.lookup(ctx.path);
|
321
|
+
ctx.body = fs.createReadStream(path.resolve(__dirname, `.output${ctx.path}`));
|
322
|
+
} else if (ctx.path === '/') {
|
323
|
+
ctx.type = 'html';
|
324
|
+
ctx.body = fs.createReadStream(path.resolve(__dirname, '.output/html/main/index.html'));
|
325
|
+
}
|
326
|
+
});
|
327
|
+
app.listen(3000);
|
328
|
+
```
|
329
|
+
|
330
|
+
:::note
|
331
|
+
以上代码是最基础的例子,你的应用可能是多入口的,需要根据不同的路径访问不同的 HTML 文件,自建 Node.js 服务器也会存在更多的逻辑。
|
332
|
+
:::
|
333
|
+
|
334
|
+
需要注意的是,如果你的项目中使用 Modern.js 约定式路由,或是使用 React Router 自行搭建了浏览器端路由,你必须通过正确的 `baseURL` 来访问 HTML 文件。
|
335
|
+
|
336
|
+
在 Modern.js 中,默认的 `baseURL` 是 `'/'`,你可以通过在 `modern.config.ts` 中修改 [`server.baseUrl`](/configure/app/server/base-url) 来配置。
|
337
|
+
|
338
|
+
:::danger
|
339
|
+
存在浏览器路由的项目,永远无法通过 `/index.html` 路径来访问到 HTML 文件。
|
340
|
+
:::
|
341
|
+
|
342
|
+
## Nignx
|
343
|
+
|
344
|
+
Nginx 是一个高性能的 HTTP 和反向代理服务器,它可以处理静态文件、反向代理、负载均衡等功能。在 Nginx 上部署,通常需要配置 `nginx.conf` 文件。
|
345
|
+
|
346
|
+
如果你的项目是纯前端项目,也可以通过 Nginx 来部署应用,以下提供一个 Nginx 配置的示例来演示如何托管一个纯前端项目的产物。
|
347
|
+
|
348
|
+
```conf title="nginx.conf"
|
349
|
+
# user [user] [group];
|
350
|
+
worker_processes 1;
|
351
|
+
|
352
|
+
events {
|
353
|
+
worker_connections 1024;
|
354
|
+
}
|
355
|
+
|
356
|
+
http {
|
357
|
+
include mime.types;
|
358
|
+
default_type application/octet-stream;
|
359
|
+
|
360
|
+
server {
|
361
|
+
listen 8080;
|
362
|
+
server_name localhost;
|
363
|
+
|
364
|
+
location / {
|
365
|
+
# root [projectPath]/.output/html/main;
|
366
|
+
index index.html;
|
367
|
+
try_files $uri $uri/ =404;
|
368
|
+
}
|
369
|
+
|
370
|
+
location /static {
|
371
|
+
# alias [projectPath]/.output/static;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
}
|
375
|
+
```
|
280
376
|
|
377
|
+
在上述配置中,你需要将 `[projectPath]` 替换为你的项目路径,将 `[user]` 和 `[group]` 替换为你当前的用户和用户组。
|
281
378
|
|
379
|
+
你可以将上述配置复制到 Nginx 安装目录的 `nginx.conf` 文件中,然后启动 Nginx 服务。你也可以通过 `nginx -c` 启动指定路径下的配置文件,此时你需要额外保证 `include` 指令配置的路径正确。
|
@@ -12,7 +12,7 @@ sidebar_position: 1
|
|
12
12
|
|
13
13
|
在 Modern.js 应用中,每一个入口对应一个独立的页面,也对应一条服务端路由。默认情况下,Modern.js 会基于目录约定来自动确定页面的入口,同时也支持通过配置项来自定义入口。
|
14
14
|
|
15
|
-
Modern.js 提供的很多配置项都是以入口为维度进行划分的,比如页面标题、HTML 模板、页面 Meta 信息、是否开启 SSR/SSG
|
15
|
+
Modern.js 提供的很多配置项都是以入口为维度进行划分的,比如页面标题、HTML 模板、页面 Meta 信息、是否开启 SSR/SSG、服务端路由规则等。如果你希望了解更多关于入口的技术细节,请参考[深入了解](#深入了解)章节的内容。
|
16
16
|
|
17
17
|
## 单入口与多入口
|
18
18
|
|
@@ -239,6 +239,57 @@ export default defineConfig({
|
|
239
239
|
值得注意的是,默认情况下,Modern.js 认为通过配置指定的入口是**框架模式入口**,将自动生成真正的编译入口。如果你的应用是从 Webpack 或 Vite 等构建工具迁移到 Modern.js 框架时,你通常需要在入口配置中开启 `disableMount` 选项,此时 Modern.js 认为该入口是**构建模式入口**。
|
240
240
|
|
241
241
|
|
242
|
+
## 深入了解
|
243
|
+
|
244
|
+
页面入口的概念衍生自 webpack 的入口(Entrypoint)概念,其主要用于配置 JavaScript 或其他模块在应用启动时加载和执行。webpack 对于网页应用的 [最佳实践](https://webpack.docschina.org/concepts/entry-points/#multi-page-application) 通常将入口与 HTML 产物对应,即每增加一个入口最终就会在产物中生成一份对应的 HTML 文件。入口引入的模块会在编译打包后生成多个 Chunk 产物,例如对于 JavaScript 模块最终可能会生成数个类似 `dist/static/js/index.ea39u8.js` 的文件产物。
|
245
|
+
|
246
|
+
需要注意区分入口、路由等概念之间的关系:
|
247
|
+
|
248
|
+
- **入口**:包含多个用于启动时执行的模块。
|
249
|
+
- **客户端路由**:在 Modern.js 中通常由 `react-router` 实现,通过 History API 判断浏览器当前 URL 决定加载和显示哪个 React 组件。
|
250
|
+
- **服务端路由**:服务端可以模仿 [devServer 的行为](https://webpack.docschina.org/configuration/dev-server/#devserverhistoryapifallback),将 index.html 页面代替所有 404 响应被返回以实现客户端路由,也可以自行实现任何路由逻辑。
|
251
|
+
|
252
|
+
它们的对应关系如下:
|
253
|
+
|
254
|
+
- 每个 webpack 网站项目可以包含多个入口
|
255
|
+
- 每个入口包含若干个模块(源码文件)
|
256
|
+
- 每个入口通常对应一个 HTML 文件产物和若干其它产物。
|
257
|
+
- 每个 HTML 文件可以包含多个客户端路由方案(比如在页面中同时使用 `react-router` 和 `@tanstack/react-router`)。
|
258
|
+
- 每个 HTML 文件可以被多个服务端路由对应。
|
259
|
+
- 每个 HTML 文件可以包含多个客户端路由,当访问单入口应用的不同路由时实际使用的是同一个 HTML 文件。
|
260
|
+
|
261
|
+
## 常见问题
|
262
|
+
|
263
|
+
1. **`react-router` 定义的每个客户端路由会分别生成一个 HTML 文件吗?**
|
264
|
+
|
265
|
+
不会。每个入口通常只会生成一个 HTML 文件,单个入口中如果定义多个客户端路由系统会共用这一个 HTML 文件。
|
266
|
+
|
267
|
+
2. **约定式路由的 `routes/` 目录下每个 `page.tsx` 文件都会生成一个 HTML 文件吗?**
|
268
|
+
|
269
|
+
不是。约定式路由是基于 `react-router` 实现的客户端路由方案,其约定 `routes/` 目录下每个 `page.tsx` 文件都会对应生成一个 `react-router` 的客户端路由。`routes/` 本身作为一个页面入口,对应最终产物中的一个 HTML 文件。
|
270
|
+
|
271
|
+
3. **服务端渲染(SSR)的项目是否会构建多份 HTML 产物?**
|
272
|
+
|
273
|
+
在使用服务端渲染应用时并不必须在编译时生成一份 HTML 产物,它可以只包含用于渲染的服务端 JavaScript 产物。此时 `react-router` 将在服务端运行和调度路由,并在每次请求时渲染并响应 HTML 内容。
|
274
|
+
|
275
|
+
而 Modern.js 在编译时仍会为每个入口生成包含 HTML 文件的完整的客户端产物,用于在服务端渲染失败时降级为客户端渲染使用。
|
276
|
+
|
277
|
+
另一个特殊情况是使用静态站点生成(SSG)的项目,即使是使用约定式路由搭建的单入口 SSG 应用,Modern.js 也会在 webpack 的流程外为每个 `page.tsx` 文件生成一份单独的 HTML 文件。
|
278
|
+
|
279
|
+
需要注意的是即使开启服务端渲染,React 通常仍需要执行水合阶段并在前端执行 `react-router` 的路由。
|
280
|
+
|
281
|
+
4. **单入口应用是否存在输出多个 HTML 文件的例外情况?**
|
282
|
+
|
283
|
+
你可以自行配置 [html-rspack-plugin](https://rspack.dev/zh/plugins/rspack/html-rspack-plugin#%E7%94%9F%E6%88%90%E5%A4%9A%E4%B8%AA-html-%E6%96%87%E4%BB%B6) 为每个入口生成多个 HTML 产物,或使多个入口共用一个 HTML 产物。
|
284
|
+
|
285
|
+
5. **什么叫多页应用(Multi-Page Application)?**
|
286
|
+
|
287
|
+
多页应用的 “页面” 指的是静态的 HTML 文件。
|
288
|
+
一般可以将任何包含多个入口、多个 HTML 文件产物的网页应用称为多页应用。
|
289
|
+
狭义的多页应用可能不包含客户端路由、仅通过 `<a>` 之类的标签元素进行 HTML 静态页面之间的跳转,但实践中上多页应用也经常需要为其入口配置客户端路由以满足不同需求。
|
290
|
+
|
291
|
+
相反地,通过 `react-router` 定义多个路由的单入口应用因为只生成一个 HTML 文件产物,所以被称为单页应用(Single Page Application)。
|
292
|
+
|
242
293
|
## 弃用功能
|
243
294
|
|
244
295
|
目前,如果入口所在的目录满足以下条件,也会成为应用入口。
|
@@ -267,6 +318,3 @@ export default (App: React.ComponentType, bootstrap: () => void) => {
|
|
267
318
|
### 构建模式入口
|
268
319
|
|
269
320
|
当入口目录中存在 `index.[jt]sx`(即将废弃) 并且没有通过 `export default` 导出函数时,该入口也将被认为是构建模式入口。
|
270
|
-
|
271
|
-
|
272
|
-
|
@@ -5,42 +5,7 @@ sidebar_position: 1
|
|
5
5
|
|
6
6
|
# Modern.js 介绍
|
7
7
|
|
8
|
-
**Modern.js
|
9
|
-
|
10
|
-
目前 Modern.js 包含两个解决方案,分别面向 Web 应用开发场景 和 npm 包开发场景:
|
11
|
-
|
12
|
-
import { SolutionCards } from '@site/src/components/SolutionCards';
|
13
|
-
|
14
|
-
<SolutionCards
|
15
|
-
cards={[
|
16
|
-
{
|
17
|
-
title: 'Modern.js Framework',
|
18
|
-
description: '基于 React 的渐进式 Web 开发框架',
|
19
|
-
link: 'http://modernjs.dev/',
|
20
|
-
},
|
21
|
-
{
|
22
|
-
title: 'Modern.js Module',
|
23
|
-
description: '易用、高性能的 npm 包开发方案',
|
24
|
-
link: 'http://modernjs.dev/module-tools/',
|
25
|
-
},
|
26
|
-
]}
|
27
|
-
/>
|
28
|
-
|
29
|
-
## 关于文档
|
30
|
-
|
31
|
-
**当前文档站对应的是 Modern.js 框架**,适用于开发 Web 应用。
|
32
|
-
|
33
|
-
- 如果你需要开发一个 npm 包,请移步 [Modern.js Module 文档](https://modernjs.dev/module-tools)。
|
34
|
-
- 如果你需要一个构建工具来打包 Web 应用,请移步 [Rsbuild 文档](https://rsbuild.dev/)。
|
35
|
-
- 如果你需要开发一个文档站点,推荐使用 [Rspress 文档](https://rspress.dev/)。
|
36
|
-
|
37
|
-
:::tip
|
38
|
-
由于 Modern.js 框架的使用最为广泛,在本文档站中,我们会省略「框架」,直接称其为 Modern.js。
|
39
|
-
:::
|
40
|
-
|
41
|
-
## Modern.js 框架
|
42
|
-
|
43
|
-
**Modern.js 框架是一个基于 React 的渐进式 Web 开发框架**。在字节跳动内部,我们将 Modern.js 封装为上层框架,并支撑了数千个 Web 应用的研发。
|
8
|
+
**Modern.js 是一个基于 React 的渐进式 Web 开发框架**。在字节跳动内部,我们将 Modern.js 封装为上层框架,并支撑了数千个 Web 应用的研发。
|
44
9
|
|
45
10
|
Modern.js 能为开发者提供极致的**开发体验(Development Experience)**,让应用拥有更好的**用户体验(User Experience)**。
|
46
11
|
|
package/package.json
CHANGED
@@ -15,17 +15,17 @@
|
|
15
15
|
"modern",
|
16
16
|
"modern.js"
|
17
17
|
],
|
18
|
-
"version": "2.
|
18
|
+
"version": "2.61.0",
|
19
19
|
"publishConfig": {
|
20
20
|
"registry": "https://registry.npmjs.org/",
|
21
21
|
"access": "public",
|
22
22
|
"provenance": true
|
23
23
|
},
|
24
24
|
"dependencies": {
|
25
|
-
"@modern-js/sandpack-react": "2.
|
25
|
+
"@modern-js/sandpack-react": "2.61.0"
|
26
26
|
},
|
27
27
|
"devDependencies": {
|
28
|
-
"@rspress/shared": "1.
|
28
|
+
"@rspress/shared": "1.35.3",
|
29
29
|
"@types/fs-extra": "9.0.13",
|
30
30
|
"@types/node": "^16",
|
31
31
|
"classnames": "^2",
|
@@ -33,7 +33,7 @@
|
|
33
33
|
"fs-extra": "^10",
|
34
34
|
"react": "^18.3.1",
|
35
35
|
"react-dom": "^18.3.1",
|
36
|
-
"rspress": "1.
|
36
|
+
"rspress": "1.35.3",
|
37
37
|
"ts-node": "^10.9.1",
|
38
38
|
"typescript": "^5"
|
39
39
|
},
|