@maiyunnet/kebab 9.0.2 → 9.1.1

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.
Files changed (166) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +0 -0
  3. package/bin/kebab.js +0 -0
  4. package/doc/kebab-rag.md +616 -616
  5. package/index.d.ts +1 -1
  6. package/index.js +1 -1
  7. package/lib/ai.d.ts +0 -0
  8. package/lib/ai.js +0 -0
  9. package/lib/buffer.d.ts +0 -0
  10. package/lib/buffer.js +0 -0
  11. package/lib/captcha/zcool-addict-italic.ttf +0 -0
  12. package/lib/captcha.d.ts +0 -0
  13. package/lib/captcha.js +0 -0
  14. package/lib/consistent.d.ts +0 -0
  15. package/lib/consistent.js +0 -0
  16. package/lib/core.d.ts +0 -0
  17. package/lib/core.js +0 -0
  18. package/lib/cron.d.ts +0 -0
  19. package/lib/cron.js +0 -0
  20. package/lib/crypto.d.ts +0 -0
  21. package/lib/crypto.js +0 -0
  22. package/lib/db/conn.d.ts +0 -0
  23. package/lib/db/conn.js +0 -0
  24. package/lib/db/pool.d.ts +0 -0
  25. package/lib/db/pool.js +0 -0
  26. package/lib/db/tran.d.ts +0 -0
  27. package/lib/db/tran.js +8 -8
  28. package/lib/db.d.ts +0 -0
  29. package/lib/db.js +0 -0
  30. package/lib/dns.d.ts +0 -0
  31. package/lib/dns.js +0 -0
  32. package/lib/fs.d.ts +0 -0
  33. package/lib/fs.js +0 -0
  34. package/lib/kv.d.ts +0 -0
  35. package/lib/kv.js +0 -0
  36. package/lib/lan.d.ts +0 -0
  37. package/lib/lan.js +0 -0
  38. package/lib/lang.d.ts +0 -0
  39. package/lib/lang.js +0 -0
  40. package/lib/net/cacert.pem +0 -0
  41. package/lib/net/formdata.d.ts +0 -0
  42. package/lib/net/formdata.js +0 -0
  43. package/lib/net/request.d.ts +0 -0
  44. package/lib/net/request.js +0 -0
  45. package/lib/net/response.d.ts +0 -0
  46. package/lib/net/response.js +0 -0
  47. package/lib/net.d.ts +0 -0
  48. package/lib/net.js +0 -0
  49. package/lib/ratelimit.d.ts +0 -0
  50. package/lib/ratelimit.js +0 -0
  51. package/lib/s3.d.ts +0 -0
  52. package/lib/s3.js +0 -0
  53. package/lib/scan.d.ts +0 -0
  54. package/lib/scan.js +0 -0
  55. package/lib/session.d.ts +0 -0
  56. package/lib/session.js +6 -1
  57. package/lib/socket.d.ts +0 -0
  58. package/lib/socket.js +0 -0
  59. package/lib/sql.d.ts +0 -0
  60. package/lib/sql.js +0 -0
  61. package/lib/ssh/sftp.d.ts +0 -0
  62. package/lib/ssh/sftp.js +0 -0
  63. package/lib/ssh/shell.d.ts +0 -0
  64. package/lib/ssh/shell.js +0 -0
  65. package/lib/ssh.d.ts +0 -0
  66. package/lib/ssh.js +0 -0
  67. package/lib/text/tld.json +0 -0
  68. package/lib/text.d.ts +0 -0
  69. package/lib/text.js +0 -0
  70. package/lib/time.d.ts +0 -0
  71. package/lib/time.js +0 -0
  72. package/lib/turnstile.d.ts +0 -0
  73. package/lib/turnstile.js +0 -0
  74. package/lib/vector.d.ts +0 -0
  75. package/lib/vector.js +0 -0
  76. package/lib/ws.d.ts +0 -0
  77. package/lib/ws.js +0 -0
  78. package/lib/zip.d.ts +0 -0
  79. package/lib/zip.js +0 -0
  80. package/lib/zlib.d.ts +0 -0
  81. package/lib/zlib.js +0 -0
  82. package/main.d.ts +0 -0
  83. package/main.js +0 -0
  84. package/package.json +9 -2
  85. package/sys/child.d.ts +0 -0
  86. package/sys/child.js +0 -0
  87. package/sys/cmd.d.ts +0 -0
  88. package/sys/cmd.js +69 -42
  89. package/sys/ctr.d.ts +16 -4
  90. package/sys/ctr.js +108 -45
  91. package/sys/master.d.ts +0 -0
  92. package/sys/master.js +0 -0
  93. package/sys/mod.d.ts +0 -0
  94. package/sys/mod.js +0 -0
  95. package/sys/monitor/watchdog.d.ts +0 -0
  96. package/sys/monitor/watchdog.js +0 -0
  97. package/sys/monitor.d.ts +0 -0
  98. package/sys/monitor.js +1 -1
  99. package/sys/route.d.ts +0 -0
  100. package/sys/route.js +31 -2
  101. package/www/example/ctr/main.d.ts +0 -0
  102. package/www/example/ctr/main.js +0 -0
  103. package/www/example/ctr/middle.d.ts +0 -0
  104. package/www/example/ctr/middle.js +0 -0
  105. package/www/example/ctr/test.d.ts +4 -0
  106. package/www/example/ctr/test.js +12 -3
  107. package/www/example/data/locale/en.test.json +0 -0
  108. package/www/example/data/locale/index.html +0 -0
  109. package/www/example/data/locale/ja.test.json +0 -0
  110. package/www/example/data/locale/sc.test.json +0 -0
  111. package/www/example/data/locale/tc.test.json +0 -0
  112. package/www/example/data/test.zip +0 -0
  113. package/www/example/kebab.json +0 -0
  114. package/www/example/mod/test.d.ts +0 -0
  115. package/www/example/mod/test.js +0 -0
  116. package/www/example/mod/testdata.d.ts +0 -0
  117. package/www/example/mod/testdata.js +0 -0
  118. package/www/example/route.json +0 -0
  119. package/www/example/stc/chunk-YJ3GYATF.js +81 -0
  120. package/www/example/stc/lib/ui/checkbox.d.ts +9 -0
  121. package/www/example/stc/lib/ui/checkbox.js +11 -0
  122. package/www/example/stc/lib/ui/checkbox.tsx +30 -0
  123. package/www/example/stc/lib/ui/input.d.ts +3 -0
  124. package/www/example/stc/lib/ui/input.js +10 -0
  125. package/www/example/stc/lib/ui/input.tsx +24 -0
  126. package/www/example/stc/lib/ui/label.d.ts +11 -0
  127. package/www/example/stc/lib/ui/label.js +13 -0
  128. package/www/example/stc/lib/ui/label.tsx +24 -0
  129. package/www/example/stc/lib/ui/switch.d.ts +9 -0
  130. package/www/example/stc/lib/ui/switch.js +11 -0
  131. package/www/example/stc/lib/ui/switch.tsx +31 -0
  132. package/www/example/stc/lib/utils.d.ts +7 -0
  133. package/www/example/stc/lib/utils.js +10 -0
  134. package/www/example/stc/view/hello.page.bundle.js +1 -0
  135. package/www/example/stc/view/hello.page.css +2 -0
  136. package/www/example/stc/view/hello.page.d.ts +17 -0
  137. package/www/example/stc/view/hello.page.js +15 -0
  138. package/www/example/stc/view/hello.page.tsx +49 -0
  139. package/www/example/stc/view/react-router.page.bundle.js +1 -0
  140. package/www/example/stc/view/react-router.page.css +2 -0
  141. package/www/example/stc/view/{react-router-page.d.ts → react-router.page.d.ts} +1 -1
  142. package/www/example/stc/view/{react-router-page.js → react-router.page.js} +1 -1
  143. package/www/example/stc/view/{react-router-page.tsx → react-router.page.tsx} +1 -1
  144. package/www/example/stc/view/react.page.bundle.js +26 -0
  145. package/www/example/stc/view/react.page.css +2 -0
  146. package/www/example/stc/view/{react-page.d.ts → react.page.d.ts} +16 -18
  147. package/www/example/stc/view/react.page.js +181 -0
  148. package/www/example/stc/view/{react-page.tsx → react.page.tsx} +259 -111
  149. package/www/example/view/test.ejs +0 -0
  150. package/www/example/ws/handler.d.ts +0 -0
  151. package/www/example/ws/handler.js +0 -0
  152. package/www/example/ws/main.d.ts +0 -0
  153. package/www/example/ws/main.js +0 -0
  154. package/www/example/ws/mproxy.d.ts +0 -0
  155. package/www/example/ws/mproxy.js +0 -0
  156. package/www/example/ws/rproxy.d.ts +0 -0
  157. package/www/example/ws/rproxy.js +0 -0
  158. package/www/example/ws/rsocket.d.ts +0 -0
  159. package/www/example/ws/rsocket.js +0 -0
  160. package/www/example/ws/test.d.ts +0 -0
  161. package/www/example/ws/test.js +0 -0
  162. package/www/example/stc/view/react-page.bundle.js +0 -97
  163. package/www/example/stc/view/react-page.css +0 -2
  164. package/www/example/stc/view/react-page.js +0 -136
  165. package/www/example/stc/view/react-router-page.bundle.js +0 -81
  166. package/www/example/stc/view/react-router-page.css +0 -2
