@modern-js/main-doc 2.65.1 → 2.65.3
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/components/internal-logger.mdx +25 -0
- package/docs/en/components/internal-metrics.mdx +20 -0
- package/docs/en/configure/app/runtime/master-app.mdx +1 -1
- package/docs/en/guides/advanced-features/_meta.json +7 -0
- package/docs/en/guides/advanced-features/server-monitor/_meta.json +1 -0
- package/docs/en/guides/advanced-features/server-monitor/logger.mdx +30 -0
- package/docs/en/guides/advanced-features/server-monitor/metrics.mdx +41 -0
- package/docs/en/guides/advanced-features/server-monitor/monitors.mdx +157 -0
- package/docs/en/guides/basic-features/data/_meta.json +1 -1
- package/docs/en/guides/basic-features/data/data-cache.mdx +169 -0
- package/docs/zh/components/internal-logger.mdx +25 -0
- package/docs/zh/components/internal-metrics.mdx +20 -0
- package/docs/zh/configure/app/runtime/master-app.mdx +1 -1
- package/docs/zh/guides/advanced-features/_meta.json +7 -0
- package/docs/zh/guides/advanced-features/server-monitor/_meta.json +1 -0
- package/docs/zh/guides/advanced-features/server-monitor/logger.mdx +32 -0
- package/docs/zh/guides/advanced-features/server-monitor/metrics.mdx +42 -0
- package/docs/zh/guides/advanced-features/server-monitor/monitors.mdx +157 -0
- package/docs/zh/guides/basic-features/data/_meta.json +1 -1
- package/docs/zh/guides/basic-features/data/data-cache.mdx +162 -0
- package/i18n.json +4 -0
- package/package.json +4 -4
@@ -0,0 +1,25 @@
|
|
1
|
+
In Modern.js, log events are handled by `LoggerMonitor`, which outputs logs to the console.
|
2
|
+
|
3
|
+
:::info
|
4
|
+
The built-in `LoggerMonitor` depends on the [rslog](https://www.npmjs.com/package/rslog) library.
|
5
|
+
:::
|
6
|
+
|
7
|
+
For example, intentionally throwing an error in the project:
|
8
|
+
|
9
|
+
```tsx title=routes/page.tsx
|
10
|
+
import './index.css';
|
11
|
+
|
12
|
+
const Index = () => <div className="container-box">{a}</div>;
|
13
|
+
|
14
|
+
export default Index;
|
15
|
+
```
|
16
|
+
|
17
|
+
If running normally, you can see the following output in the console:
|
18
|
+
|
19
|
+
```shell
|
20
|
+
> Local: http://localhost:8080/
|
21
|
+
> press h + enter to show shortcuts
|
22
|
+
|
23
|
+
error SSR Error - App Prerender, error = ReferenceError: a is not defined
|
24
|
+
at Index (/somepath/page.tsx:3:1)
|
25
|
+
```
|
@@ -0,0 +1,20 @@
|
|
1
|
+
In Modern.js, metric events are also handled by `LoggerMonitor`, which outputs metrics to the console in a specific format.
|
2
|
+
|
3
|
+
:::info
|
4
|
+
The built-in `LoggerMonitor` depends on the [rslog](https://www.npmjs.com/package/rslog) library.
|
5
|
+
:::
|
6
|
+
|
7
|
+
The `rslog` instance initialized in Modern.js outputs logs at `debug` level and above in development environment by default, and outputs all logs in production environment. In the built-in `LoggerMonitor`, all metric events are output as Debug logs. Therefore, if you want to view metric event information in the development environment, you need to add additional environment variables.
|
8
|
+
|
9
|
+
Developers can add the environment variable `DEBUG=true` when running the `dev` command. If running normally, after accessing, you can see the following output in the console:
|
10
|
+
|
11
|
+
```shell
|
12
|
+
> Local: http://localhost:8080/
|
13
|
+
> press h + enter to show shortcuts
|
14
|
+
|
15
|
+
debug SSR Debug - server-loader, cost: 2.000094, req.url = /
|
16
|
+
debug SSR Debug - ssr-prerender, cost: 6.99997, req.url = /
|
17
|
+
debug SSR Debug - ssr-render-html, cost: 0.999927, req.url = /
|
18
|
+
debug Debug - server-handle-request, cost: 19.999981, req.url = /
|
19
|
+
debug --> GET / 200 90ms
|
20
|
+
```
|
@@ -37,4 +37,4 @@ interface AppInfo {
|
|
37
37
|
|
38
38
|
In the `masterApp` configuration, developers can pass through options supported by Garfish
|
39
39
|
|
40
|
-
All supported configuration options [see here](https://garfishjs.org/api/run
|
40
|
+
All supported configuration options [see here](https://www.garfishjs.org/api/run.html).
|
@@ -6,6 +6,7 @@
|
|
6
6
|
"label": "use-bff",
|
7
7
|
"collapsed": true
|
8
8
|
},
|
9
|
+
|
9
10
|
{
|
10
11
|
"type": "dir",
|
11
12
|
"name": "page-performance",
|
@@ -16,5 +17,11 @@
|
|
16
17
|
"compatibility",
|
17
18
|
"low-level",
|
18
19
|
"source-build",
|
20
|
+
{
|
21
|
+
"type": "dir",
|
22
|
+
"name": "server-monitor",
|
23
|
+
"label": "server-monitor",
|
24
|
+
"collapsed": true
|
25
|
+
},
|
19
26
|
"web-server"
|
20
27
|
]
|
@@ -0,0 +1 @@
|
|
1
|
+
["monitors", "logger", "metrics"]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Logs Events
|
2
|
+
|
3
|
+
Log events are distributed by Modern.js as events of type `log`.
|
4
|
+
|
5
|
+
## Built-in Events
|
6
|
+
|
7
|
+
Based on server-side runtime logic, Modern.js provides the following log events:
|
8
|
+
|
9
|
+
| Stage | Message | Level |
|
10
|
+
| --------------- | -------------------------------------------- | ----- |
|
11
|
+
| RENDER_HTML | App Render To HTML | error |
|
12
|
+
| RENDER_STREAM | An error occurs during streaming SSR | error |
|
13
|
+
| RENDER_SHELL | An error occurs during streaming render shell | error |
|
14
|
+
|
15
|
+
Modern.js also retains SSR logs from legacy versions using `useLoader`:
|
16
|
+
|
17
|
+
| Stage | Message | Level |
|
18
|
+
| ------------- | ------------------------------- | ----- |
|
19
|
+
| PRERENDER | App Prerender | error |
|
20
|
+
| USE_LOADER | App run useLoader | error |
|
21
|
+
|
22
|
+
:::tip
|
23
|
+
The `useLoader` API is now deprecated. We recommend migrating to convention-based routing and using Data Loaders for data fetching. Applications already using Data Loaders can enable [`ssr.disablePrerender`](/configure/app/server/ssr.html#object-type) to disable prerendering and improve SSR performance.
|
24
|
+
:::
|
25
|
+
|
26
|
+
## Built-in Monitor
|
27
|
+
|
28
|
+
import InternalLogger from '@site-docs-en/components/internal-logger.mdx';
|
29
|
+
|
30
|
+
<InternalLogger />
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Metrics Events
|
2
|
+
|
3
|
+
Metric events are distributed by Monitors as events of type `timing` or `counter`.
|
4
|
+
|
5
|
+
## Built-in Events
|
6
|
+
|
7
|
+
When SSR is enabled, we need to monitor server-side phase durations and have the capability to diagnose server-side issues.
|
8
|
+
|
9
|
+
Based on server-side runtime logic, Modern.js provides the following metric events:
|
10
|
+
|
11
|
+
| Key | Description |
|
12
|
+
| ------------------------ | ------------------------ |
|
13
|
+
| server-handle-request | EdenX Server request handling duration |
|
14
|
+
| ssr-render-shell | [SSR] When using Streaming SSR, React renders a shell for early streaming. This marks shell rendering completion time |
|
15
|
+
| ssr-render-html | [SSR] Time taken by React to render component tree to HTML (typically under 50ms) |
|
16
|
+
| server-middleware | Total execution time of EdenX custom server middlewares |
|
17
|
+
| server-loader | Server-side Data Loader total duration |
|
18
|
+
| server-loader-#id | Individual Data Loader durations on server-side |
|
19
|
+
| server-loader-navigation | Server-side Data Loader duration during client navigation |
|
20
|
+
|
21
|
+
Modern.js server workflow diagram:
|
22
|
+
|
23
|
+

|
24
|
+
|
25
|
+
## Built-in Monitor
|
26
|
+
|
27
|
+
import InternalMetrics from '@site-docs-en/components/internal-metrics.mdx';
|
28
|
+
|
29
|
+
<InternalMetrics />
|
30
|
+
|
31
|
+
## Server-Timing
|
32
|
+
|
33
|
+
Modern.js injects phase metrics as Server-Timing headers into HTML responses.
|
34
|
+
|
35
|
+
Developers can retrieve these metrics using the [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance) in browsers:
|
36
|
+
|
37
|
+
```ts
|
38
|
+
const navigation = performance.getEntriesByType('navigation')[0];
|
39
|
+
const serverTiming = navigation.serverTiming;
|
40
|
+
console.log(serverTiming);
|
41
|
+
```
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# Monitors
|
2
|
+
|
3
|
+
Modern.js is a full-stack framework that supports both client-side and server-side development. When server-side rendering (SSR), the framework automatically injects additional logs and metrics during server runtime to help production issue diagnosis.
|
4
|
+
|
5
|
+
As server code operates in Node.js environments, developers cannot directly utilize browser consoles for troubleshooting. Given that different projects may adopt varied logging libraries or data reporting platforms, the framework provides a unified approach for developers to manage built-in logging and metric collection.
|
6
|
+
|
7
|
+
The Monitors module in Modern.js empowers application monitoring through two core capabilities: Monitor registration and monitoring event distribution. When developers invoke Monitors APIs, the framework propagates corresponding monitoring events to all registered Monitors.
|
8
|
+
|
9
|
+
:::note
|
10
|
+
Modern.js ships with a default Monitor implementation, while simultaneously allowing developers to register custom Monitors.
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Monitors Type
|
14
|
+
|
15
|
+
The Monitors module is defined with the following types, where the `push` method is used to register Monitor and other methods are used to dispatch monitoring events.
|
16
|
+
|
17
|
+
```ts
|
18
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
19
|
+
export interface LogEvent {
|
20
|
+
type: 'log';
|
21
|
+
payload: {
|
22
|
+
level: LogLevel;
|
23
|
+
message: string;
|
24
|
+
args?: any[];
|
25
|
+
};
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface TimingEvent {
|
29
|
+
type: 'timing';
|
30
|
+
payload: {
|
31
|
+
name: string;
|
32
|
+
dur: number;
|
33
|
+
desc?: string;
|
34
|
+
args?: any[];
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
export interface CounterEvent {
|
39
|
+
type: 'counter';
|
40
|
+
payload: {
|
41
|
+
name: string;
|
42
|
+
args?: any[];
|
43
|
+
};
|
44
|
+
}
|
45
|
+
|
46
|
+
export type MonitorEvent = LogEvent | TimingEvent | CounterEvent;
|
47
|
+
|
48
|
+
export type CoreMonitor = (event: MonitorEvent) => void;
|
49
|
+
|
50
|
+
export interface Monitors {
|
51
|
+
// Register Monitor
|
52
|
+
push(monitor: CoreMonitor): void;
|
53
|
+
|
54
|
+
// Log Event
|
55
|
+
error(message: string, ...args: any[]): void;
|
56
|
+
warn(message: string, ...args: any[]): void;
|
57
|
+
debug(message: string, ...args: any[]): void;
|
58
|
+
info(message: string, ...args: any[]): void;
|
59
|
+
trace(message: string,...args: any[]): void;
|
60
|
+
|
61
|
+
// Metrics Event
|
62
|
+
timing(name: string, dur: number, ...args: any[]): void;
|
63
|
+
counter(name: string, ...args: any[]): void
|
64
|
+
}
|
65
|
+
```
|
66
|
+
|
67
|
+
## Internal Monitor
|
68
|
+
|
69
|
+
Modern.js comes with a built-in default Monitor, where different events trigger different behaviors in the built-in Monitor. For more details, see:
|
70
|
+
|
71
|
+
- [Log Event](/guides/advanced-features/server-monitor/logger)
|
72
|
+
- [Metrics Event](/guides/advanced-features/server-monitor/metrics)
|
73
|
+
|
74
|
+
|
75
|
+
## Register Monitors
|
76
|
+
|
77
|
+
Developers can register their own Monitors using the `push` API, but this can only be done in [server middleware](/apis/app/runtime/web-server/unstable_middleware) or server plugins. Registration is not available in Data Loaders, components, or init functions.
|
78
|
+
|
79
|
+
:::note
|
80
|
+
Server plugins are currently not available, and documentation will be added in the future.
|
81
|
+
:::
|
82
|
+
|
83
|
+
```ts title="server/index.ts"
|
84
|
+
import type { CoreMonitors } from '@modern-js/types';
|
85
|
+
const injectMonitorMiddleware = (c) => {
|
86
|
+
const monitors = c.get('monitors');
|
87
|
+
const myMonitor = (event: MonitorEvent) => {
|
88
|
+
if (event.type === 'log') {
|
89
|
+
// some code
|
90
|
+
} else {
|
91
|
+
// some other code
|
92
|
+
}
|
93
|
+
}
|
94
|
+
monitors.push(myMonitor);
|
95
|
+
return next();
|
96
|
+
}
|
97
|
+
|
98
|
+
export const middlewares = [injectMonitorMiddleware]
|
99
|
+
```
|
100
|
+
|
101
|
+
## Use Monitors
|
102
|
+
|
103
|
+
Modern.js allows developers to invoke Monitors in Data Loaders and components.
|
104
|
+
|
105
|
+
:::tip
|
106
|
+
Monitors can only be invoked in Node.js environments, and calling them in browser environments will have no effect.
|
107
|
+
:::
|
108
|
+
|
109
|
+
In Data Loaders, developers can use them as follows:
|
110
|
+
|
111
|
+
```ts title="routes/page.data.ts"
|
112
|
+
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
113
|
+
import { getMonitors } from '@modern-js/runtime';
|
114
|
+
const loader = async ({ context }: LoaderFunctionArgs) => {
|
115
|
+
const monitors = getMonitors();
|
116
|
+
const start = Date.now();
|
117
|
+
try {
|
118
|
+
await fetch(...);
|
119
|
+
monitors.timing('loader_fetch_timing', Date.now() - start);
|
120
|
+
} catch(e) {
|
121
|
+
monitors.error(e);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
126
|
+
When invoking Monitors in components, you need to determine whether the current runtime environment is Node.js:
|
127
|
+
|
128
|
+
```tsx title="routes/page.tsx"
|
129
|
+
import { getMonitors } from '@modern-js/runtime';
|
130
|
+
const Page = () => {
|
131
|
+
const { context } = useRuntimeContext();
|
132
|
+
if (process.env.MODERN_TARGET === 'node') {
|
133
|
+
const monitors = getMonitors();
|
134
|
+
monitors.info();
|
135
|
+
}
|
136
|
+
return <div>Hello World</div>
|
137
|
+
}
|
138
|
+
export default Page;
|
139
|
+
```
|
140
|
+
|
141
|
+
In middleware, we can also invoke Monitors, but the approach differs from runtime code as it requires accessing them through `context`:
|
142
|
+
|
143
|
+
```ts title="server/index.ts"
|
144
|
+
import { UnstableMiddleware } from '@modern-js/runtime/server';
|
145
|
+
|
146
|
+
const time: UnstableMiddleware = async (c, next) => {
|
147
|
+
const start = Date.now();
|
148
|
+
await next();
|
149
|
+
const end = Date.now();
|
150
|
+
const monitors = c.get('monitors');
|
151
|
+
monitors.info(`${end - start}`);
|
152
|
+
};
|
153
|
+
|
154
|
+
export const unstableMiddleware: UnstableMiddleware[] = [time];
|
155
|
+
```
|
156
|
+
|
157
|
+
|
@@ -1 +1 @@
|
|
1
|
-
["data-fetch", "data-write"]
|
1
|
+
["data-fetch", "data-write", "data-cache"]
|
@@ -0,0 +1,169 @@
|
|
1
|
+
---
|
2
|
+
title: Data Caching
|
3
|
+
sidebar_position: 4
|
4
|
+
---
|
5
|
+
# Data Caching
|
6
|
+
|
7
|
+
The `cache` function allows you to cache the results of data fetching or computation.
|
8
|
+
|
9
|
+
## Basic Usage
|
10
|
+
|
11
|
+
```ts
|
12
|
+
import { cache } from '@modern-js/runtime/cache';
|
13
|
+
import { fetchUserData } from './api';
|
14
|
+
|
15
|
+
const getUser = cache(fetchUserData);
|
16
|
+
|
17
|
+
const loader = async () => {
|
18
|
+
const user = await getUser(user); // When the parameters of the function changes, the function will be re-executed
|
19
|
+
return {
|
20
|
+
user,
|
21
|
+
};
|
22
|
+
};
|
23
|
+
```
|
24
|
+
|
25
|
+
|
26
|
+
### Parameters
|
27
|
+
|
28
|
+
- `fn`: The data fetching or computation function to be cached
|
29
|
+
- `options` (optional): Cache configuration
|
30
|
+
- `tag`: Tag to identify the cache, which can be used to invalidate the cache
|
31
|
+
- `maxAge`: Cache validity period (milliseconds)
|
32
|
+
- `revalidate`: Time window for revalidating the cache (milliseconds), similar to HTTP Cache-Control's stale-while-revalidate functionality
|
33
|
+
|
34
|
+
The type of the `options` parameter is as follows:
|
35
|
+
|
36
|
+
```ts
|
37
|
+
interface CacheOptions {
|
38
|
+
tag?: string | string[];
|
39
|
+
maxAge?: number;
|
40
|
+
revalidate?: number;
|
41
|
+
}
|
42
|
+
```
|
43
|
+
|
44
|
+
|
45
|
+
### Return Value
|
46
|
+
|
47
|
+
The `cache` function returns a new function with caching capabilities. Multiple calls to this function will not re-execute the `fn` function.
|
48
|
+
|
49
|
+
## Usage Scope
|
50
|
+
|
51
|
+
Unlike React's [cache](https://react.dev/reference/react/cache) function which can only be used in server components,
|
52
|
+
EdenX's `cache` function can be used in any frontend or server-side code.
|
53
|
+
|
54
|
+
## Detailed Usage
|
55
|
+
|
56
|
+
### Without `options` Parameter
|
57
|
+
|
58
|
+
When no `options` parameter is provided, it's primarily useful in SSR projects, the cache lifecycle is limited to a single SSR rendering request. For example, when the same cachedFn is called in multiple data loaders, the cachedFn function won't be executed repeatedly. This allows data sharing between different data loaders while avoiding duplicate requests. EdenX will re-execute the `fn` function with each server request.
|
59
|
+
|
60
|
+
:::info
|
61
|
+
Without the `options` parameter, it can be considered a replacement for React's [`cache`](https://react.dev/reference/react/cache) function and can be used in any server-side code (such as in data loaders of SSR projects), not limited to server components.
|
62
|
+
:::
|
63
|
+
|
64
|
+
```ts
|
65
|
+
import { cache } from '@modern-js/runtime/cache';
|
66
|
+
import { fetchUserData } from './api';
|
67
|
+
|
68
|
+
const getUser = cache(fetchUserData);
|
69
|
+
|
70
|
+
const loader = async () => {
|
71
|
+
const user = await getUser();
|
72
|
+
return {
|
73
|
+
user,
|
74
|
+
};
|
75
|
+
};
|
76
|
+
```
|
77
|
+
|
78
|
+
|
79
|
+
### With `options` Parameter
|
80
|
+
|
81
|
+
#### `maxAge` Parameter
|
82
|
+
|
83
|
+
After each computation, the framework records the time when the cache is written.
|
84
|
+
When the function is called again, it checks if the cache has expired based on the `maxAge` parameter.
|
85
|
+
If expired, the `fn` function is re-executed; otherwise, the cached data is returned.
|
86
|
+
|
87
|
+
```ts
|
88
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
89
|
+
|
90
|
+
const getDashboardStats = cache(
|
91
|
+
async () => {
|
92
|
+
return await fetchComplexStatistics();
|
93
|
+
},
|
94
|
+
{
|
95
|
+
maxAge: CacheTime.MINUTE * 2, // Calling this function within 2 minutes will return cached data
|
96
|
+
}
|
97
|
+
);
|
98
|
+
```
|
99
|
+
|
100
|
+
|
101
|
+
#### `revalidate` Parameter
|
102
|
+
|
103
|
+
The `revalidate` parameter sets a time window for revalidating the cache after it expires. It can be used together with the `maxAge` parameter, similar to HTTP Cache-Control's stale-while-revalidate mode.
|
104
|
+
|
105
|
+
In the following example, within the 2-minute period before the cache expires, calls to `getDashboardStats` will return cached data. If the cache has expired (between 2 and 3 minutes), requests will first return the old data, then refresh the data in the background and update the cache.
|
106
|
+
|
107
|
+
```ts
|
108
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
109
|
+
|
110
|
+
const getDashboardStats = cache(
|
111
|
+
async () => {
|
112
|
+
return await fetchComplexStatistics();
|
113
|
+
},
|
114
|
+
{
|
115
|
+
maxAge: CacheTime.MINUTE * 2,
|
116
|
+
revalidate: CacheTime.MINUTE * 1,
|
117
|
+
}
|
118
|
+
);
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
#### `tag` Parameter
|
123
|
+
|
124
|
+
The `tag` parameter identifies the cache with a tag, which can be a string or an array of strings.
|
125
|
+
You can invalidate caches based on this tag, and multiple cache functions can use the same tag.
|
126
|
+
|
127
|
+
```ts
|
128
|
+
import { cache, revalidateTag } from '@modern-js/runtime/cache';
|
129
|
+
|
130
|
+
const getDashboardStats = cache(
|
131
|
+
async () => {
|
132
|
+
return await fetchDashboardStats();
|
133
|
+
},
|
134
|
+
{
|
135
|
+
tag: 'dashboard',
|
136
|
+
}
|
137
|
+
);
|
138
|
+
|
139
|
+
const getComplexStatistics = cache(
|
140
|
+
async () => {
|
141
|
+
return await fetchComplexStatistics();
|
142
|
+
},
|
143
|
+
{
|
144
|
+
tag: 'dashboard',
|
145
|
+
}
|
146
|
+
);
|
147
|
+
|
148
|
+
revalidateTag('dashboard-stats'); // Invalidates the cache for both getDashboardStats and getComplexStatistics functions
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
### Storage
|
153
|
+
|
154
|
+
Currently, both client and server caches are stored in memory.
|
155
|
+
The default storage limit for all cached functions is 1GB. When this limit is reached, the oldest cache is removed using an LRU algorithm.
|
156
|
+
|
157
|
+
:::info
|
158
|
+
Considering that the results of `cache` function caching are not large, they are currently stored in memory by default.
|
159
|
+
:::
|
160
|
+
|
161
|
+
You can specify the storage limit using the `configureCache` function:
|
162
|
+
|
163
|
+
```ts
|
164
|
+
import { configureCache, CacheSize } from '@modern-js/runtime/cache';
|
165
|
+
|
166
|
+
configureCache({
|
167
|
+
maxSize: CacheSize.MB * 10, // 10MB
|
168
|
+
});
|
169
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
在 Modern.js 中,日志事件由 LoggerMonitor 处理,它会将日志输出到控制台。
|
2
|
+
|
3
|
+
:::info
|
4
|
+
内置的 LoggerMonitor 依赖了 [rslog](https://www.npmjs.com/package/rslog) 库。
|
5
|
+
:::
|
6
|
+
|
7
|
+
例如在项目中有意的抛出一个错误:
|
8
|
+
|
9
|
+
```tsx title=routes/page.tsx
|
10
|
+
import './index.css';
|
11
|
+
|
12
|
+
const Index = () => <div className="container-box">{a}</div>;
|
13
|
+
|
14
|
+
export default Index;
|
15
|
+
```
|
16
|
+
|
17
|
+
如果运行正常,可以在控制台看到如下输出:
|
18
|
+
|
19
|
+
```shell
|
20
|
+
> Local: http://localhost:8080/
|
21
|
+
> press h + enter to show shortcuts
|
22
|
+
|
23
|
+
error SSR Error - App Prerender, error = ReferenceError: a is not defined
|
24
|
+
at Index (/somepath/page.tsx:3:1)
|
25
|
+
```
|
@@ -0,0 +1,20 @@
|
|
1
|
+
在 Modern.js 中,指标事件也由 LoggerMonitor 处理,它会将指标按照一定格式输出到控制台。
|
2
|
+
|
3
|
+
:::info
|
4
|
+
内置的 LoggerMonitor 依赖了 [rslog](https://www.npmjs.com/package/rslog) 库。
|
5
|
+
:::
|
6
|
+
|
7
|
+
在 Modern.js 中初始化的 `rslog` 实例默认在开发环境下会输出 `debug` 级别及以上的日志,在生产环境下会输出所有日志。而在内置的 `LoggerMonitor` 中,所有的指标事件都被输出为 Debug 日志。因此如果希望在开发环境查看指标事件的信息,需要添加额外的环境变量。
|
8
|
+
|
9
|
+
开发者可以在运行 `dev` 命令时添加环境变量 `DEBUG=true`,如果运行正常,访问后可以在控制台看到如下输出:
|
10
|
+
|
11
|
+
```shell
|
12
|
+
> Local: http://localhost:8080/
|
13
|
+
> press h + enter to show shortcuts
|
14
|
+
|
15
|
+
debug SSR Debug - server-loader, cost: 2.000094, req.url = /
|
16
|
+
debug SSR Debug - ssr-prerender, cost: 6.99997, req.url = /
|
17
|
+
debug SSR Debug - ssr-render-html, cost: 0.999927, req.url = /
|
18
|
+
debug Debug - server-handle-request, cost: 19.999981, req.url = /
|
19
|
+
debug --> GET / 200 90ms
|
20
|
+
```
|
@@ -6,6 +6,7 @@
|
|
6
6
|
"label": "use-bff",
|
7
7
|
"collapsed": true
|
8
8
|
},
|
9
|
+
|
9
10
|
{
|
10
11
|
"type": "dir",
|
11
12
|
"name": "page-performance",
|
@@ -16,5 +17,11 @@
|
|
16
17
|
"compatibility",
|
17
18
|
"low-level",
|
18
19
|
"source-build",
|
20
|
+
{
|
21
|
+
"type": "dir",
|
22
|
+
"name": "server-monitor",
|
23
|
+
"label": "server-monitor",
|
24
|
+
"collapsed": true
|
25
|
+
},
|
19
26
|
"web-server"
|
20
27
|
]
|
@@ -0,0 +1 @@
|
|
1
|
+
["monitors", "logger", "metrics"]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# 日志事件
|
2
|
+
|
3
|
+
日志事件是由 Modern.js 分发的,类型为 `log` 的事件。
|
4
|
+
|
5
|
+
## 内置事件
|
6
|
+
|
7
|
+
基于服务端运行逻辑,Modern.js 在内部提供了以下日志事件:
|
8
|
+
|
9
|
+
| 阶段 | 消息 | Level |
|
10
|
+
| ------------- | -------------------------------------------- | ----- |
|
11
|
+
| RENDER_HTML | App Render To HTML | error |
|
12
|
+
| RENDER_STREAM | An error occurs during streaming SSR | error |
|
13
|
+
| RENDER_SHELL | An error occurs during streaming render shell | error |
|
14
|
+
|
15
|
+
Modern.js 也保留了旧版本中使用 `useLoader` 获取数据时的 SSR 日志:
|
16
|
+
|
17
|
+
| 阶段 | 消息 | Level |
|
18
|
+
| ------------- | -------------------------------------------- | ----- |
|
19
|
+
| PRERENDER | App Prerender | error |
|
20
|
+
| USE_LOADER | App run useLoader | error |
|
21
|
+
|
22
|
+
:::tip
|
23
|
+
`useLoader` API 目前已经废弃,建议迁移到约定式路由并使用 Data Loader 进行数据获取。已经使用 Data Loader 的应用,可以开启 [`ssr.disablePrerender`](/configure/app/server/ssr.html#object-类型),禁止预渲染,提升 SSR 性能。
|
24
|
+
:::
|
25
|
+
|
26
|
+
## 内置 Monitor
|
27
|
+
|
28
|
+
import InternalLogger from '@site-docs/components/internal-logger.mdx';
|
29
|
+
|
30
|
+
<InternalLogger />
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# 指标事件
|
2
|
+
|
3
|
+
指标事件是由 Monitors 分发,类型为 `timing` 或 `counter` 的事件。
|
4
|
+
|
5
|
+
## 内置事件
|
6
|
+
|
7
|
+
当项目开启 SSR 时,我们需要关注 Server 端各阶段耗时,并且有能力对 Server 端的问题进行定位。
|
8
|
+
|
9
|
+
基于服务端运行逻辑,Modern.js 在内部提供了以下指标事件:
|
10
|
+
|
11
|
+
| Key | 描述 |
|
12
|
+
| ------------------------ | --------------- |
|
13
|
+
| server-handle-request | EdenX Server 处理请求单次耗时 |
|
14
|
+
| ssr-render-shell | [SSR]在使用 Streaming SSR 时, React 将渲染一个套壳(shell)优先传输。这里表明套壳渲染完成时间 |
|
15
|
+
| ssr-render-html | [SSR] React 将组件树渲染成 html 所消耗的时间,通常不会超过 50ms |
|
16
|
+
| server-middleware | EdenX 自定义 Server `Middleware` 执行总时 |
|
17
|
+
| server-loader | Server 端整体 Data Loader 耗时 |
|
18
|
+
| server-loader-#id | Server 端各个 Data Loader 耗时 |
|
19
|
+
| server-loader-navigation | 前端导航时,服务端 Data Loader 的整体耗时 |
|
20
|
+
|
21
|
+
Modenr.js 服务端流程如图所示:
|
22
|
+
|
23
|
+

|
24
|
+
|
25
|
+
|
26
|
+
## 内置 Monitor
|
27
|
+
|
28
|
+
import InternalMetrics from '@site-docs/components/internal-metrics.mdx';
|
29
|
+
|
30
|
+
<InternalMetrics />
|
31
|
+
|
32
|
+
## Server-Timing
|
33
|
+
|
34
|
+
在 Modern.js 中,服务端还会额外将各阶段指标作为 Server-Timing 注入到 HTML 响应头中。
|
35
|
+
|
36
|
+
在浏览器中,开发者可以通过 [Performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance) 获取:
|
37
|
+
|
38
|
+
```ts
|
39
|
+
const navigation = performance.getEntriesByType('navigation')[0];
|
40
|
+
const serverTiming = navigation.serverTiming;
|
41
|
+
console.log(serverTiming);
|
42
|
+
```
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# Monitors
|
2
|
+
|
3
|
+
Modern.js 是一个全栈框架,它可以同时支持客户端和服务端的开发。当 Modern.js 在服务端渲染页面时,框架会在运行时插入更多日志与指标,帮助开发者在线上运维时定位问题。
|
4
|
+
|
5
|
+
但服务端代码运行在 Node.js 环境中,开发者无法直接通过浏览器控制台来排查问题,而不同的项目可能使用不同的日志库,或是将数据上报到不同的平台。框架无法覆盖所有的场景,因此需要有统一的方式,允许开发者自行管理所有的内置日志与指标。
|
6
|
+
|
7
|
+
Monitors 是 Modern.js 提供的帮助开发者监控应用程序的运行情况的模块。它包含注册 Monitor 和分发监控事件两部分能力。当开发者中调用 Monitors 的某个 API 时,框架会对应的监控事件分发到所有注册的 Monitor 中。
|
8
|
+
|
9
|
+
:::note
|
10
|
+
在 Modern.js 中内置了默认的 Monitor,开发者也可以注册自定义的 Monitor。
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Monitors 定义
|
14
|
+
|
15
|
+
Monitors 模块的类型定义如下,其中 `push` 方法用于注册 Monitor,其他方法用于分发监控事件。
|
16
|
+
|
17
|
+
```ts
|
18
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
19
|
+
export interface LogEvent {
|
20
|
+
type: 'log';
|
21
|
+
payload: {
|
22
|
+
level: LogLevel;
|
23
|
+
message: string;
|
24
|
+
args?: any[];
|
25
|
+
};
|
26
|
+
}
|
27
|
+
|
28
|
+
export interface TimingEvent {
|
29
|
+
type: 'timing';
|
30
|
+
payload: {
|
31
|
+
name: string;
|
32
|
+
dur: number;
|
33
|
+
desc?: string;
|
34
|
+
args?: any[];
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
export interface CounterEvent {
|
39
|
+
type: 'counter';
|
40
|
+
payload: {
|
41
|
+
name: string;
|
42
|
+
args?: any[];
|
43
|
+
};
|
44
|
+
}
|
45
|
+
|
46
|
+
export type MonitorEvent = LogEvent | TimingEvent | CounterEvent;
|
47
|
+
|
48
|
+
export type CoreMonitor = (event: MonitorEvent) => void;
|
49
|
+
|
50
|
+
export interface Monitors {
|
51
|
+
// 注册 Monitor
|
52
|
+
push(monitor: CoreMonitor): void;
|
53
|
+
|
54
|
+
// 日志事件
|
55
|
+
error(message: string, ...args: any[]): void;
|
56
|
+
warn(message: string, ...args: any[]): void;
|
57
|
+
debug(message: string, ...args: any[]): void;
|
58
|
+
info(message: string, ...args: any[]): void;
|
59
|
+
trace(message: string,...args: any[]): void;
|
60
|
+
|
61
|
+
// 指标事件
|
62
|
+
timing(name: string, dur: number, ...args: any[]): void;
|
63
|
+
counter(name: string, ...args: any[]): void
|
64
|
+
}
|
65
|
+
```
|
66
|
+
|
67
|
+
## 内置 Monitor
|
68
|
+
|
69
|
+
Modern.js 内置了默认的 Monitor,不同的事件会触发内置 Monitor 不同的行为。详细内容查看:
|
70
|
+
|
71
|
+
- [日志事件](/guides/advanced-features/server-monitor/logger)
|
72
|
+
- [指标事件](/guides/advanced-features/server-monitor/metrics)
|
73
|
+
|
74
|
+
|
75
|
+
## 注册 Monitors
|
76
|
+
|
77
|
+
开发者可以通过 Monitors 的 `push` API 注册自己的 Monitor,但只能在[服务端中间件](/apis/app/runtime/web-server/unstable_middleware)或服务端插件中注册,在 Data Loader、组件、init 函数中无法注册。
|
78
|
+
|
79
|
+
:::note
|
80
|
+
目前暂未开放服务端插件,后续会补充相关文档。
|
81
|
+
:::
|
82
|
+
|
83
|
+
```ts title="server/index.ts"
|
84
|
+
import type { CoreMonitors } from '@modern-js/types';
|
85
|
+
const injectMonitorMiddleware = (c) => {
|
86
|
+
const monitors = c.get('monitors');
|
87
|
+
const myMonitor = (event: MonitorEvent) => {
|
88
|
+
if (event.type === 'log') {
|
89
|
+
// some code
|
90
|
+
} else {
|
91
|
+
// some other code
|
92
|
+
}
|
93
|
+
}
|
94
|
+
monitors.push(myMonitor);
|
95
|
+
return next();
|
96
|
+
}
|
97
|
+
|
98
|
+
export const middlewares = [injectMonitorMiddleware]
|
99
|
+
```
|
100
|
+
|
101
|
+
## 调用 Monitors
|
102
|
+
|
103
|
+
Modern.js 允许开发者在 Data Loader、组件中调用 Monitors。
|
104
|
+
|
105
|
+
:::tip
|
106
|
+
只有在 Node.js 环境才可以调用 Monitors,在浏览器环境调用无任何效果。
|
107
|
+
:::
|
108
|
+
|
109
|
+
在 Data Loader 中,开发者可以这样使用:
|
110
|
+
|
111
|
+
```ts title="routes/page.data.ts"
|
112
|
+
import { LoaderFunctionArgs } from '@modern-js/runtime/router';
|
113
|
+
import { getMonitors } from '@modern-js/runtime';
|
114
|
+
const loader = async ({ context }: LoaderFunctionArgs) => {
|
115
|
+
const monitors = getMonitors();
|
116
|
+
const start = Date.now();
|
117
|
+
try {
|
118
|
+
await fetch(...);
|
119
|
+
monitors.timing('loader_fetch_timing', Date.now() - start);
|
120
|
+
} catch(e) {
|
121
|
+
monitors.error(e);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
126
|
+
在组件中调用 Monitors,需要判断当前运行环境是否为 Node.js:
|
127
|
+
|
128
|
+
```tsx title="routes/page.tsx"
|
129
|
+
import { getMonitors } from '@modern-js/runtime';
|
130
|
+
const Page = () => {
|
131
|
+
const { context } = useRuntimeContext();
|
132
|
+
if (process.env.MODERN_TARGET === 'node') {
|
133
|
+
const monitors = getMonitors();
|
134
|
+
monitors.info();
|
135
|
+
}
|
136
|
+
return <div>Hello World</div>
|
137
|
+
}
|
138
|
+
export default Page;
|
139
|
+
```
|
140
|
+
|
141
|
+
在中间件中,我们也可以调用 Monitors,但方式与在运行时代码中不同,需要通过 `context` 来获取:
|
142
|
+
|
143
|
+
```ts title="server/index.ts"
|
144
|
+
import { UnstableMiddleware } from '@modern-js/runtime/server';
|
145
|
+
|
146
|
+
const time: UnstableMiddleware = async (c, next) => {
|
147
|
+
const start = Date.now();
|
148
|
+
await next();
|
149
|
+
const end = Date.now();
|
150
|
+
const monitors = c.get('monitors');
|
151
|
+
monitors.info(`${end - start}`);
|
152
|
+
};
|
153
|
+
|
154
|
+
export const unstableMiddleware: UnstableMiddleware[] = [time];
|
155
|
+
```
|
156
|
+
|
157
|
+
|
@@ -1 +1 @@
|
|
1
|
-
["data-fetch", "data-write"]
|
1
|
+
["data-fetch", "data-write", "data-cache"]
|
@@ -0,0 +1,162 @@
|
|
1
|
+
---
|
2
|
+
title: 数据缓存
|
3
|
+
sidebar_position: 4
|
4
|
+
---
|
5
|
+
# 数据缓存
|
6
|
+
|
7
|
+
`cache` 函数可以让你缓存数据获取或计算的结果。
|
8
|
+
|
9
|
+
## 基本用法
|
10
|
+
|
11
|
+
```ts
|
12
|
+
import { cache } from '@modern-js/runtime/cache';
|
13
|
+
import { fetchUserData } from './api';
|
14
|
+
|
15
|
+
const getUser = cache(fetchUserData);
|
16
|
+
|
17
|
+
const loader = async () => {
|
18
|
+
const user = await getUser(user); // 函数入参发生变化时,函数会重新执行
|
19
|
+
return {
|
20
|
+
user,
|
21
|
+
};
|
22
|
+
};
|
23
|
+
```
|
24
|
+
|
25
|
+
### 参数
|
26
|
+
|
27
|
+
- `fn`: 需要缓存的数据获取或计算的函数
|
28
|
+
- `options`(可选): 缓存配置
|
29
|
+
- `tag`: 用于标识缓存的标签,可以基于这个标签使缓存失效
|
30
|
+
- `maxAge`: 缓存的有效期 (毫秒)
|
31
|
+
- `revalidate`: 重新验证缓存的时间窗口(毫秒),与 HTTP Cache-Control 的 stale-while-revalidate 功能一致
|
32
|
+
|
33
|
+
`options` 参数的类型如下:
|
34
|
+
|
35
|
+
```ts
|
36
|
+
interface CacheOptions {
|
37
|
+
tag?: string | string[];
|
38
|
+
maxAge?: number;
|
39
|
+
revalidate?: number;
|
40
|
+
}
|
41
|
+
```
|
42
|
+
|
43
|
+
### 返回值
|
44
|
+
|
45
|
+
`cache` 函数会返回一个新的函数,该函数有缓存的能力,多次调用该函数,不会重复执行 `fn` 函数。
|
46
|
+
|
47
|
+
## 使用范围
|
48
|
+
|
49
|
+
与 react 的 [cache](https://react.dev/reference/react/cache) 函数只能在 server component 组件中使用不同,
|
50
|
+
EdenX 提供的 `cache` 函数可以在任意的前端或服务端的代码中使用。
|
51
|
+
|
52
|
+
## 详细用法
|
53
|
+
|
54
|
+
### 无 `options` 参数
|
55
|
+
|
56
|
+
当无 `options` 参数传入时,主要可以用于 SSR 项目,缓存的生命周期是单次 ssr 渲染的请求,如可以在多个 data loader 中调用同一个 cachedFn 时,不会重复执行 cachedFn 函数。这样可以在不同的 data loader 中共享数据,同时避免重复的请求,EdenX 会在每次收到服务端请求时,重新执行 `fn` 函数。
|
57
|
+
|
58
|
+
:::info
|
59
|
+
无 `options` 参数时,可以看作是 react [`cache`](https://react.dev/reference/react/cache) 函数的替代品,可以在任意服务端代码中使用(比如可以在 SSR 项目的 data loader 中),不局限于 server component。
|
60
|
+
:::
|
61
|
+
|
62
|
+
|
63
|
+
```ts
|
64
|
+
import { cache } from '@modern-js/runtime/cache';
|
65
|
+
import { fetchUserData } from './api';
|
66
|
+
|
67
|
+
const getUser = cache(fetchUserData);
|
68
|
+
|
69
|
+
const loader = async () => {
|
70
|
+
const user = await getUser();
|
71
|
+
return {
|
72
|
+
user,
|
73
|
+
};
|
74
|
+
};
|
75
|
+
```
|
76
|
+
|
77
|
+
### 有 `options` 参数
|
78
|
+
|
79
|
+
#### `maxAge` 参数
|
80
|
+
|
81
|
+
每次计算完成后,框架会记录写入缓存的时间,当再次调用该函数时,会根据 `maxAge` 参数判断缓存是否过期,如果过期,则重新执行 `fn` 函数,否则返回缓存的数据。
|
82
|
+
|
83
|
+
```ts
|
84
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
85
|
+
|
86
|
+
const getDashboardStats = cache(
|
87
|
+
async () => {
|
88
|
+
return await fetchComplexStatistics();
|
89
|
+
},
|
90
|
+
{
|
91
|
+
maxAge: CacheTime.MINUTE * 2, // 在 2 分钟内调用该函数会返回缓存的数据
|
92
|
+
}
|
93
|
+
);
|
94
|
+
```
|
95
|
+
|
96
|
+
#### `revalidate` 参数
|
97
|
+
|
98
|
+
`revalidate` 参数用于设置缓存过期后,重新验证缓存的时间窗口,可以和 `maxAge` 参数一起使用,类似与 HTTP Cache-Control 的 stale-while-revalidate 模式。
|
99
|
+
|
100
|
+
|
101
|
+
如以下示例,在缓存未过期的 2分钟内,如果调用 `getDashboardStats` 函数,会返回缓存的数据,如果缓存过期,2分到3分钟内,收到的请求会先返回旧数据,然后后台会重新请求数据,并更新缓存。
|
102
|
+
|
103
|
+
```ts
|
104
|
+
import { cache, CacheTime } from '@modern-js/runtime/cache';
|
105
|
+
|
106
|
+
const getDashboardStats = cache(
|
107
|
+
async () => {
|
108
|
+
return await fetchComplexStatistics();
|
109
|
+
},
|
110
|
+
{
|
111
|
+
maxAge: CacheTime.MINUTE * 2,
|
112
|
+
revalidate: CacheTime.MINUTE * 1,
|
113
|
+
}
|
114
|
+
);
|
115
|
+
```
|
116
|
+
|
117
|
+
#### `tag` 参数
|
118
|
+
|
119
|
+
`tag` 参数用于标识缓存的标签,可以传入一个字符串或字符串数组,可以基于这个标签使缓存失效,多个缓存函数可以使用一个标签。
|
120
|
+
|
121
|
+
```ts
|
122
|
+
import { cache, revalidateTag } from '@modern-js/runtime/cache';
|
123
|
+
|
124
|
+
const getDashboardStats = cache(
|
125
|
+
async () => {
|
126
|
+
return await fetchDashboardStats();
|
127
|
+
},
|
128
|
+
{
|
129
|
+
tag: 'dashboard',
|
130
|
+
}
|
131
|
+
);
|
132
|
+
|
133
|
+
const getComplexStatistics = cache(
|
134
|
+
async () => {
|
135
|
+
return await fetchComplexStatistics();
|
136
|
+
},
|
137
|
+
{
|
138
|
+
tag: 'dashboard',
|
139
|
+
}
|
140
|
+
);
|
141
|
+
|
142
|
+
revalidateTag('dashboard-stats'); // 会使 getDashboardStats 函数和 getComplexStatistics 函数的缓存都失效
|
143
|
+
```
|
144
|
+
|
145
|
+
|
146
|
+
### 存储
|
147
|
+
|
148
|
+
目前不管是客户端还是服务端,缓存都存储在内存中,默认情况下所有缓存函数共享的存储上限是 1GB,当达到存储上限后,使用 LRU 算法移除旧的缓存。
|
149
|
+
|
150
|
+
:::info
|
151
|
+
考虑到 `cache` 函数缓存的结果内容不会很大,所以目前默认都存储在内存中
|
152
|
+
:::
|
153
|
+
|
154
|
+
可以通过 `configureCache` 函数指定缓存的存储上限:
|
155
|
+
|
156
|
+
```ts
|
157
|
+
import { configureCache, CacheSize } from '@modern-js/runtime/cache';
|
158
|
+
|
159
|
+
configureCache({
|
160
|
+
maxSize: CacheSize.MB * 10, // 10MB
|
161
|
+
});
|
162
|
+
```
|
package/i18n.json
CHANGED
package/package.json
CHANGED
@@ -15,17 +15,17 @@
|
|
15
15
|
"modern",
|
16
16
|
"modern.js"
|
17
17
|
],
|
18
|
-
"version": "2.65.
|
18
|
+
"version": "2.65.3",
|
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.65.
|
25
|
+
"@modern-js/sandpack-react": "2.65.3"
|
26
26
|
},
|
27
27
|
"devDependencies": {
|
28
|
-
"@rspress/shared": "1.
|
28
|
+
"@rspress/shared": "1.42.0",
|
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.42.0",
|
37
37
|
"ts-node": "^10.9.1",
|
38
38
|
"typescript": "^5"
|
39
39
|
},
|