@modern-js/main-doc 2.33.1 → 2.34.0
Sign up to get free protection for your applications and to get access to all the features.
- package/docs/en/configure/app/server/ssr.mdx +2 -0
- package/docs/en/tutorials/examples/_category_.json +5 -0
- package/docs/en/tutorials/examples/csr-auth.mdx +214 -0
- package/docs/en/tutorials/foundations/introduction.mdx +10 -1
- package/docs/zh/configure/app/server/ssr.mdx +3 -0
- package/docs/zh/guides/basic-features/routes.mdx +1 -1
- package/docs/zh/tutorials/examples/_category_.json +5 -0
- package/docs/zh/tutorials/examples/csr-auth.mdx +214 -0
- package/docs/zh/tutorials/foundations/introduction.mdx +8 -0
- package/package.json +7 -4
- package/src/components/Sandpack/index.css +10 -0
- package/src/components/Sandpack/index.tsx +30 -0
@@ -28,6 +28,8 @@ When the value type is `Object`, the following properties can be configured:
|
|
28
28
|
- `mode`: `string = 'string'`, which defaults to using `renderToString` for rendering. Configure `stream` to enable streaming rendering.
|
29
29
|
- `forceCSR`: `boolean = false`, which is off by default for forcing CSR rendering. Configure `true` to force CSR by adding `?csr=true` or adding `x-modern-ssr-fallback` header when accessing the page.
|
30
30
|
- `inlineScript`: `boolean = true`, by default, SSR data is injected into HTML as inline scripts and assigned directly to global variables. Configure `false` to distribute JSON instead of assigning to global variables.
|
31
|
+
- `disablePrerender`: `boolean = fasle`, To ensure compatibility with the old data request method (`useLoader`), by default, Modern.js performs pre-rendering of components.
|
32
|
+
However, if developers want to reduce one rendering when there is no use of the useLoader API in your project, you can set the configuration `disablePrerender=true`.
|
31
33
|
|
32
34
|
```ts title="modern.config.ts"
|
33
35
|
export default defineConfig({
|
@@ -0,0 +1,214 @@
|
|
1
|
+
---
|
2
|
+
title: Route Authorization
|
3
|
+
---
|
4
|
+
|
5
|
+
# Route Authorization
|
6
|
+
|
7
|
+
Modern.js defaults to the convention-based routing based on React Router 6. For more details, please refer to [Routing](/guides/basic-features/routes.html#routing-scheme).
|
8
|
+
|
9
|
+
In a web application, if there are multiple routes, we may need to authorize access to some of them before accessing them. For example, in the following scenario:
|
10
|
+
|
11
|
+
- Access to the `/` route does not require authorization and can be accessed directly.
|
12
|
+
- Access to the `/protected` route requires authorization. If there is no authorization, it will automatically redirect to the `/login` route. After successful login, it returns to `/protected`.
|
13
|
+
|
14
|
+
import Sandpack from '@site/src/components/Sandpack';
|
15
|
+
|
16
|
+
<Sandpack template="web-app">
|
17
|
+
```tsx title="src/routes/page.tsx"
|
18
|
+
import { Helmet } from '@modern-js/runtime/head';
|
19
|
+
import './index.css';
|
20
|
+
|
21
|
+
const PublicPage = (): JSX.Element => (
|
22
|
+
<div className="container-box">
|
23
|
+
<Helmet>
|
24
|
+
<link
|
25
|
+
rel="icon"
|
26
|
+
type="image/x-icon"
|
27
|
+
href="https://lf3-static.bytednsdoc.com/obj/eden-cn/uhbfnupenuhf/favicon.ico"
|
28
|
+
/>
|
29
|
+
</Helmet>
|
30
|
+
<h3>Public</h3>
|
31
|
+
</div>
|
32
|
+
);
|
33
|
+
|
34
|
+
export default PublicPage;
|
35
|
+
|
36
|
+
```
|
37
|
+
```tsx title="src/routes/layout.tsx"
|
38
|
+
import { Link, Outlet } from '@modern-js/runtime/router';
|
39
|
+
import { AuthProvider, AuthStatus } from './Auth';
|
40
|
+
|
41
|
+
export default function Layout() {
|
42
|
+
return (
|
43
|
+
<AuthProvider>
|
44
|
+
<AuthStatus />
|
45
|
+
|
46
|
+
<ul>
|
47
|
+
<li>
|
48
|
+
<Link to="/">Public Page</Link>
|
49
|
+
</li>
|
50
|
+
<li>
|
51
|
+
<Link to="/protected">Protected Page</Link>
|
52
|
+
</li>
|
53
|
+
</ul>
|
54
|
+
|
55
|
+
<Outlet />
|
56
|
+
</AuthProvider>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
|
60
|
+
```
|
61
|
+
```ts title="src/routes/fakeAuth.ts"
|
62
|
+
/**
|
63
|
+
* This represents some generic auth provider API, like Firebase.
|
64
|
+
*/
|
65
|
+
const fakeAuthProvider = {
|
66
|
+
isAuthenticated: false,
|
67
|
+
signin(callback: VoidFunction) {
|
68
|
+
fakeAuthProvider.isAuthenticated = true;
|
69
|
+
setTimeout(callback, 100); // fake async
|
70
|
+
},
|
71
|
+
signout(callback: VoidFunction) {
|
72
|
+
fakeAuthProvider.isAuthenticated = false;
|
73
|
+
setTimeout(callback, 100);
|
74
|
+
},
|
75
|
+
};
|
76
|
+
|
77
|
+
export { fakeAuthProvider };
|
78
|
+
|
79
|
+
```
|
80
|
+
```ts title="src/routes/Auth.tsx"
|
81
|
+
import React from 'react';
|
82
|
+
import { useNavigate, Navigate, useLocation } from '@modern-js/runtime/router';
|
83
|
+
import { fakeAuthProvider } from './fakeAuth';
|
84
|
+
|
85
|
+
interface AuthContextType {
|
86
|
+
user: any;
|
87
|
+
signin: (user: string, callback: VoidFunction) => void;
|
88
|
+
signout: (callback: VoidFunction) => void;
|
89
|
+
}
|
90
|
+
|
91
|
+
const AuthContext = React.createContext<AuthContextType>(null!);
|
92
|
+
|
93
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
94
|
+
const [user, setUser] = React.useState<any>(null);
|
95
|
+
|
96
|
+
const signin = (newUser: string, callback: VoidFunction) =>
|
97
|
+
fakeAuthProvider.signin(() => {
|
98
|
+
setUser(newUser);
|
99
|
+
callback();
|
100
|
+
});
|
101
|
+
|
102
|
+
const signout = (callback: VoidFunction) =>
|
103
|
+
fakeAuthProvider.signout(() => {
|
104
|
+
setUser(null);
|
105
|
+
callback();
|
106
|
+
});
|
107
|
+
|
108
|
+
const value = { user, signin, signout };
|
109
|
+
|
110
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
111
|
+
}
|
112
|
+
|
113
|
+
export function useAuth() {
|
114
|
+
return React.useContext(AuthContext);
|
115
|
+
}
|
116
|
+
|
117
|
+
export function AuthStatus() {
|
118
|
+
const auth = useAuth();
|
119
|
+
console.log('auth', auth);
|
120
|
+
const navigate = useNavigate();
|
121
|
+
|
122
|
+
if (!auth.user) {
|
123
|
+
return <p>You are not logged in.</p>;
|
124
|
+
}
|
125
|
+
|
126
|
+
return (
|
127
|
+
<p>
|
128
|
+
Welcome {auth.user}!{' '}
|
129
|
+
<button
|
130
|
+
type="button"
|
131
|
+
onClick={() => {
|
132
|
+
auth.signout(() => navigate('/'));
|
133
|
+
}}
|
134
|
+
>
|
135
|
+
Sign out
|
136
|
+
</button>
|
137
|
+
</p>
|
138
|
+
);
|
139
|
+
}
|
140
|
+
|
141
|
+
export function RequireAuth({ children }: { children: JSX.Element }) {
|
142
|
+
const auth = useAuth();
|
143
|
+
const location = useLocation();
|
144
|
+
|
145
|
+
if (!auth.user) {
|
146
|
+
// Redirect them to the /login page, but save the current location they were
|
147
|
+
// trying to go to when they were redirected. This allows us to send them
|
148
|
+
// along to that page after they login, which is a nicer user experience
|
149
|
+
// than dropping them off on the home page.
|
150
|
+
return <Navigate to="/login" state={{ from: location }} replace />;
|
151
|
+
}
|
152
|
+
|
153
|
+
return children;
|
154
|
+
}
|
155
|
+
|
156
|
+
```
|
157
|
+
```ts title="src/routes/protected/page.tsx"
|
158
|
+
import { RequireAuth } from '../Auth';
|
159
|
+
|
160
|
+
export default function ProtectedPage() {
|
161
|
+
return (
|
162
|
+
<div className="container-box">
|
163
|
+
<RequireAuth>
|
164
|
+
<h3>Protected</h3>
|
165
|
+
</RequireAuth>
|
166
|
+
</div>
|
167
|
+
);
|
168
|
+
}
|
169
|
+
|
170
|
+
```
|
171
|
+
```ts title="src/routes/login/page.tsx"
|
172
|
+
import { useLocation, useNavigate } from '@modern-js/runtime/router';
|
173
|
+
import { useAuth } from '../Auth';
|
174
|
+
|
175
|
+
export default function Login() {
|
176
|
+
const navigate = useNavigate();
|
177
|
+
const location = useLocation();
|
178
|
+
const auth = useAuth();
|
179
|
+
|
180
|
+
const from = location.state?.from?.pathname || '/';
|
181
|
+
|
182
|
+
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
183
|
+
event.preventDefault();
|
184
|
+
|
185
|
+
const formData = new FormData(event.currentTarget);
|
186
|
+
const username = formData.get('username') as string;
|
187
|
+
|
188
|
+
auth.signin(username, () => {
|
189
|
+
// Send them back to the page they tried to visit when they were
|
190
|
+
// redirected to the login page. Use { replace: true } so we don't create
|
191
|
+
// another entry in the history stack for the login page. This means that
|
192
|
+
// when they get to the protected page and click the back button, they
|
193
|
+
// won't end up back on the login page, which is also really nice for the
|
194
|
+
// user experience.
|
195
|
+
navigate(from, { replace: true });
|
196
|
+
});
|
197
|
+
}
|
198
|
+
|
199
|
+
return (
|
200
|
+
<div>
|
201
|
+
<p>You must log in to view the page at {from}</p>
|
202
|
+
|
203
|
+
<form onSubmit={handleSubmit}>
|
204
|
+
<label>
|
205
|
+
Username: <input name="username" type="text" />
|
206
|
+
</label>{' '}
|
207
|
+
<button type="submit">Login</button>
|
208
|
+
</form>
|
209
|
+
</div>
|
210
|
+
);
|
211
|
+
}
|
212
|
+
|
213
|
+
```
|
214
|
+
</Sandpack>
|
@@ -24,5 +24,14 @@ We have prepared a tutorial on creating a "contact list app" that you can follow
|
|
24
24
|
- Data fetching
|
25
25
|
- State Management
|
26
26
|
- Container components
|
27
|
-
- New
|
27
|
+
- New entry
|
28
28
|
- ...
|
29
|
+
|
30
|
+
## Case Studies
|
31
|
+
|
32
|
+
We offer some commonly used case studies for your reference during the development process. Here you can find some usage patterns of the feature combinations provided by Modern.js. We will continue to improve the case studies here.
|
33
|
+
|
34
|
+
- [Route Authorization](/tutorials/examples/csr-auth.html)
|
35
|
+
- ...
|
36
|
+
|
37
|
+
Let's start from [creating a project](tutorials/first-app/c01-start) now!
|
@@ -28,6 +28,8 @@ export default defineConfig({
|
|
28
28
|
- `mode`:`string = 'string'`,默认为使用 `renderToString` 渲染。配置为 `stream` 开启流式渲染。
|
29
29
|
- `forceCSR`:`boolean = false`,默认关闭强制 CSR 渲染。配置为 `true` 后,在页面访问时添加 `?csr=true` 或添加请求头 `x-modern-ssr-fallback` 即可强制 CSR。
|
30
30
|
- `inlineScript`:`boolean = true`,默认情况下,SSR 的数据会以内联脚本的方式注入到 HTML 中,并且直接赋值给全局变量。配置为 `false` 后,会下发 JSON,而不是赋值给全局变量。
|
31
|
+
- `disablePrerender`: `boolean = fasle`, 为了兼容旧数据请求方式 - `useLoader`, 默认情况下 Modern.js 会对组件进行一次预渲染即有两次渲染。
|
32
|
+
开发者在保证项目中没有使用 useLoader Api 情况下, 可通过配置 `disablePrerender=true`来减少一次渲染。
|
31
33
|
|
32
34
|
```ts title="modern.config.ts"
|
33
35
|
export default defineConfig({
|
@@ -36,6 +38,7 @@ export default defineConfig({
|
|
36
38
|
forceCSR: true,
|
37
39
|
mode: 'stream',
|
38
40
|
inlineScript: false,
|
41
|
+
disablePrerender: true,
|
39
42
|
},
|
40
43
|
},
|
41
44
|
});
|
@@ -207,7 +207,7 @@ export default () => {
|
|
207
207
|
└── page.tsx
|
208
208
|
```
|
209
209
|
|
210
|
-
`routes/user/[id$]/page.tsx` 文件会转为 `/user/:id?` 路由。`/user` 下的所有路由都会匹配到该路由,并且 `id`
|
210
|
+
`routes/user/[id$]/page.tsx` 文件会转为 `/user/:id?` 路由。`/user` 下的所有路由都会匹配到该路由,并且 `id` 参数可选存在。通常在区分**创建**与**编辑**时,可以使用该路由。
|
211
211
|
|
212
212
|
在组件中,可以通过 [useParams](/apis/app/runtime/router/router#useparams) 获取对应命名的参数。
|
213
213
|
|
@@ -0,0 +1,214 @@
|
|
1
|
+
---
|
2
|
+
title: 路由鉴权
|
3
|
+
---
|
4
|
+
|
5
|
+
# 路由鉴权
|
6
|
+
|
7
|
+
Modern.js 默认提供的路由方式是基于 React Router 6 的约定式路由,具体可查看[路由方案](/guides/basic-features/routes.html#路由方案)。
|
8
|
+
|
9
|
+
在一个 Web 应用中如果存在多个路由,我们可能需要对部分路由进行鉴权后才能访问。例如下面这个案例:
|
10
|
+
|
11
|
+
- 访问 `/` 路由,无需鉴权,可直接访问。
|
12
|
+
- 访问 `/protected` 路由,需要鉴权,如果无,自动跳转到 `/login` 路由,登录成功后返回 `/protected`。
|
13
|
+
|
14
|
+
import Sandpack from '@site/src/components/Sandpack';
|
15
|
+
|
16
|
+
<Sandpack template="web-app">
|
17
|
+
```tsx title="src/routes/page.tsx"
|
18
|
+
import { Helmet } from '@modern-js/runtime/head';
|
19
|
+
import './index.css';
|
20
|
+
|
21
|
+
const PublicPage = (): JSX.Element => (
|
22
|
+
<div className="container-box">
|
23
|
+
<Helmet>
|
24
|
+
<link
|
25
|
+
rel="icon"
|
26
|
+
type="image/x-icon"
|
27
|
+
href="https://lf3-static.bytednsdoc.com/obj/eden-cn/uhbfnupenuhf/favicon.ico"
|
28
|
+
/>
|
29
|
+
</Helmet>
|
30
|
+
<h3>Public</h3>
|
31
|
+
</div>
|
32
|
+
);
|
33
|
+
|
34
|
+
export default PublicPage;
|
35
|
+
|
36
|
+
```
|
37
|
+
```tsx title="src/routes/layout.tsx"
|
38
|
+
import { Link, Outlet } from '@modern-js/runtime/router';
|
39
|
+
import { AuthProvider, AuthStatus } from './Auth';
|
40
|
+
|
41
|
+
export default function Layout() {
|
42
|
+
return (
|
43
|
+
<AuthProvider>
|
44
|
+
<AuthStatus />
|
45
|
+
|
46
|
+
<ul>
|
47
|
+
<li>
|
48
|
+
<Link to="/">Public Page</Link>
|
49
|
+
</li>
|
50
|
+
<li>
|
51
|
+
<Link to="/protected">Protected Page</Link>
|
52
|
+
</li>
|
53
|
+
</ul>
|
54
|
+
|
55
|
+
<Outlet />
|
56
|
+
</AuthProvider>
|
57
|
+
);
|
58
|
+
}
|
59
|
+
|
60
|
+
```
|
61
|
+
```ts title="src/routes/fakeAuth.ts"
|
62
|
+
/**
|
63
|
+
* This represents some generic auth provider API, like Firebase.
|
64
|
+
*/
|
65
|
+
const fakeAuthProvider = {
|
66
|
+
isAuthenticated: false,
|
67
|
+
signin(callback: VoidFunction) {
|
68
|
+
fakeAuthProvider.isAuthenticated = true;
|
69
|
+
setTimeout(callback, 100); // fake async
|
70
|
+
},
|
71
|
+
signout(callback: VoidFunction) {
|
72
|
+
fakeAuthProvider.isAuthenticated = false;
|
73
|
+
setTimeout(callback, 100);
|
74
|
+
},
|
75
|
+
};
|
76
|
+
|
77
|
+
export { fakeAuthProvider };
|
78
|
+
|
79
|
+
```
|
80
|
+
```ts title="src/routes/Auth.tsx"
|
81
|
+
import React from 'react';
|
82
|
+
import { useNavigate, Navigate, useLocation } from '@modern-js/runtime/router';
|
83
|
+
import { fakeAuthProvider } from './fakeAuth';
|
84
|
+
|
85
|
+
interface AuthContextType {
|
86
|
+
user: any;
|
87
|
+
signin: (user: string, callback: VoidFunction) => void;
|
88
|
+
signout: (callback: VoidFunction) => void;
|
89
|
+
}
|
90
|
+
|
91
|
+
const AuthContext = React.createContext<AuthContextType>(null!);
|
92
|
+
|
93
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
94
|
+
const [user, setUser] = React.useState<any>(null);
|
95
|
+
|
96
|
+
const signin = (newUser: string, callback: VoidFunction) =>
|
97
|
+
fakeAuthProvider.signin(() => {
|
98
|
+
setUser(newUser);
|
99
|
+
callback();
|
100
|
+
});
|
101
|
+
|
102
|
+
const signout = (callback: VoidFunction) =>
|
103
|
+
fakeAuthProvider.signout(() => {
|
104
|
+
setUser(null);
|
105
|
+
callback();
|
106
|
+
});
|
107
|
+
|
108
|
+
const value = { user, signin, signout };
|
109
|
+
|
110
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
111
|
+
}
|
112
|
+
|
113
|
+
export function useAuth() {
|
114
|
+
return React.useContext(AuthContext);
|
115
|
+
}
|
116
|
+
|
117
|
+
export function AuthStatus() {
|
118
|
+
const auth = useAuth();
|
119
|
+
console.log('auth', auth);
|
120
|
+
const navigate = useNavigate();
|
121
|
+
|
122
|
+
if (!auth.user) {
|
123
|
+
return <p>You are not logged in.</p>;
|
124
|
+
}
|
125
|
+
|
126
|
+
return (
|
127
|
+
<p>
|
128
|
+
Welcome {auth.user}!{' '}
|
129
|
+
<button
|
130
|
+
type="button"
|
131
|
+
onClick={() => {
|
132
|
+
auth.signout(() => navigate('/'));
|
133
|
+
}}
|
134
|
+
>
|
135
|
+
Sign out
|
136
|
+
</button>
|
137
|
+
</p>
|
138
|
+
);
|
139
|
+
}
|
140
|
+
|
141
|
+
export function RequireAuth({ children }: { children: JSX.Element }) {
|
142
|
+
const auth = useAuth();
|
143
|
+
const location = useLocation();
|
144
|
+
|
145
|
+
if (!auth.user) {
|
146
|
+
// Redirect them to the /login page, but save the current location they were
|
147
|
+
// trying to go to when they were redirected. This allows us to send them
|
148
|
+
// along to that page after they login, which is a nicer user experience
|
149
|
+
// than dropping them off on the home page.
|
150
|
+
return <Navigate to="/login" state={{ from: location }} replace />;
|
151
|
+
}
|
152
|
+
|
153
|
+
return children;
|
154
|
+
}
|
155
|
+
|
156
|
+
```
|
157
|
+
```ts title="src/routes/protected/page.tsx"
|
158
|
+
import { RequireAuth } from '../Auth';
|
159
|
+
|
160
|
+
export default function ProtectedPage() {
|
161
|
+
return (
|
162
|
+
<div className="container-box">
|
163
|
+
<RequireAuth>
|
164
|
+
<h3>Protected</h3>
|
165
|
+
</RequireAuth>
|
166
|
+
</div>
|
167
|
+
);
|
168
|
+
}
|
169
|
+
|
170
|
+
```
|
171
|
+
```ts title="src/routes/login/page.tsx"
|
172
|
+
import { useLocation, useNavigate } from '@modern-js/runtime/router';
|
173
|
+
import { useAuth } from '../Auth';
|
174
|
+
|
175
|
+
export default function Login() {
|
176
|
+
const navigate = useNavigate();
|
177
|
+
const location = useLocation();
|
178
|
+
const auth = useAuth();
|
179
|
+
|
180
|
+
const from = location.state?.from?.pathname || '/';
|
181
|
+
|
182
|
+
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
183
|
+
event.preventDefault();
|
184
|
+
|
185
|
+
const formData = new FormData(event.currentTarget);
|
186
|
+
const username = formData.get('username') as string;
|
187
|
+
|
188
|
+
auth.signin(username, () => {
|
189
|
+
// Send them back to the page they tried to visit when they were
|
190
|
+
// redirected to the login page. Use { replace: true } so we don't create
|
191
|
+
// another entry in the history stack for the login page. This means that
|
192
|
+
// when they get to the protected page and click the back button, they
|
193
|
+
// won't end up back on the login page, which is also really nice for the
|
194
|
+
// user experience.
|
195
|
+
navigate(from, { replace: true });
|
196
|
+
});
|
197
|
+
}
|
198
|
+
|
199
|
+
return (
|
200
|
+
<div>
|
201
|
+
<p>You must log in to view the page at {from}</p>
|
202
|
+
|
203
|
+
<form onSubmit={handleSubmit}>
|
204
|
+
<label>
|
205
|
+
Username: <input name="username" type="text" />
|
206
|
+
</label>{' '}
|
207
|
+
<button type="submit">Login</button>
|
208
|
+
</form>
|
209
|
+
</div>
|
210
|
+
);
|
211
|
+
}
|
212
|
+
|
213
|
+
```
|
214
|
+
</Sandpack>
|
package/package.json
CHANGED
@@ -15,14 +15,17 @@
|
|
15
15
|
"modern",
|
16
16
|
"modern.js"
|
17
17
|
],
|
18
|
-
"version": "2.
|
18
|
+
"version": "2.34.0",
|
19
19
|
"publishConfig": {
|
20
20
|
"registry": "https://registry.npmjs.org/",
|
21
21
|
"access": "public",
|
22
22
|
"provenance": true
|
23
23
|
},
|
24
|
+
"dependencies": {
|
25
|
+
"@modern-js/sandpack-react": "2.34.0"
|
26
|
+
},
|
24
27
|
"peerDependencies": {
|
25
|
-
"@modern-js/builder-doc": "^2.
|
28
|
+
"@modern-js/builder-doc": "^2.34.0"
|
26
29
|
},
|
27
30
|
"devDependencies": {
|
28
31
|
"classnames": "^2",
|
@@ -36,8 +39,8 @@
|
|
36
39
|
"@rspress/shared": "0.0.6",
|
37
40
|
"@types/node": "^16",
|
38
41
|
"@types/fs-extra": "^9",
|
39
|
-
"@modern-js/builder-doc": "2.
|
40
|
-
"@modern-js/doc-plugin-auto-sidebar": "2.
|
42
|
+
"@modern-js/builder-doc": "2.34.0",
|
43
|
+
"@modern-js/doc-plugin-auto-sidebar": "2.34.0"
|
41
44
|
},
|
42
45
|
"scripts": {
|
43
46
|
"dev": "rspress dev",
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import ModernSandpack, { ModernSandpackProps } from '@modern-js/sandpack-react';
|
2
|
+
import React, { PropsWithChildren } from 'react';
|
3
|
+
import { useDark, NoSSR } from 'rspress/runtime';
|
4
|
+
|
5
|
+
import './index.css';
|
6
|
+
|
7
|
+
const Sandpack = (props: PropsWithChildren<ModernSandpackProps>) => {
|
8
|
+
const dark = useDark();
|
9
|
+
const { children, ...otherProps } = props;
|
10
|
+
const files: Record<string, string> = {};
|
11
|
+
React.Children.forEach(children, (child: any) => {
|
12
|
+
if (child) {
|
13
|
+
const { meta, children } = child.props.children.props;
|
14
|
+
const matches = meta.match(/title="(.*)"/);
|
15
|
+
if (matches.length > 1) {
|
16
|
+
files[matches[1]] = children;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
});
|
20
|
+
return (
|
21
|
+
<NoSSR>
|
22
|
+
<ModernSandpack
|
23
|
+
files={files}
|
24
|
+
theme={dark ? 'dark' : 'light'}
|
25
|
+
{...otherProps}
|
26
|
+
/>
|
27
|
+
</NoSSR>
|
28
|
+
);
|
29
|
+
};
|
30
|
+
export default Sandpack;
|