package/sys/ctr.js CHANGED
@@ -78,12 +78,12 @@ export class Ctr {
78
78
  }
79
79
  /** --- timeout 的 timer --- */
80
80
  _timer;
81
- /** --- 获取当前过期时间 --- */
81
+ /** --- 获取当前过期时间(毫秒) --- */
82
82
  get timeout() {
83
83
  return this._timer?.timeout ?? 30_000;
84
84
  }
85
85
  /**
86
- * --- 设置当前过期时间 ---
86
+ * --- 设置当前过期时间(毫秒) ---
87
87
  */
88
88
  set timeout(num) {
89
89
  if (!this._timer) {
@@ -249,8 +249,9 @@ export class Ctr {
249
249
  * @param opt 可选配置
250
250
  */
251
251
  async _loadReactPage(path, props = {}, opt = {}) {
252
+ // --- 约定:传入路径不含 .page 后缀,框架自动补全(对应 build 命令的 *.page.tsx 约定)---
252
253
  // --- 组件 JS 从 stc 目录读取,浏览器同样通过 staticPath(支持 CDN)下载 ---
253
- const componentPath = this._config.const.rootPath + 'stc/' + path + '.js';
254
+ const componentPath = this._config.const.rootPath + 'stc/' + path + '.page.js';
254
255
  if (!await lFs.isFile(componentPath)) {
255
256
  return '';
256
257
  }
@@ -280,56 +281,67 @@ export class Ctr {
280
281
  '_locale': this._locale,
281
282
  '_localeData': localeData,
282
283
  };
284
+ // --- 框架自动注入的 HTML 片段,用户组件无需手动渲染 ---
285
+ let headInject = '';
286
+ let bodyInject = '';
283
287
  if (opt.hydrate !== false) {
284
288
  const reactVer = opt.reactVer ?? '19';
285
289
  const esm = 'https://esm.sh/';
286
290
  // --- 检查是否有 npx kebab build 生成的自包含预构建包 ---
287
- const bundlePath = this._config.const.rootPath + 'stc/' + path + '.bundle.js';
291
+ const bundlePath = this._config.const.rootPath + 'stc/' + path + '.page.bundle.js';
288
292
  const hasBundle = await lFs.isFile(bundlePath);
289
293
  if (opt.router === 'browser') {
290
- // --- BrowserRouter 模式:_routerBase 注入 props,bundle 读取此值决定是否包裹 BrowserRouter ---
294
+ // --- BrowserRouter 模式:_routerBase 注入 props,供水合脚本读取 ---
291
295
  const base = opt.routerBase ?? '';
292
296
  const routerBase = this._config.const.urlBase + base.replace(/^\//, '');
293
297
  fullProps['_routerBase'] = routerBase.replace(/\/$/, '');
294
298
  }
299
+ // --- propsJson 在渲染前序列化,框架直接注入 HTML,组件无需手动渲染 ---
300
+ const propsJson = lText.stringifyJson(fullProps).replace(/<\/script>/gi, '<\\/script>');
301
+ let hydrateScript;
295
302
  if (hasBundle) {
296
- // --- bundle 模式:bundle 自包含 React + 水合逻辑,无需 import map,一个 JS 文件搞定 ---
297
- const clientUrl = `${staticPath}${path}.bundle.js?v=${this._config.set.staticVer}`;
298
- fullProps['_hydrateScript'] = `import'${clientUrl}';`;
303
+ // --- bundle 模式:bundle 自包含 React + 水合逻辑,无需 import map ---
304
+ const clientUrl = `${staticPath}${path}.page.bundle.js?v=${this._config.set.staticVer}`;
305
+ hydrateScript = `import'${clientUrl}';`;
299
306
  }
300
307
  else {
301
308
  // --- 开发模式(tsc 编译 .js):通过 esm.sh import map 解析 bare import ---
302
- const clientUrl = `${staticPath}${path}.js?v=${this._config.set.staticVer}`;
303
- fullProps['_importMapJson'] = lText.stringifyJson({
304
- 'imports': {
305
- 'react': `${esm}react@${reactVer}`,
306
- 'react-dom': `${esm}react-dom@${reactVer}`,
307
- 'react-dom/client': `${esm}react-dom@${reactVer}/client`,
308
- 'react/jsx-runtime': `${esm}react@${reactVer}/jsx-runtime`,
309
- 'react-router-dom': `${esm}react-router-dom@7?external=react,react-dom`,
310
- },
311
- });
312
- if (opt.router === 'browser') {
313
- fullProps['_hydrateScript'] =
314
- `import{hydrateRoot}from'react-dom/client';` +
315
- `import{createElement}from'react';` +
316
- `import{BrowserRouter}from'react-router-dom';` +
317
- `import App from'${clientUrl}';` +
318
- `const p=JSON.parse(document.getElementById('__kebab_props__').textContent);` +
319
- `hydrateRoot(document,createElement(BrowserRouter,{basename:p._routerBase},createElement(App,p)));`;
320
- }
321
- else {
322
- fullProps['_hydrateScript'] =
323
- `import{hydrateRoot}from'react-dom/client';` +
324
- `import{createElement}from'react';` +
325
- `import App from'${clientUrl}';` +
326
- `const p=JSON.parse(document.getElementById('__kebab_props__').textContent);` +
327
- `hydrateRoot(document,createElement(App,p));`;
309
+ const clientUrl = `${staticPath}${path}.page.js?v=${this._config.set.staticVer}`;
310
+ // --- 内置 import map 条目(React 生态核心包)---
311
+ const builtinImports = {
312
+ 'react': `${esm}react@${reactVer}`,
313
+ 'react-dom': `${esm}react-dom@${reactVer}`,
314
+ 'react-dom/client': `${esm}react-dom@${reactVer}/client`,
315
+ 'react/jsx-runtime': `${esm}react@${reactVer}/jsx-runtime`,
316
+ 'react-router-dom': `${esm}react-router-dom@7?external=react,react-dom`,
317
+ };
318
+ // --- 自动扫描入口 JS 及其相对引用,收集所有第三方 bare specifier ---
319
+ const scannedFiles = new Set();
320
+ const extraImports = new Set();
321
+ await this._scanImports(componentPath, scannedFiles, extraImports, builtinImports);
322
+ // --- 第三方包统一通过 esm.sh 解析,external react/react-dom 避免重复加载 ---
323
+ for (const pkg of extraImports) {
324
+ builtinImports[pkg] = `${esm}${pkg}?external=react,react-dom`;
328
325
  }
326
+ // --- import map 注入到 </head> 前 ---
327
+ headInject = `<script type="importmap">${lText.stringifyJson({ 'imports': builtinImports })}</script>`;
328
+ // --- BrowserRouter 模式多一段 Router 导入与包裹层 ---
329
+ const routerImport = opt.router === 'browser' ? `import{BrowserRouter}from'react-router-dom';` : '';
330
+ const routerCreate = opt.router === 'browser'
331
+ ? `createElement(BrowserRouter,{basename:p._routerBase},createElement(App,p))`
332
+ : `createElement(App,p)`;
333
+ hydrateScript =
334
+ `import{hydrateRoot}from'react-dom/client';` +
335
+ `import{createElement}from'react';` +
336
+ routerImport +
337
+ `import App from'${clientUrl}';` +
338
+ `const p=JSON.parse(document.getElementById('__kebab_props__').textContent);` +
339
+ `hydrateRoot(document,${routerCreate});`;
329
340
  }
330
- // --- _propsJson 序列化当前 fullProps(不含 _propsJson 本身,避免循环引用)---
331
- // --- 客户端水合时读取此 JSON,_propsJson 缺失,suppressHydrationWarning 处理差异 ---
332
- fullProps['_propsJson'] = lText.stringifyJson(fullProps).replace(/<\/script>/gi, '<\\/script>');
341
+ // --- props JSON + 水合脚本注入到 </body> 前 ---
342
+ bodyInject =
343
+ `<script id="__kebab_props__" type="application/json">${propsJson}</script>` +
344
+ `<script type="module">${hydrateScript}</script>`;
333
345
  }
334
346
  // --- BrowserRouter 模式:服务端用 StaticRouter 渲染,与客户端的 BrowserRouter 等价 ---
335
347
  // --- component 来自动态 import,TypeScript 无法精确推断,需要明确限定 element 类型 ---
@@ -343,13 +355,51 @@ export class Ctr {
343
355
  'basename': fullProps['_routerBase'],
344
356
  }, react.createElement(component, fullProps));
345
357
  }
346
- return '<!DOCTYPE html>' + reactDomServer.renderToString(element);
358
+ // --- 框架将 import map 注入 </head> 前,props JSON + 水合脚本注入 </body> 前 ---
359
+ let html = '<!DOCTYPE html>' + reactDomServer.renderToString(element);
360
+ if (opt.hydrate !== false) {
361
+ html = html.replace('</head>', headInject + '</head>');
362
+ html = html.replace('</body>', bodyInject + '</body>');
363
+ }
364
+ return html;
347
365
  }
348
366
  catch (e) {
349
367
  lCore.debug(`[CTR][_loadReactPage] ${e.message ?? ''}`);
350
368
  return '';
351
369
  }
352
370
  }
371
+ /**
372
+ * --- 递归扫描 JS 文件中的 import 语句,收集第三方 bare specifier ---
373
+ * @param filePath 当前要扫描的文件绝对路径
374
+ * @param scannedFiles 已扫描文件集(去重用)
375
+ * @param extraImports 收集到的第三方包名集合
376
+ * @param builtinImports 内置 import map,已有条目不重复添加
377
+ */
378
+ async _scanImports(filePath, scannedFiles, extraImports, builtinImports) {
379
+ if (scannedFiles.has(filePath)) {
380
+ return;
381
+ }
382
+ scannedFiles.add(filePath);
383
+ const src = await lFs.getContent(filePath, 'utf8');
384
+ if (!src) {
385
+ return;
386
+ }
387
+ const re = /\bfrom\s*['"]([^'"]+)['"]/g;
388
+ let m;
389
+ while ((m = re.exec(src)) !== null) {
390
+ const spec = m[1];
391
+ if (spec.startsWith('./') || spec.startsWith('../')) {
392
+ // --- 相对引用:解析为绝对路径后递归扫描 ---
393
+ const dir = filePath.substring(0, filePath.lastIndexOf('/') + 1);
394
+ const resolved = new URL(spec, 'file://' + dir).pathname;
395
+ await this._scanImports(resolved, scannedFiles, extraImports, builtinImports);
396
+ }
397
+ else if (!spec.startsWith('/') && !spec.startsWith('http') && !(spec in builtinImports)) {
398
+ // --- 第三方 bare specifier:加入 import map ---
399
+ extraImports.add(spec);
400
+ }
401
+ }
402
+ }
353
403
  /**
354
404
  * --- 设置校验错误返回值 ---
355
405
  * @param rtn 返回值数组
@@ -592,10 +642,10 @@ export class Ctr {
592
642
  }
593
643
  return 'unknown';
594
644
  }
595
- /** --- auth 对象,user, pwd --- */
645
+ /** --- auth 对象 --- */
596
646
  _authorization = null;
597
647
  /**
598
- * --- 通过 header 或 _auth 获取 Basic Auth 鉴权信息 ---
648
+ * --- 通过 header 或 _auth 获取鉴权信息,支持 Basic Auth Bearer Token ---
599
649
  */
600
650
  getAuthorization() {
601
651
  if (this._authorization !== null) {
@@ -614,15 +664,28 @@ export class Ctr {
614
664
  if (typeof auth !== 'string') {
615
665
  return false;
616
666
  }
617
- let authArr = auth.split(' ');
618
- if (authArr[1] === undefined) {
667
+ const spaceIdx = auth.indexOf(' ');
668
+ if (spaceIdx === -1) {
619
669
  return false;
620
670
  }
621
- if (!(auth = lCrypto.base64Decode(authArr[1]))) {
671
+ const scheme = auth.slice(0, spaceIdx).toLowerCase();
672
+ const credential = auth.slice(spaceIdx + 1).trim();
673
+ if (!credential) {
674
+ return false;
675
+ }
676
+ if (scheme === 'bearer') {
677
+ this._authorization = { 'type': 'bearer', 'token': credential };
678
+ return this._authorization;
679
+ }
680
+ // --- Basic Auth: base64(user:pwd) ---
681
+ const decoded = lCrypto.base64Decode(credential);
682
+ if (!decoded) {
622
683
  return false;
623
684
  }
624
- authArr = auth.split(':');
625
- this._authorization = { 'user': authArr[0], 'pwd': authArr[1] ?? '' };
685
+ const colonIdx = decoded.indexOf(':');
686
+ const user = colonIdx === -1 ? decoded : decoded.slice(0, colonIdx);
687
+ const pwd = colonIdx === -1 ? '' : decoded.slice(colonIdx + 1);
688
+ this._authorization = { 'type': 'basic', 'user': user, 'pwd': pwd };
626
689
  return this._authorization;
627
690
  }
628
691
  /**
package/sys/master.d.ts CHANGED
File without changes
package/sys/master.js CHANGED
File without changes
package/sys/mod.d.ts CHANGED
File without changes
package/sys/mod.js CHANGED
File without changes
File without changes
File without changes
package/sys/monitor.d.ts CHANGED
File without changes
package/sys/monitor.js CHANGED
@@ -411,7 +411,7 @@ function logSpike(alerts, cpuPercent, cpuOs, eloopLag, blocked = false) {
411
411
  */
412
412
  function getOsCpuPercent() {
413
413
  const cpus = os.cpus();
414
- if (!lastOsCpus || lastOsCpus.length !== cpus.length) {
414
+ if (lastOsCpus?.length !== cpus.length) {
415
415
  lastOsCpus = cpus;
416
416
  return 0;
417
417
  }
package/sys/route.d.ts CHANGED
File without changes
package/sys/route.js CHANGED
@@ -227,8 +227,18 @@ export async function run(data) {
227
227
  if (data.req.headers['cookie']) {
228
228
  const hcookies = data.req.headers['cookie'].split(';');
229
229
  for (const cookie of hcookies) {
230
- const co = cookie.split('=');
231
- cookies[co[0].trim()] = decodeURIComponent(co[1]);
230
+ const eqIndex = cookie.indexOf('=');
231
+ if (eqIndex === -1) {
232
+ continue;
233
+ }
234
+ const key = cookie.slice(0, eqIndex).trim();
235
+ const rawVal = cookie.slice(eqIndex + 1);
236
+ try {
237
+ cookies[key] = decodeURIComponent(rawVal);
238
+ }
239
+ catch {
240
+ cookies[key] = rawVal;
241
+ }
232
242
  }
233
243
  }
234
244
  // --- 处理 headers ---
@@ -818,11 +828,30 @@ export function getPost(req) {
818
828
  return;
819
829
  }
820
830
  // --- json 或普通 post ---
831
+ /** --- POST body 最大允许 50 MB,防止攻击者发送超大请求体耗尽内存 --- */
832
+ const maxPostSize = 50 * 1024 * 1024;
821
833
  let buffer = Buffer.from('');
834
+ let overflow = false;
822
835
  req.on('data', function (chunk) {
836
+ if (overflow) {
837
+ return;
838
+ }
839
+ if (buffer.length + chunk.length > maxPostSize) {
840
+ overflow = true;
841
+ buffer = Buffer.from('');
842
+ return;
843
+ }
823
844
  buffer = Buffer.concat([buffer, chunk], buffer.length + chunk.length);
824
845
  });
825
846
  req.on('end', function () {
847
+ if (overflow) {
848
+ resolve({
849
+ 'input': '',
850
+ 'raw': {},
851
+ 'post': {},
852
+ });
853
+ return;
854
+ }
826
855
  const s = buffer.toString();
827
856
  if (!s) {
828
857
  resolve({
File without changes
File without changes
File without changes
File without changes
@@ -127,6 +127,10 @@ export default class extends sCtr.Ctr {
127
127
  * --- Rate Limit 限流测试 ---
128
128
  */
129
129
  ratelimit(): Promise<string>;
130
+ /**
131
+ * --- React 最简页面示例:展示一个 React 页面最少需要写哪些内容 ---
132
+ */
133
+ reactHelloPage(): Promise<string>;
130
134
  /**
131
135
  * --- React 全页模式测试:组件自主渲染完整 HTML 文档 + 客户端水合,无需 EJS ---
132
136
  */
@@ -98,6 +98,7 @@ export default class extends sCtr.Ctr {
98
98
  `<br><a href="${this._config.const.urlBase}test/react-page">View "test/react-page" (SSR)</a>`,
99
99
  `<br><a href="${this._config.const.urlBase}test/react-router-page">View "test/react-router-page" (SSR + BrowserRouter)</a>`,
100
100
  `<br><a href="${this._config.const.urlBase}test/react-router-page-data?path=/user">View "test/react-router-page-data?path=/user" (Data API)</a>`,
101
+ `<br><a href="${this._config.const.urlBase}test/react-hello-page">View "test/react-hello-page" (React Hello Page)</a>`,
101
102
  '<br><br><b>Return json:</b>',
102
103
  `<br><br><a href="${this._config.const.urlBase}test/json?type=1">View "test/json?type=1"</a>`,
103
104
  `<br><a href="${this._config.const.urlBase}test/json?type=2">View "test/json?type=2"</a>`,
@@ -161,7 +162,7 @@ export default class extends sCtr.Ctr {
161
162
  `<br><br><a href="${this._config.const.urlBase}test/crypto">View "test/crypto"</a>`,
162
163
  '<br><br><b>Db:</b>',
163
164
  `<br><br><a href="${this._config.const.urlBase}test/db">View "test/db"</a> <a href="${this._config.const.urlBase}test/db?s=pgsql">pgsql</a>`,
164
- `<br><br><a href="${this._config.const.urlBase}test/db-read">View "test/db-read"</a>`,
165
+ `<br><a href="${this._config.const.urlBase}test/db-read">View "test/db-read"</a>`,
165
166
  `<br><br><b>Vector:</b>`,
166
167
  `<br><br><a href="${this._config.const.urlBase}test/vector">View "test/vector"</a>`,
167
168
  '<br><br><b>Kv:</b>',
@@ -3907,12 +3908,20 @@ send.addEventListener('click', async () => {
3907
3908
  echo.push(`Result: ${JSON.stringify(rtn2)}`);
3908
3909
  return echo.join('') + '<br><br>' + this._getEnd();
3909
3910
  }
3911
+ /**
3912
+ * --- React 最简页面示例:展示一个 React 页面最少需要写哪些内容 ---
3913
+ */
3914
+ async reactHelloPage() {
3915
+ return this._loadReactPage('view/hello', {
3916
+ 'greeting': 'Hello, Kebab React!',
3917
+ });
3918
+ }
3910
3919
  /**
3911
3920
  * --- React 全页模式测试:组件自主渲染完整 HTML 文档 + 客户端水合,无需 EJS ---
3912
3921
  */
3913
3922
  async reactPage() {
3914
3923
  // --- Ctr 方法负责数据准备(可以查数据库、调接口等),组件不包含任何服务端专属代码 ---
3915
- return this._loadReactPage('view/react-page', {
3924
+ return this._loadReactPage('view/react', {
3916
3925
  'title': 'Kebab React Full Page',
3917
3926
  'serverTime': lTime.format(this, 'Y-m-d H:i:s'),
3918
3927
  'node': process.version,
@@ -3956,7 +3965,7 @@ send.addEventListener('click', async () => {
3956
3965
  ? (reqUrl.substring(basePrefix.length) || '/')
3957
3966
  : '/';
3958
3967
  const routeData = this._getRouteData(routePath);
3959
- return this._loadReactPage('view/react-router-page', {
3968
+ return this._loadReactPage('view/react-router', {
3960
3969
  'title': 'Kebab React Router',
3961
3970
  'serverTime': lTime.format(this, 'Y-m-d H:i:s'),
3962
3971
  'node': process.version,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes