@modern-js/main-doc 2.67.2 → 2.67.4
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/docs/en/apis/app/hooks/src/routes.mdx +70 -0
- package/docs/en/configure/app/output/filename.mdx +8 -3
- package/docs/en/configure/app/runtime/0-intro.mdx +23 -8
- package/docs/en/configure/app/usage.mdx +0 -2
- package/docs/en/guides/basic-features/data/data-cache.mdx +216 -1
- package/docs/en/guides/basic-features/deploy.mdx +63 -10
- package/docs/en/guides/basic-features/routes.mdx +40 -2
- package/docs/en/guides/get-started/quick-start.mdx +1 -1
- package/docs/zh/configure/app/output/filename.mdx +8 -0
- package/docs/zh/configure/app/runtime/0-intro.mdx +7 -3
- package/docs/zh/configure/app/usage.mdx +0 -1
- package/docs/zh/guides/basic-features/data/data-cache.mdx +205 -1
- package/docs/zh/guides/basic-features/deploy.mdx +56 -8
- package/docs/zh/guides/basic-features/routes.mdx +112 -0
- package/package.json +3 -3
- /package/docs/en/configure/app/source/{mainEntryName.mdx → main-entry-name.mdx} +0 -0
- /package/docs/zh/configure/app/source/{mainEntryName.mdx → main-entry-name.mdx} +0 -0
@@ -88,3 +88,73 @@ export default () => {
|
|
88
88
|
`<Outlet>` is a new API in React Router 6. For details, see [Outlet](https://reactrouter.com/en/main/components/outlet#outlet).
|
89
89
|
|
90
90
|
:::
|
91
|
+
|
92
|
+
## Upgrading to React Router v7
|
93
|
+
|
94
|
+
React Router v7 reduces bundle size (approximately 15% smaller) compared to React Router v6, provides a more efficient route matching algorithm, and offers better support for React 19 and TypeScript. There are very few breaking changes compared to React Router v6, and Modern.js has made both versions compatible, allowing for a seamless upgrade by simply installing and registering the appropriate plugin.
|
95
|
+
|
96
|
+
:::info
|
97
|
+
|
98
|
+
For more changes from React Router v6 to React Router v7, check the [documentation](https://reactrouter.com/upgrading/v6#upgrade-to-v7)
|
99
|
+
|
100
|
+
:::
|
101
|
+
|
102
|
+
### Requirements
|
103
|
+
|
104
|
+
React Router v7 has certain environment requirements:
|
105
|
+
|
106
|
+
- Node.js 20+
|
107
|
+
- React 18+
|
108
|
+
- React DOM 18+
|
109
|
+
|
110
|
+
### Install the Plugin
|
111
|
+
|
112
|
+
First, install the Modern.js React Router v7 plugin:
|
113
|
+
|
114
|
+
```bash
|
115
|
+
pnpm add @modern-js/plugin-router-v7
|
116
|
+
```
|
117
|
+
|
118
|
+
### Configure the Plugin
|
119
|
+
|
120
|
+
Register the plugin in `modern.config.ts`:
|
121
|
+
|
122
|
+
```ts title="modern.config.ts"
|
123
|
+
import { routerPlugin } from '@modern-js/plugin-router-v7';
|
124
|
+
|
125
|
+
export default {
|
126
|
+
runtime: {
|
127
|
+
router: true,
|
128
|
+
},
|
129
|
+
plugins: [routerPlugin()],
|
130
|
+
};
|
131
|
+
```
|
132
|
+
|
133
|
+
### Code Changes
|
134
|
+
|
135
|
+
In React Router v7, you no longer need to use the `defer` API; you can directly return data in the data loader:
|
136
|
+
|
137
|
+
```ts title="routes/page.data.ts"
|
138
|
+
import { defer } from '@modern-js/runtime/router';
|
139
|
+
|
140
|
+
export const loader = async ({ params }) => {
|
141
|
+
// Recommended v7 style
|
142
|
+
const user = fetchUser(params.id)
|
143
|
+
return { user };
|
144
|
+
|
145
|
+
// v6 style, still compatible with Modern.js
|
146
|
+
return defer({ data: 'hello' });
|
147
|
+
};
|
148
|
+
```
|
149
|
+
|
150
|
+
React Router v7 has also deprecated the `json` API:
|
151
|
+
|
152
|
+
```ts title="routes/page.data.ts"
|
153
|
+
export const loader = async ({ params }) => {
|
154
|
+
// Recommended v7 style
|
155
|
+
return { data: 'hello' };
|
156
|
+
|
157
|
+
// v6 style, still compatible with Modern.js
|
158
|
+
return json({ data: 'hello' });
|
159
|
+
};
|
160
|
+
```
|
@@ -3,13 +3,20 @@ title: filename
|
|
3
3
|
configName: output.filename
|
4
4
|
---
|
5
5
|
|
6
|
+
:::warning
|
7
|
+
|
8
|
+
In Modern.js `dev` command or use Modern.js server deployment, don't modify the html output filename. This will cause the page to be 404.
|
9
|
+
|
10
|
+
In common, you don't need to modify the html filename. If you want modify `main.html` to `index.html`, using [source.mainEntryName](/configure/app/source/main-entry-name).
|
11
|
+
|
12
|
+
:::
|
13
|
+
|
6
14
|
# output.filename
|
7
15
|
|
8
16
|
- **Type:**
|
9
17
|
|
10
18
|
```ts
|
11
19
|
type FilenameConfig = {
|
12
|
-
html?: string;
|
13
20
|
js?:
|
14
21
|
| string
|
15
22
|
| ((pathData: Rspack.PathData, assetInfo: Rspack.JsAssetInfo) => string);
|
@@ -27,7 +34,6 @@ type FilenameConfig = {
|
|
27
34
|
```js
|
28
35
|
// Development mode
|
29
36
|
const devDefaultFilename = {
|
30
|
-
html: '[name].html',
|
31
37
|
js: '[name].js',
|
32
38
|
css: '[name].css',
|
33
39
|
svg: '[name].[contenthash:8].svg',
|
@@ -39,7 +45,6 @@ const devDefaultFilename = {
|
|
39
45
|
|
40
46
|
// Production mode
|
41
47
|
const prodDefaultFilename = {
|
42
|
-
html: '[name].html',
|
43
48
|
js: output.target === 'node' ? '[name].js' : '[name].[contenthash:8].js',
|
44
49
|
css: '[name].[contenthash:8].css',
|
45
50
|
svg: '[name].[contenthash:8].svg',
|
@@ -2,12 +2,20 @@
|
|
2
2
|
|
3
3
|
Modern.js runtime configuration should be centralized in the `src/modern.runtime.ts` file.
|
4
4
|
|
5
|
+
:::warning
|
6
|
+
|
7
|
+
Using the `src/modern.runtime.ts` configuration approach requires Modern.js version **MAJOR_VERSION.66.0** or higher.
|
8
|
+
|
9
|
+
:::
|
10
|
+
|
5
11
|
:::info
|
12
|
+
|
6
13
|
If this file doesn't exist in your project yet, create it with the following command:
|
7
14
|
|
8
15
|
```bash
|
9
16
|
touch src/modern.runtime.ts
|
10
17
|
```
|
18
|
+
|
11
19
|
:::
|
12
20
|
|
13
21
|
## Basic Configuration
|
@@ -33,7 +41,7 @@ For multi-entry applications, `defineRuntimeConfig` can accept a function that r
|
|
33
41
|
```tsx
|
34
42
|
import { defineRuntimeConfig } from '@modern-js/runtime';
|
35
43
|
|
36
|
-
export default defineRuntimeConfig(
|
44
|
+
export default defineRuntimeConfig(entryName => {
|
37
45
|
if (entryName === 'main') {
|
38
46
|
return {
|
39
47
|
router: {
|
@@ -53,12 +61,11 @@ export default defineRuntimeConfig((entryName) => {
|
|
53
61
|
};
|
54
62
|
});
|
55
63
|
```
|
64
|
+
|
56
65
|
:::tip
|
66
|
+
|
57
67
|
Using the `src/modern.runtime.ts` configuration approach does not support exporting asynchronous functions, which is related to the rendering method of Modern.js. If you need to add asynchronous logic, please use the **[Runtime Plugin](/plugin/introduction.html#runtime-plugin)**.
|
58
|
-
:::
|
59
68
|
|
60
|
-
:::warning
|
61
|
-
Using the `src/modern.runtime.ts` configuration approach requires Modern.js version **MAJOR_VERSION.66.0** or higher.
|
62
69
|
:::
|
63
70
|
|
64
71
|
import RuntimeCliConfig from '@site-docs/components/runtime-cli-config';
|
@@ -79,15 +86,23 @@ To improve maintainability, Modern.js introduced the unified `src/modern.runtime
|
|
79
86
|
```ts
|
80
87
|
// modern.config.ts
|
81
88
|
export default {
|
82
|
-
runtime: {
|
83
|
-
|
89
|
+
runtime: {
|
90
|
+
/* ... */
|
91
|
+
},
|
92
|
+
runtimeByEntries: {
|
93
|
+
/* ... */
|
94
|
+
},
|
84
95
|
};
|
85
96
|
|
86
97
|
// App.config
|
87
|
-
App.config = {
|
98
|
+
App.config = {
|
99
|
+
/* ... */
|
100
|
+
};
|
88
101
|
|
89
102
|
// layout file
|
90
|
-
export const config = () => {
|
103
|
+
export const config = () => {
|
104
|
+
/* Dynamic configuration logic */
|
105
|
+
};
|
91
106
|
```
|
92
107
|
|
93
108
|
### Migration Recommendation
|
@@ -13,10 +13,8 @@ Modern.js does not support configuring the same configuration item in both `pack
|
|
13
13
|
|
14
14
|
**Runtime configuration** can be configured in the `src/modern.runtime.(ts|js|mjs)` file.
|
15
15
|
|
16
|
-
{/* TODO server 配置文件更新 */}
|
17
16
|
**Server Runtime configuration** can be configured in the `modern.server-runtime.config.(ts|js|mjs)` file in the root path.
|
18
17
|
|
19
|
-
|
20
18
|
## Compile Configuration
|
21
19
|
|
22
20
|
### Configuring in Configuration Files
|
@@ -4,7 +4,7 @@ sidebar_position: 4
|
|
4
4
|
---
|
5
5
|
# Data Caching
|
6
6
|
|
7
|
-
The `cache` function allows you to cache the results of data fetching or computation.
|
7
|
+
The `cache` function allows you to cache the results of data fetching or computation, Compared to full-page [rendering cache](/guides/basic-features/render/ssr-cache), it provides more fine-grained control over data granularity and is applicable to various scenarios such as Client-Side Rendering (CSR), Server-Side Rendering (SSR), and API services (BFF).
|
8
8
|
|
9
9
|
:::info
|
10
10
|
X.65.5 and above versions are required
|
@@ -12,6 +12,15 @@ X.65.5 and above versions are required
|
|
12
12
|
|
13
13
|
## Basic Usage
|
14
14
|
|
15
|
+
:::note
|
16
|
+
|
17
|
+
If you use the `cache` function in BFF, you should import the cache funtion from `@modern-js/server-runtime/cache`
|
18
|
+
|
19
|
+
`import { cache } from '@modern-js/server-runtime/cache'`
|
20
|
+
|
21
|
+
:::
|
22
|
+
|
23
|
+
|
15
24
|
```ts
|
16
25
|
import { cache } from '@modern-js/runtime/cache';
|
17
26
|
import { fetchUserData } from './api';
|
@@ -34,6 +43,8 @@ const loader = async () => {
|
|
34
43
|
- `tag`: Tag to identify the cache, which can be used to invalidate the cache
|
35
44
|
- `maxAge`: Cache validity period (milliseconds)
|
36
45
|
- `revalidate`: Time window for revalidating the cache (milliseconds), similar to HTTP Cache-Control's stale-while-revalidate functionality
|
46
|
+
- `getKey`: Simplified cache key generation function based on function parameters
|
47
|
+
- `customKey`: Custom cache key function
|
37
48
|
|
38
49
|
The type of the `options` parameter is as follows:
|
39
50
|
|
@@ -42,6 +53,12 @@ interface CacheOptions {
|
|
42
53
|
tag?: string | string[];
|
43
54
|
maxAge?: number;
|
44
55
|
revalidate?: number;
|
56
|
+
getKey?: <Args extends any[]>(...args: Args) => string;
|
57
|
+
customKey?: <Args extends any[]>(options: {
|
58
|
+
params: Args;
|
59
|
+
fn: (...args: Args) => any;
|
60
|
+
generatedKey: string;
|
61
|
+
}) => string | symbol;
|
45
62
|
}
|
46
63
|
```
|
47
64
|
|
@@ -153,6 +170,204 @@ revalidateTag('dashboard-stats'); // Invalidates the cache for both getDashboard
|
|
153
170
|
```
|
154
171
|
|
155
172
|
|
173
|
+
#### `getKey` Parameter
|
174
|
+
|
175
|
+
The `getKey` parameter simplifies cache key generation, especially useful when you only need to rely on part of the function parameters to differentiate caches. It's a function that receives the same parameters as the original function and returns a string or number as the cache key:
|
176
|
+
|
177
|
+
```ts
|
178
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
179
|
+
import { fetchUserData } from './api';
|
180
|
+
|
181
|
+
const getUser = cache(
|
182
|
+
async (userId, options) => {
|
183
|
+
// Here options might contain many configurations, but we only want to cache based on userId
|
184
|
+
return await fetchUserData(userId, options);
|
185
|
+
},
|
186
|
+
{
|
187
|
+
maxAge: CacheTime.MINUTE * 5,
|
188
|
+
// Only use the first parameter (userId) as the cache key
|
189
|
+
getKey: (userId, options) => userId,
|
190
|
+
}
|
191
|
+
);
|
192
|
+
|
193
|
+
// The following two calls will share the cache because getKey only uses userId
|
194
|
+
await getUser(123, { language: 'en' });
|
195
|
+
await getUser(123, { language: 'fr' }); // Cache hit, won't request again
|
196
|
+
|
197
|
+
// Different userId will use different cache
|
198
|
+
await getUser(456, { language: 'en' }); // Won't hit cache, will request again
|
199
|
+
```
|
200
|
+
|
201
|
+
You can also use Modern.js's `generateKey` function together with getKey to generate the cache key:
|
202
|
+
|
203
|
+
:::info
|
204
|
+
|
205
|
+
The `generateKey` function in Modern.js ensures that a consistent and unique key is generated even if object property orders change, guaranteeing stable caching.
|
206
|
+
|
207
|
+
:::
|
208
|
+
|
209
|
+
```ts
|
210
|
+
import { cache, CacheTime, generateKey } from '@modern-js/runtime/cache';
|
211
|
+
import { fetchUserData } from './api';
|
212
|
+
|
213
|
+
const getUser = cache(
|
214
|
+
async (userId, options) => {
|
215
|
+
return await fetchUserData(userId, options);
|
216
|
+
},
|
217
|
+
{
|
218
|
+
maxAge: CacheTime.MINUTE * 5,
|
219
|
+
getKey: (userId, options) => generateKey(userId),
|
220
|
+
}
|
221
|
+
);
|
222
|
+
```
|
223
|
+
|
224
|
+
Additionally, `getKey` can also return a numeric type as a cache key:
|
225
|
+
|
226
|
+
```ts
|
227
|
+
const getUserById = cache(
|
228
|
+
fetchUserDataById,
|
229
|
+
{
|
230
|
+
maxAge: CacheTime.MINUTE * 5,
|
231
|
+
// Directly use the numeric ID as the cache key
|
232
|
+
getKey: (id) => id,
|
233
|
+
}
|
234
|
+
);
|
235
|
+
|
236
|
+
await getUserById(42); // Uses 42 as the cache key
|
237
|
+
```
|
238
|
+
|
239
|
+
#### `customKey` parameter
|
240
|
+
|
241
|
+
The `customKey` parameter is used to customize the cache key. It is a function that receives an object with the following properties and returns a string or Symbol type as the cache key:
|
242
|
+
|
243
|
+
- `params`: Array of arguments passed to the cached function
|
244
|
+
- `fn`: Reference to the original function being cached
|
245
|
+
- `generatedKey`: Cache key automatically generated by the framework based on input parameters
|
246
|
+
|
247
|
+
:::info
|
248
|
+
|
249
|
+
Generally, the cache will be invalidated in the following scenarios:
|
250
|
+
1. The referenced cached function changes
|
251
|
+
2. The function's input parameters change
|
252
|
+
3. The maxAge condition is no longer satisfied
|
253
|
+
4. The `revalidateTag` method has been called
|
254
|
+
|
255
|
+
`customKey` can be used in scenarios where function references are different but shared caching is desired. If it's just for customizing the cache key, it is recommended to use `getKey`.
|
256
|
+
|
257
|
+
:::
|
258
|
+
|
259
|
+
This is very useful in some scenarios, such as when the function reference changes , but you want to still return the cached data.
|
260
|
+
|
261
|
+
```ts
|
262
|
+
import { cache } from '@modern-js/runtime/cache';
|
263
|
+
import { fetchUserData } from './api';
|
264
|
+
|
265
|
+
// Different function references, but share the same cache via customKey
|
266
|
+
const getUserA = cache(
|
267
|
+
fetchUserData,
|
268
|
+
{
|
269
|
+
maxAge: CacheTime.MINUTE * 5,
|
270
|
+
customKey: ({ params }) => {
|
271
|
+
// Return a stable string as the cache key
|
272
|
+
return `user-${params[0]}`;
|
273
|
+
},
|
274
|
+
}
|
275
|
+
);
|
276
|
+
|
277
|
+
// Even if the function reference changes,
|
278
|
+
// as long as customKey returns the same value, the cache will be hit
|
279
|
+
const getUserB = cache(
|
280
|
+
(...args) => fetchUserData(...args), // New function reference
|
281
|
+
{
|
282
|
+
maxAge: CacheTime.MINUTE * 5,
|
283
|
+
customKey: ({ params }) => {
|
284
|
+
// Return the same key as getUserA
|
285
|
+
return `user-${params[0]}`;
|
286
|
+
},
|
287
|
+
}
|
288
|
+
);
|
289
|
+
|
290
|
+
// You can also use Symbol as a cache key (usually used to share cache within the same application)
|
291
|
+
const USER_CACHE_KEY = Symbol('user-cache');
|
292
|
+
const getUserC = cache(
|
293
|
+
fetchUserData,
|
294
|
+
{
|
295
|
+
maxAge: CacheTime.MINUTE * 5,
|
296
|
+
customKey: () => USER_CACHE_KEY,
|
297
|
+
}
|
298
|
+
);
|
299
|
+
|
300
|
+
// You can utilize the generatedKey parameter to modify the default key
|
301
|
+
const getUserD = cache(
|
302
|
+
fetchUserData,
|
303
|
+
{
|
304
|
+
customKey: ({ generatedKey }) => `prefix-${generatedKey}`,
|
305
|
+
}
|
306
|
+
);
|
307
|
+
```
|
308
|
+
|
309
|
+
#### `onCache` Parameter
|
310
|
+
|
311
|
+
The `onCache` parameter allows you to track cache statistics such as hit rate. It's a callback function that receives information about each cache operation, including the status, key, parameters, and result.
|
312
|
+
|
313
|
+
```ts
|
314
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
315
|
+
|
316
|
+
// Track cache statistics
|
317
|
+
const stats = {
|
318
|
+
total: 0,
|
319
|
+
hits: 0,
|
320
|
+
misses: 0,
|
321
|
+
stales: 0,
|
322
|
+
hitRate: () => stats.hits / stats.total
|
323
|
+
};
|
324
|
+
|
325
|
+
const getUser = cache(
|
326
|
+
fetchUserData,
|
327
|
+
{
|
328
|
+
maxAge: CacheTime.MINUTE * 5,
|
329
|
+
onCache({ status, key, params, result }) {
|
330
|
+
// status can be 'hit', 'miss', or 'stale'
|
331
|
+
stats.total++;
|
332
|
+
|
333
|
+
if (status === 'hit') {
|
334
|
+
stats.hits++;
|
335
|
+
} else if (status === 'miss') {
|
336
|
+
stats.misses++;
|
337
|
+
} else if (status === 'stale') {
|
338
|
+
stats.stales++;
|
339
|
+
}
|
340
|
+
|
341
|
+
console.log(`Cache ${status} for key: ${String(key)}`);
|
342
|
+
console.log(`Current hit rate: ${stats.hitRate() * 100}%`);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
);
|
346
|
+
|
347
|
+
// Usage example
|
348
|
+
await getUser(1); // Cache miss
|
349
|
+
await getUser(1); // Cache hit
|
350
|
+
await getUser(2); // Cache miss
|
351
|
+
```
|
352
|
+
|
353
|
+
The `onCache` callback receives an object with the following properties:
|
354
|
+
|
355
|
+
- `status`: The cache operation status, which can be:
|
356
|
+
- `hit`: Cache hit, returning cached content
|
357
|
+
- `miss`: Cache miss, executing the function and caching the result
|
358
|
+
- `stale`: Cache hit but data is stale, returning cached content while revalidating in the background
|
359
|
+
- `key`: The cache key, which is either the result of `customKey` or the default generated key
|
360
|
+
- `params`: The parameters passed to the cached function
|
361
|
+
- `result`: The result data (either from cache or newly computed)
|
362
|
+
|
363
|
+
This callback is only invoked when the `options` parameter is provided. When using the cache function without options, the `onCache` callback is not called.
|
364
|
+
|
365
|
+
The `onCache` callback is useful for:
|
366
|
+
- Monitoring cache performance
|
367
|
+
- Calculating hit rates
|
368
|
+
- Logging cache operations
|
369
|
+
- Implementing custom metrics
|
370
|
+
|
156
371
|
### Storage
|
157
372
|
|
158
373
|
Currently, both client and server caches are stored in memory.
|
@@ -6,7 +6,7 @@ sidebar_position: 15
|
|
6
6
|
|
7
7
|
Currently, Modern.js offers two deployment way:
|
8
8
|
- You can host your application in a container that includes a Node.js environment on your own, which provides flexibility for the deployment of the application.
|
9
|
-
- You can also deploy your application through a platform. Currently, Modern.js supports
|
9
|
+
- You can also deploy your application through a platform. Currently, Modern.js officially supports deployment on [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/), and [Github pages](https://pages.github.com/).
|
10
10
|
|
11
11
|
:::info
|
12
12
|
Currently, Modern.js only supports running in a Node.js environment. Support for more runtime environments will be provided in the future.
|
@@ -110,6 +110,11 @@ Add the following content to `netlify.toml`:
|
|
110
110
|
command = "modern deploy"
|
111
111
|
```
|
112
112
|
|
113
|
+
:::info
|
114
|
+
You can refer to the [deployment project example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
|
115
|
+
|
116
|
+
:::
|
117
|
+
|
113
118
|
Now, add a project to the Netlify platform and deploy it!
|
114
119
|
|
115
120
|
### Full Stack Project
|
@@ -129,14 +134,16 @@ Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. Th
|
|
129
134
|
```
|
130
135
|
|
131
136
|
:::info
|
132
|
-
Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
|
137
|
+
1. Currently, Modern.js does not support deployment on Netlify Edge Functions. We will support it in future versions.
|
138
|
+
2. You can refer to the [deployment project example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr).
|
139
|
+
|
133
140
|
:::
|
134
141
|
|
135
142
|
|
136
143
|
### Monorepo
|
137
144
|
|
138
145
|
:::info
|
139
|
-
The following guide is mainly for full-stack projects, for pure CSR projects, just follow [Pure Frontend Project](#
|
146
|
+
The following guide is mainly for full-stack projects, for pure CSR projects, just follow [Pure Frontend Project](#pure-front-end-project-1) to deploy.
|
140
147
|
:::
|
141
148
|
|
142
149
|
For Monorepo projects, in addition to building our current project, you also need to build other sub-projects in the repository that the current project depends on.
|
@@ -213,6 +220,11 @@ Commit your project to git, select Framework Preset as `Other` on the Vercel pla
|
|
213
220
|
|
214
221
|
<img src="https://sf16-sg.tiktokcdn.com/obj/eden-sg/lmeh7nuptpfnuhd/vercel-framework-preset.png" />
|
215
222
|
|
223
|
+
:::info
|
224
|
+
You can refer to the [deployment project examples](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
|
225
|
+
|
226
|
+
:::
|
227
|
+
|
216
228
|
### Full Stack Project
|
217
229
|
|
218
230
|
Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. These projects need to be deployed on **Vercel Functions**.
|
@@ -220,14 +232,12 @@ Full-stack projects refer to projects that use Custom Web Server, SSR or BFF. Th
|
|
220
232
|
In addition to configuring `vercel.json` in the same way as a [pure front-end project](#pure-front-end-project), there are two points to note for full-stack projects:
|
221
233
|
|
222
234
|
1. Currently, Modern.js does not support deploying BFF projects on the Vercel platform. We will support it in future versions.
|
223
|
-
2.
|
224
|
-
see [Serverless Function contains invalid runtime error](https://vercel.com/guides/serverless-function-contains-invalid-runtime-error), you can modify `package.json` to specify the version:
|
225
|
-
```json title="package.json"
|
226
|
-
"engines": {
|
227
|
-
"node": "18.x"
|
228
|
-
}
|
229
|
-
```
|
235
|
+
2. The Node.js version for function execution is determined by the project configuration on the Vercel platform.
|
230
236
|
|
237
|
+
:::info
|
238
|
+
You can refer to the [deployment project examples](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr).
|
239
|
+
|
240
|
+
:::
|
231
241
|
|
232
242
|
### Monorepo
|
233
243
|
|
@@ -284,6 +294,49 @@ Add the following content to the `packages/app/vercel.json` file:
|
|
284
294
|
|
285
295
|
Just submit your code and deploy it using the Vercel platform.
|
286
296
|
|
297
|
+
## Github Pages
|
298
|
+
|
299
|
+
If you're creating a GitHub Pages for a repository without a custom domain, the page URL will follow this format: `http://<username>.github.io/<repository-name>`. Therefore, you need to add the following configuration in `modern.config.ts`:
|
300
|
+
|
301
|
+
```
|
302
|
+
import { defineConfig } from '@modern-js/app-tools';
|
303
|
+
|
304
|
+
export default defineConfig({
|
305
|
+
//...
|
306
|
+
server: {
|
307
|
+
baseUrl: "/<repository-name>"
|
308
|
+
},
|
309
|
+
output: {
|
310
|
+
assetPrefix: "/<repository-name>",
|
311
|
+
}
|
312
|
+
});
|
313
|
+
```
|
314
|
+
|
315
|
+
GitHub Pages supports two deployment ways: branch deployment or GitHub Actions deployment.
|
316
|
+
|
317
|
+
For branch deployment, follow these steps:
|
318
|
+
|
319
|
+
1. In the GitHub repository, navigate to Settings > Pages > Source > Deploy from a branch
|
320
|
+
2. Install the `gh-pages` as devDependency
|
321
|
+
3. Add the following script to `package.json`
|
322
|
+
```
|
323
|
+
"scripts": {
|
324
|
+
//...
|
325
|
+
"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"
|
326
|
+
}
|
327
|
+
```
|
328
|
+
|
329
|
+
4. Run `npm run deploy:gh-pages`
|
330
|
+
|
331
|
+
:::info
|
332
|
+
1. Running `MODERNJS_DEPLOY=ghPages modern deploy` will build the production output for GitHub in the .output directory.
|
333
|
+
2. You can refer to the [project](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)
|
334
|
+
|
335
|
+
:::
|
336
|
+
|
337
|
+
For GitHub Actions deployment, select Settings > Pages > Source > GitHub Actions, and add a workflow file to the project. You can refer to the [example](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr).
|
338
|
+
|
339
|
+
|
287
340
|
## Using Self-Built Node.js Server
|
288
341
|
|
289
342
|
Typically, we recommend using the built-in Node.js server of Modern.js to deploy applications. It supports hosting both pure frontend and full-stack projects, ensuring consistent performance in both development and production environments.
|
@@ -7,7 +7,9 @@ sidebar_position: 2
|
|
7
7
|
Modern.js routing is based on [React Router 6](https://reactrouter.com/en/main), offering file convention-based routing capabilities and supporting the industry-popular **nested routing** pattern. When an entry is recognized as [conventional routing](/guides/concept/entries.html#conventional-routing), Modern.js automatically generates the corresponding routing structure based on the file system.
|
8
8
|
|
9
9
|
:::note
|
10
|
+
|
10
11
|
The routing mentioned in this section all refers to conventional routing.
|
12
|
+
|
11
13
|
:::
|
12
14
|
|
13
15
|
## What is Nested Routing
|
@@ -38,7 +40,9 @@ In the `routes/` directory, subdirectory names are mapped to route URLs. Modern.
|
|
38
40
|
- `layout.tsx`: This is the layout component and controls the layout of all sub-routes in its directory by using `<Outlet>` to represent child components.
|
39
41
|
|
40
42
|
:::tip
|
43
|
+
|
41
44
|
`.ts`, `.js`, `.jsx`, or `.tsx` file extensions can be used for the above convention files.
|
45
|
+
|
42
46
|
:::
|
43
47
|
|
44
48
|
### Page
|
@@ -83,7 +87,9 @@ export default () => {
|
|
83
87
|
```
|
84
88
|
|
85
89
|
:::note
|
90
|
+
|
86
91
|
`<Outlet>` is an API provided by React Router 6. For more details, see [Outlet](https://reactrouter.com/en/main/components/outlet#outlet).
|
92
|
+
|
87
93
|
:::
|
88
94
|
|
89
95
|
Under different directory structures, the components represented by `<Outlet>` are also different. To illustrate the relationship between `<Layout>` and `<Outlet>`, let's consider the following directory structure:
|
@@ -219,7 +225,7 @@ When you visit `/blog/a` and no routes match, the page will render the `routes/b
|
|
219
225
|
</RootLayout>
|
220
226
|
```
|
221
227
|
|
222
|
-
If you want `/blog` to match the `blog/$.tsx` file as well, you need to remove the `blog.tsx` file from the same directory and ensure there are no other sub-routes under `blog`.
|
228
|
+
If you want `/blog` to match the `blog/$.tsx` file as well, you need to remove the `blog/layout.tsx` file from the same directory and ensure there are no other sub-routes under `blog`.
|
223
229
|
|
224
230
|
Similarly, you can use [useParams](/apis/app/runtime/router/router#useparams) to capture the remaining part of the URL in the `$.tsx` component.
|
225
231
|
|
@@ -480,6 +486,8 @@ The `prefetch` attribute has three optional values:
|
|
480
486
|
|
481
487
|
:::
|
482
488
|
|
489
|
+
import Motivation from '@site-docs-en/components/convention-routing-motivation';
|
490
|
+
|
483
491
|
<Motivation />
|
484
492
|
|
485
493
|
## FAQ
|
@@ -496,4 +504,34 @@ Additionally, when using conventional routing, make sure to use the API from `@m
|
|
496
504
|
If you must directly use the React Router package's API (e.g., route behavior wrapped in a unified npm package), you can set [`source.alias`](/configure/app/source/alias) to point `react-router` and `react-router-dom` to the project's dependencies, avoiding the issue of two versions of React Router.
|
497
505
|
:::
|
498
506
|
|
499
|
-
|
507
|
+
2. About `config` function and `init` function
|
508
|
+
|
509
|
+
:::warning Not Recommended
|
510
|
+
|
511
|
+
Modern.js early versions supported runtime configuration and initialization operations through `config` function and `init` function exported in route layout files. These methods are still **supported**, but we **strongly recommend** using the [Runtime Configuration File](/configure/app/runtime/0-intro) and [Runtime Plugin](/plugin/introduction.html#runtime-plugin) to implement the corresponding functions.
|
512
|
+
|
513
|
+
:::
|
514
|
+
|
515
|
+
**config**
|
516
|
+
|
517
|
+
In route components, you can add dynamic Runtime configuration by exporting a `config` function:
|
518
|
+
|
519
|
+
```tsx
|
520
|
+
// routes/layout.tsx
|
521
|
+
export const config = () => {
|
522
|
+
return {
|
523
|
+
// dynamic Runtime configuration
|
524
|
+
};
|
525
|
+
};
|
526
|
+
```
|
527
|
+
|
528
|
+
**init**
|
529
|
+
|
530
|
+
In route components, you can execute pre-rendering logic by exporting an `init` function:
|
531
|
+
|
532
|
+
```tsx
|
533
|
+
// routes/layout.tsx
|
534
|
+
export const init = () => {
|
535
|
+
// initialization logic
|
536
|
+
};
|
537
|
+
```
|
@@ -148,7 +148,7 @@ info Starting production server...
|
|
148
148
|
> Network: http://192.168.0.1:8080/
|
149
149
|
```
|
150
150
|
|
151
|
-
Open `http://localhost:
|
151
|
+
Open `http://localhost:8080/` in the browser, and the content should be consistent with that of `pnpm run dev`.
|
152
152
|
|
153
153
|
## Deployment
|
154
154
|
|
@@ -3,6 +3,14 @@ title: filename
|
|
3
3
|
configName: output.filename
|
4
4
|
---
|
5
5
|
|
6
|
+
:::warning
|
7
|
+
|
8
|
+
在 Modern.js 开发阶段,或是使用 Modern.js 服务器部署应用时,应避免使用该配置修改 html 产物文件名,会导致页面 404。
|
9
|
+
|
10
|
+
通常情况下,无需修改 html 产物文件名。常见的需求是将 `main.html` 修改为 `index.html`,请使用 [source.mainEntryName](/configure/app/source/main-entry-name)。
|
11
|
+
|
12
|
+
:::
|
13
|
+
|
6
14
|
# output.filename
|
7
15
|
|
8
16
|
- **类型:**
|
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
Modern.js 的运行时(Runtime)配置需集中在 `src/modern.runtime.ts` 文件中声明。
|
4
4
|
|
5
|
+
:::warning
|
6
|
+
|
7
|
+
使用 `src/modern.runtime.ts` 配置方式需要 Modern.js 版本 **MAJOR_VERSION.66.0** 或更高版本。
|
8
|
+
|
9
|
+
:::
|
10
|
+
|
5
11
|
:::info
|
6
12
|
如果项目中还没有此文件,请执行以下命令创建:
|
7
13
|
|
@@ -59,9 +65,7 @@ export default defineRuntimeConfig(entryName => {
|
|
59
65
|
使用 `src/modern.runtime.ts` 配置方式不支持导出异步函数,这与 Modern.js 的渲染方式有关。如果需要添加异步逻辑,请使用 **[Runtime 插件 (Runtime Plugin)](/plugin/introduction.html#runtime-插件)**。
|
60
66
|
:::
|
61
67
|
|
62
|
-
|
63
|
-
使用 `src/modern.runtime.ts` 配置方式需要 Modern.js 版本 **MAJOR_VERSION.66.0** 或更高版本。
|
64
|
-
:::
|
68
|
+
|
65
69
|
|
66
70
|
import RuntimeCliConfig from '@site-docs/components/runtime-cli-config';
|
67
71
|
|
@@ -4,7 +4,7 @@ sidebar_position: 4
|
|
4
4
|
---
|
5
5
|
# 数据缓存
|
6
6
|
|
7
|
-
`cache`
|
7
|
+
`cache` 函数可以让你缓存数据获取或计算的结果,相比整页[渲染缓存](/guides/basic-features/render/ssr-cache),它提供了更精细的数据粒度控制,并且适用于客户端渲染(CSR)、服务端渲染(SSR)、API 服务(BFF)等多种场景。
|
8
8
|
|
9
9
|
:::info
|
10
10
|
需要 x.65.5 及以上版本
|
@@ -12,6 +12,14 @@ sidebar_position: 4
|
|
12
12
|
|
13
13
|
## 基本用法
|
14
14
|
|
15
|
+
:::note
|
16
|
+
|
17
|
+
如果在 BFF 中使用 `cache` 函数,应该从 `@modern-js/server-runtime/cache` 导入相关函数
|
18
|
+
|
19
|
+
`import { cache } from '@modern-js/server-runtime/cache'`
|
20
|
+
|
21
|
+
:::
|
22
|
+
|
15
23
|
```ts
|
16
24
|
import { cache } from '@modern-js/runtime/cache';
|
17
25
|
import { fetchUserData } from './api';
|
@@ -33,6 +41,8 @@ const loader = async () => {
|
|
33
41
|
- `tag`: 用于标识缓存的标签,可以基于这个标签使缓存失效
|
34
42
|
- `maxAge`: 缓存的有效期 (毫秒)
|
35
43
|
- `revalidate`: 重新验证缓存的时间窗口(毫秒),与 HTTP Cache-Control 的 stale-while-revalidate 功能一致
|
44
|
+
- `getKey`: 简化的缓存键生成函数,根据函数参数生成缓存键
|
45
|
+
- `customKey`: 自定义缓存键生成函数,用于在函数引用变化时保持缓存
|
36
46
|
|
37
47
|
`options` 参数的类型如下:
|
38
48
|
|
@@ -41,6 +51,12 @@ interface CacheOptions {
|
|
41
51
|
tag?: string | string[];
|
42
52
|
maxAge?: number;
|
43
53
|
revalidate?: number;
|
54
|
+
getKey?: <Args extends any[]>(...args: Args) => string;
|
55
|
+
customKey?: <Args extends any[]>(options: {
|
56
|
+
params: Args;
|
57
|
+
fn: (...args: Args) => any;
|
58
|
+
generatedKey: string;
|
59
|
+
}) => string | symbol;
|
44
60
|
}
|
45
61
|
```
|
46
62
|
|
@@ -146,6 +162,193 @@ const getComplexStatistics = cache(
|
|
146
162
|
revalidateTag('dashboard-stats'); // 会使 getDashboardStats 函数和 getComplexStatistics 函数的缓存都失效
|
147
163
|
```
|
148
164
|
|
165
|
+
#### `getKey` 参数
|
166
|
+
|
167
|
+
`getKey` 参数用于自定义缓存键的生成方式,例如你可能只需要依赖函数参数的一部分来区分缓存。它是一个函数,接收与原始函数相同的参数,返回一个字符串作为缓存键:
|
168
|
+
|
169
|
+
```ts
|
170
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
171
|
+
import { fetchUserData } from './api';
|
172
|
+
|
173
|
+
const getUser = cache(
|
174
|
+
async (userId, options) => {
|
175
|
+
// 这里 options 可能包含很多配置,但我们只想根据 userId 缓存
|
176
|
+
return await fetchUserData(userId, options);
|
177
|
+
},
|
178
|
+
{
|
179
|
+
maxAge: CacheTime.MINUTE * 5,
|
180
|
+
// 只使用第一个参数(userId)作为缓存键
|
181
|
+
getKey: (userId, options) => userId,
|
182
|
+
}
|
183
|
+
);
|
184
|
+
|
185
|
+
// 下面两次调用会共享缓存,因为 getKey 只使用了 userId
|
186
|
+
await getUser(123, { language: 'zh' });
|
187
|
+
await getUser(123, { language: 'en' }); // 命中缓存,不会重新请求
|
188
|
+
|
189
|
+
// 不同的 userId 会使用不同的缓存
|
190
|
+
await getUser(456, { language: 'zh' }); // 不会命中缓存,会重新请求
|
191
|
+
```
|
192
|
+
|
193
|
+
你也可以使用 Modern.js 提供的 `generateKey` 函数配合 getKey 生成缓存的键:
|
194
|
+
|
195
|
+
:::info
|
196
|
+
|
197
|
+
Modern.js 中的 `generateKey` 函数确保即使对象属性顺序发生变化,也能生成一致的唯一键值,保证稳定的缓存
|
198
|
+
|
199
|
+
:::
|
200
|
+
|
201
|
+
```ts
|
202
|
+
import { cache, CacheTime, generateKey } from '@modern-js/runtime/cache';
|
203
|
+
import { fetchUserData } from './api';
|
204
|
+
|
205
|
+
const getUser = cache(
|
206
|
+
async (userId, options) => {
|
207
|
+
return await fetchUserData(userId, options);
|
208
|
+
},
|
209
|
+
{
|
210
|
+
maxAge: CacheTime.MINUTE * 5,
|
211
|
+
getKey: (userId, options) => generateKey(userId),
|
212
|
+
}
|
213
|
+
);
|
214
|
+
```
|
215
|
+
|
216
|
+
|
217
|
+
#### `customKey` 参数
|
218
|
+
|
219
|
+
`customKey` 参数用于定制缓存的键,它是一个函数,接收一个包含以下属性的对象,返回值必须是字符串或 Symbol 类型,将作为缓存的键:
|
220
|
+
|
221
|
+
- `params`:调用缓存函数时传入的参数数组
|
222
|
+
- `fn`:原始被缓存的函数引用
|
223
|
+
- `generatedKey`:框架基于入参自动生成的原始缓存键
|
224
|
+
|
225
|
+
:::info
|
226
|
+
|
227
|
+
一般在以下场景,缓存会失效:
|
228
|
+
1. 缓存的函数引用发生变化
|
229
|
+
2. 函数的入参发生变化
|
230
|
+
3. 不满足 maxAge
|
231
|
+
4. 调用了 `revalidateTag`
|
232
|
+
|
233
|
+
`customKey` 可以用在函数引用不同,但希望共享缓存的场景,如果只是自定义缓存键,推荐使用 `getKey`
|
234
|
+
|
235
|
+
:::
|
236
|
+
|
237
|
+
这在某些场景下非常有用,比如当函数引用发生变化时,但你希望仍然返回缓存的数据。
|
238
|
+
|
239
|
+
```ts
|
240
|
+
import { cache } from '@modern-js/runtime/cache';
|
241
|
+
import { fetchUserData } from './api';
|
242
|
+
|
243
|
+
// 不同的函数引用,但是通过 customKey 可以使它们共享一个缓存
|
244
|
+
const getUserA = cache(
|
245
|
+
fetchUserData,
|
246
|
+
{
|
247
|
+
maxAge: CacheTime.MINUTE * 5,
|
248
|
+
customKey: ({ params }) => {
|
249
|
+
// 返回一个稳定的字符串作为缓存的键
|
250
|
+
return `user-${params[0]}`;
|
251
|
+
},
|
252
|
+
}
|
253
|
+
);
|
254
|
+
|
255
|
+
// 即使函数引用变了,只要 customKey 返回相同的值,也会命中缓存
|
256
|
+
const getUserB = cache(
|
257
|
+
(...args) => fetchUserData(...args), // 新的函数引用
|
258
|
+
{
|
259
|
+
maxAge: CacheTime.MINUTE * 5,
|
260
|
+
customKey: ({ params }) => {
|
261
|
+
// 返回与 getUserA 相同的键
|
262
|
+
return `user-${params[0]}`;
|
263
|
+
},
|
264
|
+
}
|
265
|
+
);
|
266
|
+
|
267
|
+
// 即使 getUserA 和 getUserB 是不同的函数引用,但由于它们的 customKey 返回相同的值
|
268
|
+
// 所以当调用参数相同时,它们会共享缓存
|
269
|
+
const dataA = await getUserA(1);
|
270
|
+
const dataB = await getUserB(1); // 这里会命中缓存,不会再次发起请求
|
271
|
+
|
272
|
+
// 也可以使用 Symbol 作为缓存键(通常用于共享同一个应用内的缓存)
|
273
|
+
const USER_CACHE_KEY = Symbol('user-cache');
|
274
|
+
const getUserC = cache(
|
275
|
+
fetchUserData,
|
276
|
+
{
|
277
|
+
maxAge: CacheTime.MINUTE * 5,
|
278
|
+
customKey: () => USER_CACHE_KEY,
|
279
|
+
}
|
280
|
+
);
|
281
|
+
|
282
|
+
// 可以利用 generatedKey 参数在默认键的基础上进行修改
|
283
|
+
const getUserD = cache(
|
284
|
+
fetchUserData,
|
285
|
+
{
|
286
|
+
customKey: ({ generatedKey }) => `prefix-${generatedKey}`,
|
287
|
+
}
|
288
|
+
);
|
289
|
+
```
|
290
|
+
|
291
|
+
#### `onCache` 参数
|
292
|
+
|
293
|
+
`onCache` 参数允许你跟踪缓存统计信息,例如命中率。这是一个回调函数,接收有关每次缓存操作的信息,包括状态、键、参数和结果。
|
294
|
+
|
295
|
+
```ts
|
296
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
297
|
+
|
298
|
+
// 跟踪缓存统计
|
299
|
+
const stats = {
|
300
|
+
total: 0,
|
301
|
+
hits: 0,
|
302
|
+
misses: 0,
|
303
|
+
stales: 0,
|
304
|
+
hitRate: () => stats.hits / stats.total
|
305
|
+
};
|
306
|
+
|
307
|
+
const getUser = cache(
|
308
|
+
fetchUserData,
|
309
|
+
{
|
310
|
+
maxAge: CacheTime.MINUTE * 5,
|
311
|
+
onCache({ status, key, params, result }) {
|
312
|
+
// status 可以是 'hit'、'miss' 或 'stale'
|
313
|
+
stats.total++;
|
314
|
+
|
315
|
+
if (status === 'hit') {
|
316
|
+
stats.hits++;
|
317
|
+
} else if (status === 'miss') {
|
318
|
+
stats.misses++;
|
319
|
+
} else if (status === 'stale') {
|
320
|
+
stats.stales++;
|
321
|
+
}
|
322
|
+
|
323
|
+
console.log(`缓存${status === 'hit' ? '命中' : status === 'miss' ? '未命中' : '陈旧'},键:${String(key)}`);
|
324
|
+
console.log(`当前命中率:${stats.hitRate() * 100}%`);
|
325
|
+
}
|
326
|
+
}
|
327
|
+
);
|
328
|
+
|
329
|
+
// 使用示例
|
330
|
+
await getUser(1); // 缓存未命中
|
331
|
+
await getUser(1); // 缓存命中
|
332
|
+
await getUser(2); // 缓存未命中
|
333
|
+
```
|
334
|
+
|
335
|
+
`onCache` 回调接收一个包含以下属性的对象:
|
336
|
+
|
337
|
+
- `status`: 缓存操作状态,可以是:
|
338
|
+
- `hit`: 缓存命中,返回缓存内容
|
339
|
+
- `miss`: 缓存未命中,执行函数并缓存结果
|
340
|
+
- `stale`: 缓存命中但数据陈旧,返回缓存内容同时在后台重新验证
|
341
|
+
- `key`: 缓存键,可能是 `customKey` 的结果或默认生成的键
|
342
|
+
- `params`: 传递给缓存函数的参数
|
343
|
+
- `result`: 结果数据(来自缓存或新计算的)
|
344
|
+
|
345
|
+
这个回调只在提供 `options` 参数时被调用。当使用无 options 的缓存函数时,不会调用 `onCache` 回调。
|
346
|
+
|
347
|
+
`onCache` 回调对以下场景非常有用:
|
348
|
+
- 监控缓存性能
|
349
|
+
- 计算命中率
|
350
|
+
- 记录缓存操作
|
351
|
+
- 实现自定义指标
|
149
352
|
|
150
353
|
### 存储
|
151
354
|
|
@@ -164,3 +367,4 @@ configureCache({
|
|
164
367
|
maxSize: CacheSize.MB * 10, // 10MB
|
165
368
|
});
|
166
369
|
```
|
370
|
+
|
@@ -7,7 +7,7 @@ sidebar_position: 15
|
|
7
7
|
目前,Modern.js 提供了两种部署方式:
|
8
8
|
|
9
9
|
- 你可以将应用自行托管在包含 Node.js 环境的容器中,这为应用提供了部署的灵活性。
|
10
|
-
- 你也可以通过平台部署应用,目前 Modern.js 官方支持了 [Netlify](https://www.netlify.com/)
|
10
|
+
- 你也可以通过平台部署应用,目前 Modern.js 官方支持了 [Netlify](https://www.netlify.com/), [Vercel](https://vercel.com/) 和 [Github pages](https://pages.github.com/) 平台。
|
11
11
|
|
12
12
|
:::info
|
13
13
|
目前 Modern.js 仅支持在 Node.js 环境中运行,未来将提供更多运行时环境的支持。
|
@@ -26,6 +26,7 @@ MODERNJS_DEPLOY=netlify npx modern deploy
|
|
26
26
|
在 Modern.js 官方支持的部署平台中部署时,无需指定环境变量。
|
27
27
|
:::
|
28
28
|
|
29
|
+
|
29
30
|
## ModernJS 内置 Node.js 服务器
|
30
31
|
|
31
32
|
### 单仓库项目
|
@@ -107,6 +108,11 @@ Netlify 是一个流行的 Web 开发平台,专为构建、发布和维护现
|
|
107
108
|
command = "modern deploy"
|
108
109
|
```
|
109
110
|
|
111
|
+
:::info
|
112
|
+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。
|
113
|
+
|
114
|
+
:::
|
115
|
+
|
110
116
|
在 Netlify 平台上添加项目,部署即可。
|
111
117
|
|
112
118
|
### 全栈项目
|
@@ -126,10 +132,14 @@ Netlify 是一个流行的 Web 开发平台,专为构建、发布和维护现
|
|
126
132
|
```
|
127
133
|
|
128
134
|
:::info
|
129
|
-
目前 Modern.js 还不支持在 Netlify Edge Functions 进行部署,我们将在后续的版本中支持。
|
135
|
+
1. 目前 Modern.js 还不支持在 Netlify Edge Functions 进行部署,我们将在后续的版本中支持。
|
136
|
+
2. 你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr)。
|
137
|
+
|
130
138
|
:::
|
131
139
|
|
132
140
|
|
141
|
+
|
142
|
+
|
133
143
|
### Monorepo 项目
|
134
144
|
|
135
145
|
:::info
|
@@ -207,6 +217,11 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
|
|
207
217
|
|
208
218
|
<img src="https://sf16-sg.tiktokcdn.com/obj/eden-sg/lmeh7nuptpfnuhd/vercel-framework-preset.png" />
|
209
219
|
|
220
|
+
:::info
|
221
|
+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。
|
222
|
+
|
223
|
+
:::
|
224
|
+
|
210
225
|
### 全栈项目
|
211
226
|
|
212
227
|
全栈项目是指使用了自定义 Web Server、SSR、BFF 的项目,这些项目需要部署在 **Vercel Functions** 上。
|
@@ -214,12 +229,13 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
|
|
214
229
|
全栈项目除了按照[纯前端项目](#纯前端项目)的方式配置 `vercel.json` 外,有两点需要注意:
|
215
230
|
|
216
231
|
1. 当前,Modern.js 还不支持在 Vercel 平台上部署 BFF 项目,我们将在后续的版本中支持。
|
217
|
-
2.
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
232
|
+
2. 函数运行的 node.js 版本由项目在 Vercel 平台配置决定。
|
233
|
+
|
234
|
+
|
235
|
+
:::info
|
236
|
+
你可参考部署[项目示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-ssr)。
|
237
|
+
|
238
|
+
:::
|
223
239
|
|
224
240
|
### Monorepo 项目
|
225
241
|
|
@@ -273,6 +289,38 @@ Vercel 是一个面向现代 Web 应用的部署平台,它提供了丰富的
|
|
273
289
|
|
274
290
|
提交你的代码,使用 Vercel 平台部署即可。
|
275
291
|
|
292
|
+
## Github Pages
|
293
|
+
|
294
|
+
如果你要为一个仓库常见 Github 页面,并且你没有自定义域名,则该页面的 URL 将会是以下格式:`http://<username>.github.io/<repository-name>`,所以需要在 `modern.config.ts` 中添加
|
295
|
+
以下配置:
|
296
|
+
```ts
|
297
|
+
import { defineConfig } from '@modern-js/app-tools';
|
298
|
+
|
299
|
+
export default defineConfig({
|
300
|
+
//...
|
301
|
+
server:{
|
302
|
+
baseUrl: "/<repository-name>"
|
303
|
+
},
|
304
|
+
output: {
|
305
|
+
assetPrefix: "/<repository-name>",
|
306
|
+
}
|
307
|
+
});
|
308
|
+
```
|
309
|
+
|
310
|
+
Github Pages 支持两种部署方式,通过分支部署或通过 Github Actions 部署,如果通过分支部署,可以使用以下步骤:
|
311
|
+
1. 在 github 仓库中,选择 `Settings > Pages > Source > Deploy from a branch`。
|
312
|
+
2. 安装 `gh-pages` 依赖作为开发依赖。
|
313
|
+
3. 在 package.json 的 `scripts` 中添加 `"deploy:gh-pages": "MODERNJS_DEPLOY=ghPages modern deploy && gh-pages -d .output"`。
|
314
|
+
4. 执行 `npm run deploy:gh-pages`。
|
315
|
+
|
316
|
+
:::info
|
317
|
+
1. 执行 `MODERNJS_DEPLOY=ghPages modern deploy`,Modern.js 会把可用于 github 部署的产物构建到 `.output` 目录。
|
318
|
+
2. 可以参考项目[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。
|
319
|
+
|
320
|
+
:::
|
321
|
+
|
322
|
+
如果通过 Github Actions 部署,可以选择 Settings > Pages > Source > GitHub Actions,并在项目中添加 workflow 文件,可参考[示例](https://github.com/web-infra-dev/modern-js-examples/tree/main/examples/modern-js-deploy-csr)。
|
323
|
+
|
276
324
|
|
277
325
|
## 自建 Node.js 服务器
|
278
326
|
|
@@ -7,7 +7,9 @@ sidebar_position: 2
|
|
7
7
|
Modern.js 的路由基于 [React Router 6](https://reactrouter.com/en/main),提供了基于文件约定的路由能力,并支持了业界流行的约定式路由模式:**嵌套路由**。当入口被识别为 [约定式路由](/guides/concept/entries.html#约定式路由) 时,Modern.js 会自动基于文件系统,生成对应的路由结构。
|
8
8
|
|
9
9
|
:::note
|
10
|
+
|
10
11
|
本小节提到的路由,都是约定式路由。
|
12
|
+
|
11
13
|
:::
|
12
14
|
|
13
15
|
## 什么是嵌套路由
|
@@ -84,7 +86,9 @@ export default () => {
|
|
84
86
|
```
|
85
87
|
|
86
88
|
:::note
|
89
|
+
|
87
90
|
`<Outlet>` 是 React Router 6 中提供的 API,详情可以查看 [Outlet](https://reactrouter.com/en/main/components/outlet#outlet)。
|
91
|
+
|
88
92
|
:::
|
89
93
|
|
90
94
|
不同目录结构下,`<Outlet>` 所代表的组件也不同。为了方便介绍 `<Layout>` 与 `<Outlet>` 的关系,以下面的文件目录举例:
|
@@ -192,11 +196,15 @@ export default Blog;
|
|
192
196
|
如果在某个子目录下存在 `$.tsx` 文件,该文件会作为通配路由组件,当没有匹配的路由时,会渲染该路由组件。
|
193
197
|
|
194
198
|
:::note
|
199
|
+
|
195
200
|
`$.tsx` 可以认为是一种特殊的 `<Page>` 组件,如果路由无法匹配,则 `$.tsx` 会作为 `<Layout>` 的子组件渲染。
|
201
|
+
|
196
202
|
:::
|
197
203
|
|
198
204
|
:::warning
|
205
|
+
|
199
206
|
如果当前目录下不存在 `<Layout>` 组件时,则 `$.tsx` 不会生效。
|
207
|
+
|
200
208
|
:::
|
201
209
|
|
202
210
|
例如以下目录结构:
|
@@ -487,6 +495,78 @@ import Motivation from '@site-docs/components/convention-routing-motivation';
|
|
487
495
|
|
488
496
|
<Motivation />
|
489
497
|
|
498
|
+
## 升级到 react-router v7
|
499
|
+
|
500
|
+
React Router v7 相比 React Router v6 减少了包体积(小约 15%),提供了更高效的路由匹配算法,对 React 19 和 TypeScript 也提供了更好的支持,
|
501
|
+
相比 React Router v6 breaking change 非常少,同时 Modern.js 也对两个版本做了兼容,只需在项目中安装并注册相应的插件即可无缝升级。
|
502
|
+
|
503
|
+
:::info
|
504
|
+
|
505
|
+
更多 react router v6 到 react router v7 的变更,请查看[文档](https://reactrouter.com/upgrading/v6#upgrade-to-v7)
|
506
|
+
|
507
|
+
:::
|
508
|
+
|
509
|
+
### 环境要求
|
510
|
+
|
511
|
+
React Router v7 对环境有一定要求:
|
512
|
+
|
513
|
+
- Node.js 20+
|
514
|
+
- React 18+
|
515
|
+
- React DOM 18+
|
516
|
+
|
517
|
+
### 安装插件
|
518
|
+
|
519
|
+
首先,安装 Modern.js 的 React Router v7 插件:
|
520
|
+
|
521
|
+
```bash
|
522
|
+
pnpm add @modern-js/plugin-router-v7
|
523
|
+
```
|
524
|
+
|
525
|
+
### 配置插件
|
526
|
+
|
527
|
+
在 `modern.config.ts` 中注册插件:
|
528
|
+
|
529
|
+
```ts title="modern.config.ts"
|
530
|
+
import { routerPlugin } from '@modern-js/plugin-router-v7';
|
531
|
+
|
532
|
+
export default {
|
533
|
+
runtime: {
|
534
|
+
router: true,
|
535
|
+
},
|
536
|
+
plugins: [routerPlugin()],
|
537
|
+
};
|
538
|
+
```
|
539
|
+
|
540
|
+
### 修改代码
|
541
|
+
|
542
|
+
在 react router v7 中,不需要再使用 `defer` API 了,直接在 data loader 中返回数据即可:
|
543
|
+
|
544
|
+
```ts title="routes/page.data.ts"
|
545
|
+
import { defer } from '@modern-js/runtime/router';
|
546
|
+
|
547
|
+
export const loader = async ({ params }) => {
|
548
|
+
// 推荐的 v7 风格
|
549
|
+
const user = fetchUser(params.id)
|
550
|
+
return { user };
|
551
|
+
|
552
|
+
// v6 风格,Modern.js 做了兼容,仍然可以继续使用
|
553
|
+
return defer({ data: 'hello' });
|
554
|
+
};
|
555
|
+
```
|
556
|
+
|
557
|
+
react router v7 同样废弃了 `json` API:
|
558
|
+
|
559
|
+
```ts title="routes/page.data.ts"
|
560
|
+
export const loader = async ({ params }) => {
|
561
|
+
// 推荐的 v7 风格
|
562
|
+
return { data: 'hello' };
|
563
|
+
|
564
|
+
// v6 风格,Modern.js 做了兼容,仍然可以继续使用
|
565
|
+
return json({ data: 'hello' });
|
566
|
+
};
|
567
|
+
```
|
568
|
+
|
569
|
+
|
490
570
|
## 常见问题
|
491
571
|
|
492
572
|
1. 为什么要提供 `@modern-js/runtime/router` 来导出 React Router API ?
|
@@ -498,7 +578,39 @@ import Motivation from '@site-docs/components/convention-routing-motivation';
|
|
498
578
|
在使用约定式路由的情况下,务必使用 `@modern-js/runtime/router` 中的 API,不直接使用 React Router 的 API。因为 Modern.js 内部会安装 React Router,如果应用中使用了 React Router 的 API,可能会导致两个版本的 React Router 同时存在,出现不符合预期的行为。
|
499
579
|
|
500
580
|
:::note
|
581
|
+
|
501
582
|
如果应用中必须直接使用 React Router 包的 API,例如部分路由行为被封装在统一的 npm 包中,那应用可以通过设置 [`source.alias`](/configure/app/source/alias),将 `react-router` 和 `react-router-dom` 统一指向项目的依赖,避免两个版本的 React Router 同时存在的问题。
|
583
|
+
|
502
584
|
:::
|
503
585
|
|
586
|
+
2. 关于 `config` 函数和 `init` 函数的说明
|
587
|
+
|
588
|
+
:::warning 不推荐使用
|
504
589
|
|
590
|
+
Modern.js 早期版本支持在路由 layout 文件中通过导出 `config` 函数和 `init` 函数进行运行时配置和执行初始化操作。这些方式目前仍然**被支持**,但我们**强烈推荐**使用 [Runtime 配置文件](/configure/app/runtime/0-intro) 和 [Runtime 插件](/plugin/introduction.html#runtime-插件) 实现对应功能。
|
591
|
+
|
592
|
+
:::
|
593
|
+
|
594
|
+
**config**
|
595
|
+
|
596
|
+
在路由组件中,你可以通过导出 `config` 函数来添加动态 Runtime 配置:
|
597
|
+
|
598
|
+
```tsx
|
599
|
+
// routes/layout.tsx
|
600
|
+
export const config = () => {
|
601
|
+
return {
|
602
|
+
// 动态 Runtime 配置
|
603
|
+
};
|
604
|
+
};
|
605
|
+
```
|
606
|
+
|
607
|
+
**init**
|
608
|
+
|
609
|
+
在路由组件中,你可以通过导出 `init` 函数来执行预渲染逻辑:
|
610
|
+
|
611
|
+
```tsx
|
612
|
+
// routes/layout.tsx
|
613
|
+
export const init = () => {
|
614
|
+
// 初始化逻辑
|
615
|
+
};
|
616
|
+
```
|
package/package.json
CHANGED
@@ -15,20 +15,20 @@
|
|
15
15
|
"modern",
|
16
16
|
"modern.js"
|
17
17
|
],
|
18
|
-
"version": "2.67.
|
18
|
+
"version": "2.67.4",
|
19
19
|
"publishConfig": {
|
20
20
|
"registry": "https://registry.npmjs.org/",
|
21
21
|
"access": "public"
|
22
22
|
},
|
23
23
|
"dependencies": {
|
24
24
|
"mermaid": "^11.4.1",
|
25
|
-
"@modern-js/sandpack-react": "2.67.
|
25
|
+
"@modern-js/sandpack-react": "2.67.4"
|
26
26
|
},
|
27
27
|
"devDependencies": {
|
28
28
|
"@rspress/shared": "1.43.11",
|
29
29
|
"@types/fs-extra": "9.0.13",
|
30
30
|
"@types/node": "^16",
|
31
|
-
"classnames": "^2",
|
31
|
+
"classnames": "^2.5.1",
|
32
32
|
"clsx": "^1.2.1",
|
33
33
|
"fs-extra": "^10",
|
34
34
|
"react": "^18.3.1",
|
File without changes
|
File without changes
|