@maiyunnet/kebab 9.0.2 → 9.1.0
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/LICENSE +0 -0
- package/README.md +0 -0
- package/bin/kebab.js +0 -0
- package/doc/kebab-rag.md +604 -604
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/lib/ai.d.ts +0 -0
- package/lib/ai.js +0 -0
- package/lib/buffer.d.ts +0 -0
- package/lib/buffer.js +0 -0
- package/lib/captcha/zcool-addict-italic.ttf +0 -0
- package/lib/captcha.d.ts +0 -0
- package/lib/captcha.js +0 -0
- package/lib/consistent.d.ts +0 -0
- package/lib/consistent.js +0 -0
- package/lib/core.d.ts +0 -0
- package/lib/core.js +0 -0
- package/lib/cron.d.ts +0 -0
- package/lib/cron.js +0 -0
- package/lib/crypto.d.ts +0 -0
- package/lib/crypto.js +0 -0
- package/lib/db/conn.d.ts +0 -0
- package/lib/db/conn.js +0 -0
- package/lib/db/pool.d.ts +0 -0
- package/lib/db/pool.js +0 -0
- package/lib/db/tran.d.ts +0 -0
- package/lib/db/tran.js +8 -8
- package/lib/db.d.ts +0 -0
- package/lib/db.js +0 -0
- package/lib/dns.d.ts +0 -0
- package/lib/dns.js +0 -0
- package/lib/fs.d.ts +0 -0
- package/lib/fs.js +0 -0
- package/lib/kv.d.ts +0 -0
- package/lib/kv.js +0 -0
- package/lib/lan.d.ts +0 -0
- package/lib/lan.js +0 -0
- package/lib/lang.d.ts +0 -0
- package/lib/lang.js +0 -0
- package/lib/net/cacert.pem +0 -0
- package/lib/net/formdata.d.ts +0 -0
- package/lib/net/formdata.js +0 -0
- package/lib/net/request.d.ts +0 -0
- package/lib/net/request.js +0 -0
- package/lib/net/response.d.ts +0 -0
- package/lib/net/response.js +0 -0
- package/lib/net.d.ts +0 -0
- package/lib/net.js +0 -0
- package/lib/ratelimit.d.ts +0 -0
- package/lib/ratelimit.js +0 -0
- package/lib/s3.d.ts +0 -0
- package/lib/s3.js +0 -0
- package/lib/scan.d.ts +0 -0
- package/lib/scan.js +0 -0
- package/lib/session.d.ts +0 -0
- package/lib/session.js +0 -0
- package/lib/socket.d.ts +0 -0
- package/lib/socket.js +0 -0
- package/lib/sql.d.ts +0 -0
- package/lib/sql.js +0 -0
- package/lib/ssh/sftp.d.ts +0 -0
- package/lib/ssh/sftp.js +0 -0
- package/lib/ssh/shell.d.ts +0 -0
- package/lib/ssh/shell.js +0 -0
- package/lib/ssh.d.ts +0 -0
- package/lib/ssh.js +0 -0
- package/lib/text/tld.json +0 -0
- package/lib/text.d.ts +0 -0
- package/lib/text.js +0 -0
- package/lib/time.d.ts +0 -0
- package/lib/time.js +0 -0
- package/lib/turnstile.d.ts +0 -0
- package/lib/turnstile.js +0 -0
- package/lib/vector.d.ts +0 -0
- package/lib/vector.js +0 -0
- package/lib/ws.d.ts +0 -0
- package/lib/ws.js +0 -0
- package/lib/zip.d.ts +0 -0
- package/lib/zip.js +0 -0
- package/lib/zlib.d.ts +0 -0
- package/lib/zlib.js +0 -0
- package/main.d.ts +0 -0
- package/main.js +0 -0
- package/package.json +9 -2
- package/sys/child.d.ts +0 -0
- package/sys/child.js +0 -0
- package/sys/cmd.d.ts +0 -0
- package/sys/cmd.js +69 -42
- package/sys/ctr.d.ts +8 -0
- package/sys/ctr.js +86 -36
- package/sys/master.d.ts +0 -0
- package/sys/master.js +0 -0
- package/sys/mod.d.ts +0 -0
- package/sys/mod.js +0 -0
- package/sys/monitor/watchdog.d.ts +0 -0
- package/sys/monitor/watchdog.js +0 -0
- package/sys/monitor.d.ts +0 -0
- package/sys/monitor.js +1 -1
- package/sys/route.d.ts +0 -0
- package/sys/route.js +0 -0
- package/www/example/ctr/main.d.ts +0 -0
- package/www/example/ctr/main.js +0 -0
- package/www/example/ctr/middle.d.ts +0 -0
- package/www/example/ctr/middle.js +0 -0
- package/www/example/ctr/test.d.ts +4 -0
- package/www/example/ctr/test.js +12 -3
- package/www/example/data/locale/en.test.json +0 -0
- package/www/example/data/locale/index.html +0 -0
- package/www/example/data/locale/ja.test.json +0 -0
- package/www/example/data/locale/sc.test.json +0 -0
- package/www/example/data/locale/tc.test.json +0 -0
- package/www/example/data/test.zip +0 -0
- package/www/example/kebab.json +0 -0
- package/www/example/mod/test.d.ts +0 -0
- package/www/example/mod/test.js +0 -0
- package/www/example/mod/testdata.d.ts +0 -0
- package/www/example/mod/testdata.js +0 -0
- package/www/example/route.json +0 -0
- package/www/example/stc/chunk-YJ3GYATF.js +81 -0
- package/www/example/stc/lib/ui/checkbox.d.ts +9 -0
- package/www/example/stc/lib/ui/checkbox.js +11 -0
- package/www/example/stc/lib/ui/checkbox.tsx +30 -0
- package/www/example/stc/lib/ui/input.d.ts +3 -0
- package/www/example/stc/lib/ui/input.js +10 -0
- package/www/example/stc/lib/ui/input.tsx +24 -0
- package/www/example/stc/lib/ui/label.d.ts +11 -0
- package/www/example/stc/lib/ui/label.js +13 -0
- package/www/example/stc/lib/ui/label.tsx +24 -0
- package/www/example/stc/lib/ui/switch.d.ts +9 -0
- package/www/example/stc/lib/ui/switch.js +11 -0
- package/www/example/stc/lib/ui/switch.tsx +31 -0
- package/www/example/stc/lib/utils.d.ts +7 -0
- package/www/example/stc/lib/utils.js +10 -0
- package/www/example/stc/view/hello.page.bundle.js +1 -0
- package/www/example/stc/view/hello.page.css +2 -0
- package/www/example/stc/view/hello.page.d.ts +17 -0
- package/www/example/stc/view/hello.page.js +15 -0
- package/www/example/stc/view/hello.page.tsx +49 -0
- package/www/example/stc/view/react-router.page.bundle.js +1 -0
- package/www/example/stc/view/react-router.page.css +2 -0
- package/www/example/stc/view/{react-router-page.d.ts → react-router.page.d.ts} +1 -1
- package/www/example/stc/view/{react-router-page.js → react-router.page.js} +1 -1
- package/www/example/stc/view/{react-router-page.tsx → react-router.page.tsx} +1 -1
- package/www/example/stc/view/react.page.bundle.js +26 -0
- package/www/example/stc/view/react.page.css +2 -0
- package/www/example/stc/view/{react-page.d.ts → react.page.d.ts} +16 -18
- package/www/example/stc/view/react.page.js +181 -0
- package/www/example/stc/view/{react-page.tsx → react.page.tsx} +259 -111
- package/www/example/view/test.ejs +0 -0
- package/www/example/ws/handler.d.ts +0 -0
- package/www/example/ws/handler.js +0 -0
- package/www/example/ws/main.d.ts +0 -0
- package/www/example/ws/main.js +0 -0
- package/www/example/ws/mproxy.d.ts +0 -0
- package/www/example/ws/mproxy.js +0 -0
- package/www/example/ws/rproxy.d.ts +0 -0
- package/www/example/ws/rproxy.js +0 -0
- package/www/example/ws/rsocket.d.ts +0 -0
- package/www/example/ws/rsocket.js +0 -0
- package/www/example/ws/test.d.ts +0 -0
- package/www/example/ws/test.js +0 -0
- package/www/example/stc/view/react-page.bundle.js +0 -97
- package/www/example/stc/view/react-page.css +0 -2
- package/www/example/stc/view/react-page.js +0 -136
- package/www/example/stc/view/react-router-page.bundle.js +0 -81
- package/www/example/stc/view/react-router-page.css +0 -2
|
@@ -1,33 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* --- Kebab React 全页面示例组件 ---
|
|
3
3
|
*
|
|
4
|
+
* node ./source/main build -d source/www/example/stc
|
|
5
|
+
*
|
|
6
|
+
* 【文件命名约定】
|
|
7
|
+
* 文件名以 .page.tsx 结尾的为页面入口(本文件),框架会将其打包为 .page.bundle.js。
|
|
8
|
+
* 其余 .tsx 均视为组件(如 stc/lib/ui/button.tsx),不会被单独打包。
|
|
9
|
+
*
|
|
4
10
|
* 【使用方式】
|
|
5
|
-
* 在 Ctr 方法中调用 _loadReactPage('view/react
|
|
6
|
-
*
|
|
7
|
-
* 组件在 <head> 和 <body> 末尾渲染对应的 <script> 标签即可,其余部分与普通 React 组件无异。
|
|
11
|
+
* 在 Ctr 方法中调用 _loadReactPage('view/react', { ...props }),
|
|
12
|
+
* 框架自动将 import map、props JSON、水合脚本注入 HTML,组件只写业务逻辑,无需任何框架样板代码。
|
|
8
13
|
*
|
|
9
14
|
* 【多页面 / 共用组件】
|
|
10
|
-
* 将公共 UI(如
|
|
15
|
+
* 将公共 UI(如 Label/Input/Checkbox)放在 stc/lib/ui/ 目录,各页面 import 进来。
|
|
16
|
+
* 这和 shadcn/ui 推荐的 components/ui/ 目录结构完全一致。
|
|
11
17
|
* tsc watch 会自动将所有 .tsx 编译为同路径的 .js,无需打包工具。
|
|
12
18
|
*
|
|
13
19
|
* 【编译方式】
|
|
14
20
|
* 本文件 (.tsx) 已集成到 source/tsconfig.json 的 include 中,
|
|
15
21
|
* 开启 `tsc: watch - source/tsconfig.json` 即可自动编译,无需额外命令。
|
|
16
|
-
* 编译输出:stc/view/react
|
|
22
|
+
* 编译输出:stc/view/react.page.js(由 tsc 覆盖写入,勿手动编辑 .js 文件)
|
|
17
23
|
*
|
|
18
24
|
* 【运行时机】
|
|
19
25
|
* - 服务端(Node.js):_loadReactPage() 调用 renderToString(),产出完整 HTML 字符串。
|
|
20
26
|
* - 客户端(浏览器):import map 将 bare import 解析到 esm.sh,hydrateRoot 接管 document。
|
|
21
27
|
* - 两端使用同一份 JS 文件:服务端从磁盘读,浏览器从静态 URL 下载。
|
|
22
28
|
*
|
|
29
|
+
* 【第三方包 import map 自动解析】
|
|
30
|
+
* 开发模式下,框架会自动扫描入口 JS 及其相对引用中的所有 bare specifier,
|
|
31
|
+
* 并通过 esm.sh CDN 自动生成 import map 条目,无需手动配置。
|
|
32
|
+
*
|
|
23
33
|
* 【限制】
|
|
24
34
|
* - 不能包含 Node.js 专属代码(fs/path/lDb 等),数据必须通过 props 传入。
|
|
25
35
|
* - 等价于 Next.js 的 Client Component,而非 Server Component。
|
|
26
36
|
*/
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
// --- React / React Router ---
|
|
39
|
+
import { useState, useEffect, useId, type ReactNode } from 'react';
|
|
29
40
|
import { MemoryRouter, Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
|
|
30
41
|
|
|
42
|
+
// --- shadcn/ui 控件(从 stc/lib/ui/ 引入,与行业标准 components/ui/ 目录结构一致)---
|
|
43
|
+
import { Label } from '../lib/ui/label.js';
|
|
44
|
+
import { Input } from '../lib/ui/input.js';
|
|
45
|
+
import { Checkbox } from '../lib/ui/checkbox.js';
|
|
46
|
+
import { Switch } from '../lib/ui/switch.js';
|
|
47
|
+
|
|
31
48
|
// --- 组件接收的 props 接口,_urlXxx/_staticVer 由框架自动注入 ---
|
|
32
49
|
interface IProps {
|
|
33
50
|
'title': string;
|
|
@@ -37,24 +54,9 @@ interface IProps {
|
|
|
37
54
|
'_urlStc': string;
|
|
38
55
|
'_urlFull': string;
|
|
39
56
|
'_staticVer': string;
|
|
40
|
-
/** --- 框架注入:import map JSON 字符串,<head> 中以 type="importmap" <script> 渲染 --- */
|
|
41
|
-
'_importMapJson'?: string;
|
|
42
|
-
/**
|
|
43
|
-
* --- 框架注入:水合 JS 代码字符串,</body> 前以 type="module" <script> 渲染 ---
|
|
44
|
-
* --- 内容:import hydrateRoot + import App + hydrateRoot(document, createElement(App, p)) ---
|
|
45
|
-
*/
|
|
46
|
-
'_hydrateScript'?: string;
|
|
47
|
-
/**
|
|
48
|
-
* --- 框架注入:fullProps 的 JSON 序列化(不含 _propsJson 本身)---
|
|
49
|
-
* --- 客户端水合脚本读取此值重建 props,suppressHydrationWarning 处理服务端/客户端内容差异 ---
|
|
50
|
-
*/
|
|
51
|
-
'_propsJson'?: string;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
// ───
|
|
55
|
-
// 以下组件是 Tailwind + React 的轻量实现,与 shadcn/ui 源码结构完全一致。
|
|
56
|
-
// 生产环境可直接替换为 shadcn/ui 官方组件,import map 会通过 esm.sh 自动
|
|
57
|
-
// 解析 Radix UI 等所有依赖,无需本地 npm install 也无需打包工具。
|
|
59
|
+
// ─── 页面内部组件(仅本页演示使用,无需提取到 lib/)────────────────────────────
|
|
58
60
|
|
|
59
61
|
/** --- 卡片容器 --- */
|
|
60
62
|
function Card({ children, className = '' }: { 'children': ReactNode; 'className'?: string }) {
|
|
@@ -110,7 +112,7 @@ function Btn({ children, onClick, disabled = false, outline = false }: {
|
|
|
110
112
|
function RouterHome() {
|
|
111
113
|
return (
|
|
112
114
|
<div>
|
|
113
|
-
<p className="text-slate-700 text-sm font-medium mb-3"
|
|
115
|
+
<p className="text-slate-700 text-sm font-medium mb-3">Current route: <code className="bg-slate-100 px-1 rounded">/</code></p>
|
|
114
116
|
<div className="flex gap-2 flex-wrap">
|
|
115
117
|
<Link to="/about" className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-500 hover:bg-blue-600 text-white text-xs font-medium">
|
|
116
118
|
→ /about
|
|
@@ -131,14 +133,14 @@ function RouterAbout() {
|
|
|
131
133
|
const navigate = useNavigate();
|
|
132
134
|
return (
|
|
133
135
|
<div>
|
|
134
|
-
<p className="text-slate-700 text-sm font-medium mb-3"
|
|
135
|
-
<p className="text-slate-500 text-xs mb-3">useNavigate()
|
|
136
|
+
<p className="text-slate-700 text-sm font-medium mb-3">Current route: <code className="bg-slate-100 px-1 rounded">/about</code></p>
|
|
137
|
+
<p className="text-slate-500 text-xs mb-3">useNavigate() programmatic navigation (without Link component):</p>
|
|
136
138
|
<div className="flex gap-2">
|
|
137
139
|
<button
|
|
138
140
|
onClick={() => navigate('/')}
|
|
139
141
|
className="inline-flex items-center px-3 py-1.5 rounded-lg border border-slate-300 hover:bg-slate-50 text-slate-700 text-xs font-medium cursor-pointer"
|
|
140
142
|
>
|
|
141
|
-
←
|
|
143
|
+
← Back to /
|
|
142
144
|
</button>
|
|
143
145
|
<button
|
|
144
146
|
onClick={() => navigate('/user/99')}
|
|
@@ -158,29 +160,202 @@ function RouterUser() {
|
|
|
158
160
|
return (
|
|
159
161
|
<div>
|
|
160
162
|
<p className="text-slate-700 text-sm font-medium mb-1">
|
|
161
|
-
|
|
163
|
+
Current route: <code className="bg-slate-100 px-1 rounded">/user/:id</code>
|
|
162
164
|
</p>
|
|
163
165
|
<p className="text-slate-500 text-xs mb-3">
|
|
164
|
-
useParams()
|
|
166
|
+
useParams() dynamic segment: <code className="bg-slate-100 px-1 rounded">id = "{id}"</code>
|
|
165
167
|
</p>
|
|
166
168
|
<button
|
|
167
169
|
onClick={() => navigate('/')}
|
|
168
170
|
className="inline-flex items-center px-3 py-1.5 rounded-lg border border-slate-300 hover:bg-slate-50 text-slate-700 text-xs font-medium cursor-pointer"
|
|
169
171
|
>
|
|
170
|
-
←
|
|
172
|
+
← Back to /
|
|
171
173
|
</button>
|
|
172
174
|
</div>
|
|
173
175
|
);
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
// ─── shadcn/ui 演示区 ────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* --- 展示 Label/Input/Checkbox/Switch 组合使用 ---
|
|
182
|
+
* 控件定义在 stc/lib/ui/ 目录,本组件只做 import + 组合。
|
|
183
|
+
*/
|
|
184
|
+
function ShadcnDemo() {
|
|
185
|
+
// --- 表单受控状态 ---
|
|
186
|
+
const [name, setName] = useState('');
|
|
187
|
+
const [email, setEmail] = useState('');
|
|
188
|
+
const [agree, setAgree] = useState<boolean | 'indeterminate'>(false);
|
|
189
|
+
const [newsletter, setNewsletter] = useState(false);
|
|
190
|
+
const [submitted, setSubmitted] = useState(false);
|
|
191
|
+
|
|
192
|
+
// --- useId 生成无障碍访问用的稳定 id(SSR + 水合均确保一致)---
|
|
193
|
+
const nameId = useId();
|
|
194
|
+
const emailId = useId();
|
|
195
|
+
const agreeId = useId();
|
|
196
|
+
const newsletterId = useId();
|
|
197
|
+
|
|
198
|
+
function handleSubmit(e: React.FormEvent<HTMLFormElement>): void {
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
setSubmitted(true);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div className="space-y-4">
|
|
205
|
+
|
|
206
|
+
{/* ── 说明 ── */}
|
|
207
|
+
<Card>
|
|
208
|
+
<h2 className="font-semibold text-slate-900 mb-2">shadcn/ui Components</h2>
|
|
209
|
+
<p className="text-slate-500 text-xs leading-relaxed">
|
|
210
|
+
All components below are imported from <code className="bg-slate-100 px-1 rounded">stc/lib/ui/</code>,
|
|
211
|
+
following the same <code className="bg-slate-100 px-1 rounded">components/ui/</code> structure recommended by shadcn/ui.
|
|
212
|
+
Built on Radix UI primitives, styled with Tailwind CSS.
|
|
213
|
+
In dev mode, the framework auto-scans all bare imports and generates the import map automatically.
|
|
214
|
+
</p>
|
|
215
|
+
</Card>
|
|
216
|
+
|
|
217
|
+
{/* ── Label + Input 演示 ── */}
|
|
218
|
+
<Card>
|
|
219
|
+
<h2 className="font-semibold text-slate-900 mb-4">Label + Input</h2>
|
|
220
|
+
{/* Label 使用 @radix-ui/react-label,htmlFor 关联 Input 的 id,点击 Label 可聚焦 Input */}
|
|
221
|
+
<div className="grid gap-4">
|
|
222
|
+
<div className="grid gap-1.5">
|
|
223
|
+
<Label htmlFor={nameId}>Username</Label>
|
|
224
|
+
<Input
|
|
225
|
+
id={nameId}
|
|
226
|
+
type="text"
|
|
227
|
+
placeholder="Enter your username"
|
|
228
|
+
value={name}
|
|
229
|
+
onChange={e => setName(e.target.value)}
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
<div className="grid gap-1.5">
|
|
233
|
+
<Label htmlFor={emailId}>Email</Label>
|
|
234
|
+
<Input
|
|
235
|
+
id={emailId}
|
|
236
|
+
type="email"
|
|
237
|
+
placeholder="you@example.com"
|
|
238
|
+
value={email}
|
|
239
|
+
onChange={e => setEmail(e.target.value)}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
<p className="text-xs text-slate-400 mt-3">Click the Label text to focus the corresponding input (Radix UI a11y semantics)</p>
|
|
244
|
+
</Card>
|
|
245
|
+
|
|
246
|
+
{/* ── Checkbox 演示 ── */}
|
|
247
|
+
<Card>
|
|
248
|
+
<h2 className="font-semibold text-slate-900 mb-4">Checkbox</h2>
|
|
249
|
+
{/* Checkbox 使用 @radix-ui/react-checkbox,支持 checked / indeterminate 三态 */}
|
|
250
|
+
<div className="flex items-center gap-2">
|
|
251
|
+
<Checkbox
|
|
252
|
+
id={agreeId}
|
|
253
|
+
checked={agree}
|
|
254
|
+
onCheckedChange={setAgree}
|
|
255
|
+
/>
|
|
256
|
+
<Label htmlFor={agreeId}>I have read and agree to the Terms of Service</Label>
|
|
257
|
+
</div>
|
|
258
|
+
<p className="text-xs text-slate-400 mt-3">
|
|
259
|
+
Value: <code className="bg-slate-100 px-1 rounded">{String(agree)}</code>
|
|
260
|
+
(supports <code className="bg-slate-100 px-1 rounded">true | false | 'indeterminate'</code> tri-state)
|
|
261
|
+
</p>
|
|
262
|
+
</Card>
|
|
263
|
+
|
|
264
|
+
{/* ── Switch 演示 ── */}
|
|
265
|
+
<Card>
|
|
266
|
+
<h2 className="font-semibold text-slate-900 mb-4">Switch</h2>
|
|
267
|
+
{/* Switch 使用 @radix-ui/react-switch,onCheckedChange 接收 boolean */}
|
|
268
|
+
<div className="flex items-center gap-2">
|
|
269
|
+
<Switch
|
|
270
|
+
id={newsletterId}
|
|
271
|
+
checked={newsletter}
|
|
272
|
+
onCheckedChange={setNewsletter}
|
|
273
|
+
/>
|
|
274
|
+
<Label htmlFor={newsletterId}>Subscribe to product updates</Label>
|
|
275
|
+
</div>
|
|
276
|
+
<p className="text-xs text-slate-400 mt-3">
|
|
277
|
+
Value: <code className="bg-slate-100 px-1 rounded">{newsletter ? 'true' : 'false'}</code>
|
|
278
|
+
</p>
|
|
279
|
+
</Card>
|
|
280
|
+
|
|
281
|
+
{/* ── 综合表单提交 ── */}
|
|
282
|
+
<Card>
|
|
283
|
+
<h2 className="font-semibold text-slate-900 mb-4">Combined Form (Submit Demo)</h2>
|
|
284
|
+
{submitted ? (
|
|
285
|
+
<div className="space-y-2 text-sm">
|
|
286
|
+
<p className="text-green-700 font-medium">✓ Submitted! Received data:</p>
|
|
287
|
+
<pre className="bg-slate-50 border border-slate-200 rounded-lg p-3 text-xs text-slate-700 overflow-auto">{JSON.stringify(
|
|
288
|
+
{ name, email, agree: Boolean(agree), newsletter },
|
|
289
|
+
null,
|
|
290
|
+
2
|
|
291
|
+
)}</pre>
|
|
292
|
+
<button
|
|
293
|
+
onClick={() => setSubmitted(false)}
|
|
294
|
+
className="text-xs text-blue-500 hover:underline cursor-pointer"
|
|
295
|
+
>Reset</button>
|
|
296
|
+
</div>
|
|
297
|
+
) : (
|
|
298
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
299
|
+
<div className="grid gap-1.5">
|
|
300
|
+
<Label htmlFor={`${nameId}-form`}>Username <span className="text-red-500">*</span></Label>
|
|
301
|
+
<Input
|
|
302
|
+
id={`${nameId}-form`}
|
|
303
|
+
required
|
|
304
|
+
placeholder="At least 2 characters"
|
|
305
|
+
value={name}
|
|
306
|
+
onChange={e => setName(e.target.value)}
|
|
307
|
+
/>
|
|
308
|
+
</div>
|
|
309
|
+
<div className="grid gap-1.5">
|
|
310
|
+
<Label htmlFor={`${emailId}-form`}>Email <span className="text-red-500">*</span></Label>
|
|
311
|
+
<Input
|
|
312
|
+
id={`${emailId}-form`}
|
|
313
|
+
type="email"
|
|
314
|
+
required
|
|
315
|
+
placeholder="you@example.com"
|
|
316
|
+
value={email}
|
|
317
|
+
onChange={e => setEmail(e.target.value)}
|
|
318
|
+
/>
|
|
319
|
+
</div>
|
|
320
|
+
<div className="flex items-center gap-2">
|
|
321
|
+
<Checkbox
|
|
322
|
+
id={`${agreeId}-form`}
|
|
323
|
+
checked={agree}
|
|
324
|
+
onCheckedChange={setAgree}
|
|
325
|
+
required
|
|
326
|
+
/>
|
|
327
|
+
<Label htmlFor={`${agreeId}-form`}>Agree to terms</Label>
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex items-center gap-2">
|
|
330
|
+
<Switch
|
|
331
|
+
id={`${newsletterId}-form`}
|
|
332
|
+
checked={newsletter}
|
|
333
|
+
onCheckedChange={setNewsletter}
|
|
334
|
+
/>
|
|
335
|
+
<Label htmlFor={`${newsletterId}-form`}>Subscribe</Label>
|
|
336
|
+
</div>
|
|
337
|
+
<button
|
|
338
|
+
type="submit"
|
|
339
|
+
className="inline-flex items-center px-4 py-2 rounded-lg bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium cursor-pointer"
|
|
340
|
+
>
|
|
341
|
+
Submit
|
|
342
|
+
</button>
|
|
343
|
+
</form>
|
|
344
|
+
)}
|
|
345
|
+
</Card>
|
|
346
|
+
|
|
347
|
+
</div>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
176
351
|
// ─── 页面主组件 ────────────────────────────────────────────────────────────────
|
|
177
352
|
|
|
178
353
|
/** --- Kebab React 全页面演示 --- */
|
|
179
|
-
export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
354
|
+
export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc, _staticVer }: IProps) {
|
|
180
355
|
|
|
181
356
|
// --- useState 在 SSR 阶段使用初始值渲染,客户端水合后变为可交互状态 ---
|
|
182
357
|
const [count, setCount] = useState(0);
|
|
183
|
-
const [tab, setTab] = useState<'overview' | 'routing' | 'fetch'>('overview');
|
|
358
|
+
const [tab, setTab] = useState<'overview' | 'routing' | 'fetch' | 'shadcn'>('overview');
|
|
184
359
|
const [fetchResult, setFetchResult] = useState<string | null>(null);
|
|
185
360
|
const [isFetching, setIsFetching] = useState(false);
|
|
186
361
|
/** --- 仅客户端为 true,用于展示水合已完成 --- */
|
|
@@ -214,28 +389,12 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
214
389
|
// --- 组件渲染完整 HTML 文档,无需外部 EJS 模板 ---
|
|
215
390
|
<html lang="en">
|
|
216
391
|
<head>
|
|
217
|
-
{/*
|
|
218
|
-
meta/title 放在 <head> 最前,与 renderToString 实际输出顺序保持一致,
|
|
219
|
-
避免客户端水合时虚拟 DOM 与浏览器 DOM 的顺序不匹配(#418)。
|
|
220
|
-
*/}
|
|
221
392
|
<meta charSet="UTF-8" />
|
|
222
393
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
223
394
|
<title>{title}</title>
|
|
224
|
-
{/*
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
服务端:_importMapJson 有值 → 正常渲染。
|
|
228
|
-
客户端:同一值读自 __kebab_props__ → 渲染相同 → 水合匹配。
|
|
229
|
-
*/}
|
|
230
|
-
{_importMapJson && (
|
|
231
|
-
<script type="importmap" dangerouslySetInnerHTML={{ '__html': _importMapJson }} />
|
|
232
|
-
)}
|
|
233
|
-
{/*
|
|
234
|
-
Tailwind CSS CDN:开发和演示可直接使用。
|
|
235
|
-
生产环境建议用 `npx tailwindcss build` 输出 purged CSS,
|
|
236
|
-
然后改为:<link rel="stylesheet" href="/stc/css/app.css" />
|
|
237
|
-
*/}
|
|
238
|
-
<script src="https://cdn.tailwindcss.com"></script>
|
|
395
|
+
{/* import map 由框架自动注入在此标签前,无需手动添加 */}
|
|
396
|
+
{/* dev: Tailwind Play CDN;prod: 将此行替换为 kebab build 编译的 CSS 文件 */}
|
|
397
|
+
<link href={`${_urlStc}view/react.page.css?v=${_staticVer}`} rel="stylesheet" />
|
|
239
398
|
</head>
|
|
240
399
|
<body className="bg-slate-50 min-h-screen font-sans">
|
|
241
400
|
<div className="max-w-3xl mx-auto px-4 py-10 space-y-6">
|
|
@@ -255,7 +414,7 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
255
414
|
|
|
256
415
|
{/* ── Tab 导航(客户端状态路由,无页面刷新) ── */}
|
|
257
416
|
<div className="flex gap-1 bg-slate-100 p-1 rounded-lg w-fit">
|
|
258
|
-
{(['overview', 'routing', 'fetch'] as const).map(t => (
|
|
417
|
+
{(['overview', 'routing', 'fetch', 'shadcn'] as const).map(t => (
|
|
259
418
|
<button
|
|
260
419
|
key={t}
|
|
261
420
|
onClick={() => setTab(t)}
|
|
@@ -277,8 +436,8 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
277
436
|
<Card>
|
|
278
437
|
<h2 className="font-semibold text-slate-900 mb-1">Counter(useState + hydration)</h2>
|
|
279
438
|
<p className="text-slate-500 text-xs mb-4">
|
|
280
|
-
SSR
|
|
281
|
-
|
|
439
|
+
SSR renders with initial value 0; props are serialized as inline JSON.
|
|
440
|
+
After hydration the state becomes interactive — click the buttons to test:
|
|
282
441
|
</p>
|
|
283
442
|
<div className="flex items-center gap-4">
|
|
284
443
|
<button
|
|
@@ -295,12 +454,12 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
295
454
|
|
|
296
455
|
{/* Server Props 展示 */}
|
|
297
456
|
<Card>
|
|
298
|
-
<h2 className="font-semibold text-slate-900 mb-3">Server Props
|
|
457
|
+
<h2 className="font-semibold text-slate-900 mb-3">Server Props (from Ctr method)</h2>
|
|
299
458
|
<p className="text-slate-500 text-xs mb-3">
|
|
300
|
-
|
|
301
|
-
<code className="bg-slate-100 px-1 rounded">_loadReactPage(path, props)</code
|
|
302
|
-
|
|
303
|
-
|
|
459
|
+
Passed via{' '}
|
|
460
|
+
<code className="bg-slate-100 px-1 rounded">_loadReactPage(path, props)</code>.
|
|
461
|
+
The framework auto-injects constants like _urlBase and serializes
|
|
462
|
+
all props as inline JSON, reused directly during client hydration — no extra request needed.
|
|
304
463
|
</p>
|
|
305
464
|
<div className="space-y-2">
|
|
306
465
|
{([
|
|
@@ -317,20 +476,24 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
317
476
|
</div>
|
|
318
477
|
</Card>
|
|
319
478
|
|
|
320
|
-
{/*
|
|
479
|
+
{/* 项目结构说明 */}
|
|
321
480
|
<Card>
|
|
322
|
-
<h2 className="font-semibold text-slate-900 mb-2">
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
481
|
+
<h2 className="font-semibold text-slate-900 mb-2">Recommended Directory Structure</h2>
|
|
482
|
+
<pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs leading-relaxed text-slate-700">{`stc/
|
|
483
|
+
lib/
|
|
484
|
+
utils.ts # cn() utility
|
|
485
|
+
ui/
|
|
486
|
+
label.tsx # shadcn Label
|
|
487
|
+
input.tsx # shadcn Input
|
|
488
|
+
checkbox.tsx # shadcn Checkbox
|
|
489
|
+
switch.tsx # shadcn Switch
|
|
490
|
+
view/
|
|
491
|
+
react-page.tsx # Page component (import + compose)`}
|
|
492
|
+
</pre>
|
|
493
|
+
<p className="text-slate-500 text-xs mt-3">
|
|
494
|
+
Mirrors the shadcn/ui <code className="bg-slate-100 px-1 rounded">components/ui/</code> convention.
|
|
495
|
+
In dev mode, the framework auto-scans imports — no manual import map configuration needed.
|
|
327
496
|
</p>
|
|
328
|
-
<div className="flex flex-wrap gap-2">
|
|
329
|
-
<Badge variant="success">Card ✓</Badge>
|
|
330
|
-
<Badge variant="success">Badge ✓</Badge>
|
|
331
|
-
<Badge variant="success">Btn ✓</Badge>
|
|
332
|
-
<Badge>Radix UI Dialog 同样支持</Badge>
|
|
333
|
-
</div>
|
|
334
497
|
</Card>
|
|
335
498
|
|
|
336
499
|
</div>
|
|
@@ -345,11 +508,11 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
345
508
|
*/
|
|
346
509
|
<MemoryRouter>
|
|
347
510
|
<Card>
|
|
348
|
-
<h2 className="font-semibold text-slate-900 mb-1">React Router
|
|
511
|
+
<h2 className="font-semibold text-slate-900 mb-1">React Router Demo</h2>
|
|
349
512
|
<p className="text-slate-500 text-xs mb-4">
|
|
350
|
-
|
|
351
|
-
{' '}
|
|
352
|
-
|
|
513
|
+
Uses <code className="bg-slate-100 px-1 rounded">MemoryRouter</code>
|
|
514
|
+
{' '}to demonstrate route navigation, dynamic params (useParams), and programmatic navigation (useNavigate).
|
|
515
|
+
Works on both server SSR and client hydration with no extra server configuration.
|
|
353
516
|
</p>
|
|
354
517
|
{/* ── 路由定义 ── */}
|
|
355
518
|
<Routes>
|
|
@@ -361,39 +524,44 @@ export default function ReactPage({ title, serverTime, node, _urlBase, _urlStc,
|
|
|
361
524
|
|
|
362
525
|
{/* 整页 BrowserRouter 配置说明 */}
|
|
363
526
|
<Card className="mt-4">
|
|
364
|
-
<h2 className="font-semibold text-slate-900 mb-2"
|
|
527
|
+
<h2 className="font-semibold text-slate-900 mb-2">Full-Page BrowserRouter Setup</h2>
|
|
365
528
|
<p className="text-slate-500 text-xs mb-3">
|
|
366
|
-
|
|
367
|
-
|
|
529
|
+
To let React Router manage the real URL (e.g. /app, /app/about),
|
|
530
|
+
replace MemoryRouter with BrowserRouter and route all sub-paths to the same Ctr method on the server:
|
|
368
531
|
</p>
|
|
369
|
-
<pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs overflow-auto leading-relaxed text-slate-700">{`// 1. route.json
|
|
532
|
+
<pre className="bg-slate-50 border border-slate-200 rounded-lg p-4 text-xs overflow-auto leading-relaxed text-slate-700">{`// 1. route.json: 将所有子路径路由到同一 Ctr 方法
|
|
370
533
|
{
|
|
371
534
|
"app": "ctr/app@reactPage",
|
|
372
535
|
"app\\/.*": "ctr/app@reactPage"
|
|
373
536
|
}
|
|
374
537
|
|
|
375
|
-
// 2.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
// 3.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
538
|
+
// 2. Ctr 方法:传入 router: 'browser',框架自动处理 SSR(StaticRouter)和客户端(BrowserRouter)
|
|
539
|
+
await this._loadReactPage('view/my', { ...props }, {
|
|
540
|
+
router: 'browser',
|
|
541
|
+
routerBase: 'app', // basename,相对于 urlBase
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// 3. 组件:直接使用 Routes/Route/Link,不需要包裹任何 Router 层
|
|
545
|
+
<Routes>
|
|
546
|
+
<Route path="/" element={<Home />} />
|
|
547
|
+
<Route path="/user/:id" element={<User />} />
|
|
548
|
+
</Routes>`}
|
|
386
549
|
</pre>
|
|
387
550
|
</Card>
|
|
388
551
|
</MemoryRouter>
|
|
389
552
|
)}
|
|
390
553
|
|
|
554
|
+
{/* ══ shadcn/ui Tab ══ */}
|
|
555
|
+
{tab === 'shadcn' && (
|
|
556
|
+
<ShadcnDemo />
|
|
557
|
+
)}
|
|
558
|
+
|
|
391
559
|
{/* ══ Fetch Tab ══ */}
|
|
392
560
|
{tab === 'fetch' && (
|
|
393
561
|
<Card>
|
|
394
562
|
<h2 className="font-semibold text-slate-900 mb-3">Client Fetch Demo</h2>
|
|
395
563
|
<p className="text-slate-500 text-xs mb-4">
|
|
396
|
-
|
|
564
|
+
After hydration, click a button to send a GET request. setState triggers a partial re-render — no page refresh.
|
|
397
565
|
</p>
|
|
398
566
|
<div className="flex gap-3 flex-wrap">
|
|
399
567
|
<Btn
|
|
@@ -421,27 +589,7 @@ renderToString(
|
|
|
421
589
|
)}
|
|
422
590
|
|
|
423
591
|
</div>
|
|
424
|
-
{/*
|
|
425
|
-
__kebab_props__:存储 fullProps JSON 供水合脚本读取。放在 <body> 末尾以确保
|
|
426
|
-
JSX 树结构与 renderToString 输出、浏览器 DOM 三者一致,避免水合错误 #418。
|
|
427
|
-
服务端:_propsJson 有值(fullProps 的完整 JSON)。
|
|
428
|
-
客户端:_propsJson 为 undefined(本身不在 JSON 中),渲染空串。
|
|
429
|
-
suppressHydrationWarning:跳过此元素的文本内容比对。
|
|
430
|
-
*/}
|
|
431
|
-
<script
|
|
432
|
-
id="__kebab_props__"
|
|
433
|
-
type="application/json"
|
|
434
|
-
suppressHydrationWarning
|
|
435
|
-
dangerouslySetInnerHTML={{ '__html': _propsJson ?? '' }}
|
|
436
|
-
/>
|
|
437
|
-
{/*
|
|
438
|
-
水合脚本:读取 __kebab_props__ → hydrateRoot(document, createElement(App, p))。
|
|
439
|
-
服务端:_hydrateScript 有值 → 渲染此 script 元素。
|
|
440
|
-
客户端:同一值读自 __kebab_props__ → 渲染相同 → 水合匹配。
|
|
441
|
-
*/}
|
|
442
|
-
{_hydrateScript && (
|
|
443
|
-
<script type="module" dangerouslySetInnerHTML={{ '__html': _hydrateScript }} />
|
|
444
|
-
)}
|
|
592
|
+
{/* props JSON 与水合脚本由框架自动注入在此标签前,无需手动添加 */}
|
|
445
593
|
</body>
|
|
446
594
|
</html>
|
|
447
595
|
);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/www/example/ws/main.d.ts
CHANGED
|
File without changes
|
package/www/example/ws/main.js
CHANGED
|
File without changes
|
|
File without changes
|
package/www/example/ws/mproxy.js
CHANGED
|
File without changes
|
|
File without changes
|
package/www/example/ws/rproxy.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/www/example/ws/test.d.ts
CHANGED
|
File without changes
|
package/www/example/ws/test.js
CHANGED
|
File without changes
|