@zhin.js/core 1.0.1 → 1.0.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/lib/app.d.ts +10 -2
  3. package/lib/app.d.ts.map +1 -1
  4. package/lib/app.js +27 -2
  5. package/lib/app.js.map +1 -1
  6. package/lib/bot.d.ts +4 -2
  7. package/lib/bot.d.ts.map +1 -1
  8. package/lib/command.d.ts +2 -1
  9. package/lib/command.d.ts.map +1 -1
  10. package/lib/command.js.map +1 -1
  11. package/lib/component.d.ts +22 -102
  12. package/lib/component.d.ts.map +1 -1
  13. package/lib/component.js +438 -242
  14. package/lib/component.js.map +1 -1
  15. package/lib/jsx-dev-runtime.d.ts +3 -0
  16. package/lib/jsx-dev-runtime.d.ts.map +1 -0
  17. package/lib/jsx-dev-runtime.js +3 -0
  18. package/lib/jsx-dev-runtime.js.map +1 -0
  19. package/lib/jsx-runtime.d.ts +12 -0
  20. package/lib/jsx-runtime.d.ts.map +1 -0
  21. package/lib/jsx-runtime.js +11 -0
  22. package/lib/jsx-runtime.js.map +1 -0
  23. package/lib/jsx.d.ts +32 -0
  24. package/lib/jsx.d.ts.map +1 -0
  25. package/lib/jsx.js +57 -0
  26. package/lib/jsx.js.map +1 -0
  27. package/lib/log-transport.d.ts +37 -0
  28. package/lib/log-transport.d.ts.map +1 -0
  29. package/lib/log-transport.js +136 -0
  30. package/lib/log-transport.js.map +1 -0
  31. package/lib/message.d.ts +10 -7
  32. package/lib/message.d.ts.map +1 -1
  33. package/lib/message.js.map +1 -1
  34. package/lib/models/system-log.d.ts +11 -0
  35. package/lib/models/system-log.d.ts.map +1 -0
  36. package/lib/models/system-log.js +9 -0
  37. package/lib/models/system-log.js.map +1 -0
  38. package/lib/plugin.d.ts +4 -3
  39. package/lib/plugin.d.ts.map +1 -1
  40. package/lib/plugin.js +12 -2
  41. package/lib/plugin.js.map +1 -1
  42. package/lib/prompt.d.ts.map +1 -1
  43. package/lib/prompt.js +3 -2
  44. package/lib/prompt.js.map +1 -1
  45. package/lib/types.d.ts +13 -15
  46. package/lib/types.d.ts.map +1 -1
  47. package/lib/utils.d.ts.map +1 -1
  48. package/lib/utils.js +3 -1
  49. package/lib/utils.js.map +1 -1
  50. package/package.json +16 -4
  51. package/src/app.ts +37 -4
  52. package/src/bot.ts +5 -3
  53. package/src/command.ts +2 -1
  54. package/src/component.ts +523 -280
  55. package/src/jsx-dev-runtime.ts +2 -0
  56. package/src/jsx-runtime.ts +12 -0
  57. package/src/jsx.d.ts +52 -0
  58. package/src/jsx.ts +92 -0
  59. package/src/log-transport.ts +163 -0
  60. package/src/message.ts +8 -5
  61. package/src/models/system-log.ts +20 -0
  62. package/src/plugin.ts +19 -5
  63. package/src/prompt.ts +3 -2
  64. package/src/types.ts +13 -13
  65. package/src/utils.ts +6 -5
  66. package/tests/component-new.test.ts +348 -0
  67. package/tests/expression-evaluation.test.ts +258 -0
  68. package/tests/plugin.test.ts +26 -17
  69. package/tests/component.test.ts +0 -656
package/src/component.ts CHANGED
@@ -1,318 +1,561 @@
1
- import {getValueWithRuntime, compiler, segment} from './utils.js';
2
- import {Dict, SendContent, SendOptions} from './types.js';
3
- import {Message} from "./message.js";
4
- import {MaybePromise} from "@zhin.js/types";
1
+ import { getValueWithRuntime, compiler, segment } from './utils.js';
2
+ import { Dict, SendContent, SendOptions, MessageElement } from './types.js';
3
+ import { Message } from "./message.js";
4
+
5
+ // 组件匹配符号
5
6
  export const CapWithChild = Symbol('CapWithChild');
6
7
  export const CapWithClose = Symbol('CapWithClose');
7
- /**
8
- * Component类:消息组件系统核心,支持模板渲染、属性解析、循环等。
9
- * 用于自定义消息结构和复用UI片段。
10
- * @template T 组件props类型
11
- * @template D 组件data类型
12
- * @template P 组件props配置类型
13
- */
14
- export class Component<T = {}, D = {}, P = Component.Props<T>> {
15
- [CapWithClose]: RegExp;
16
- [CapWithChild]: RegExp;
17
- $props: Component.PropConfig[] = [];
18
- get name(){
19
- return this.$options.name;
8
+
9
+ // 函数式组件类型定义
10
+ export type Component<P = any> = {
11
+ (props: P, context: ComponentContext): Promise<SendContent>;
12
+ name: string;
13
+ }
14
+
15
+
16
+ // 组件上下文接口 - 通过闭包严格控制可访问的信息
17
+ export interface ComponentContext {
18
+ // 基础渲染能力
19
+ render: (template: string, context?: Partial<ComponentContext>) => Promise<SendContent>;
20
+
21
+ // 数据访问(只读)
22
+ props: Readonly<Dict>;
23
+
24
+ // 父组件上下文(只读)
25
+ parent?: Readonly<ComponentContext>;
26
+
27
+ // 根模板(只读)
28
+ root: string;
29
+
30
+ // 子组件内容(React 概念)
31
+ children?: string;
32
+ getValue: (template: string) => any;
33
+ compile: (template: string) => string;
34
+ }
35
+
36
+ // 组件定义函数 - 简化版,只支持函数式组件
37
+ export function defineComponent<P = any>(
38
+ component: Component<P>,
39
+ name: string = component.name
40
+ ): Component<P> {
41
+ if (name) {
42
+ // 创建一个新的函数来避免修改只读属性
43
+ const namedComponent = component as Component<P>;
44
+ Object.defineProperty(namedComponent, 'name', {
45
+ value: name,
46
+ writable: false,
47
+ enumerable: false,
48
+ configurable: true
49
+ });
50
+ return namedComponent;
20
51
  }
21
- set name(value:string){
22
- this.$options.name=value;
52
+ return component;
53
+ }
54
+
55
+ // 组件匹配函数
56
+ export function matchComponent<P = any>(comp: Component<P>, template: string): string {
57
+ // 使用更复杂的正则表达式来正确处理大括号内的内容
58
+ const selfClosingRegex = new RegExp(`<${comp.name}((?:[^>]|{[^}]*})*)?/>`);
59
+ const closingRegex = new RegExp(`<${comp.name}((?:[^>]|{[^}]*})*)?>([^<]*?)</${comp.name}>`);
60
+
61
+ let match = template.match(selfClosingRegex);
62
+ if (!match) {
63
+ match = template.match(closingRegex);
23
64
  }
24
- /**
25
- * 构造函数:初始化组件,生成属性正则
26
- * @param $options 组件配置项
27
- */
28
- constructor(private $options: Component.Options<T, D, P>) {
29
- this.formatProps();
30
- this[CapWithChild] = new RegExp(`<${$options.name}([^>]*)?>([^<])*?</${$options.name}>`);
31
- this[CapWithClose] = new RegExp(`<${$options.name}([^>]*)?/>`);
65
+
66
+ return match ? match[0] : '';
67
+ }
68
+
69
+ // 属性解析函数 - 支持 children
70
+ export function getProps<P = any>(comp: Component<P>, template: string, context?: ComponentContext): P {
71
+ // 1. 首先匹配组件标签,支持自闭合和闭合标签
72
+ const selfClosingRegex = new RegExp(`<${comp.name}((?:[^>]|{[^}]*})*)?/>`);
73
+ const closingRegex = new RegExp(`<${comp.name}((?:[^>]|{[^}]*})*)?>([^<]*?)</${comp.name}>`);
74
+
75
+ let match = template.match(selfClosingRegex);
76
+ let isSelfClosing = true;
77
+ let children = '';
78
+
79
+ if (!match) {
80
+ match = template.match(closingRegex);
81
+ isSelfClosing = false;
82
+ if (match) {
83
+ children = match[2] || '';
84
+ }
85
+ }
86
+
87
+ if (!match) {
88
+ return {} as P;
89
+ }
90
+
91
+ const attributesString = match[1] || '';
92
+
93
+ // 2. 解析属性,支持多种格式
94
+ const props: Record<string, any> = {};
95
+
96
+ // 如果有属性字符串,解析属性
97
+ if (attributesString.trim()) {
98
+ // 使用手动解析来处理复杂的嵌套结构
99
+ let i = 0;
100
+ while (i < attributesString.length) {
101
+ // 跳过空白字符
102
+ while (i < attributesString.length && /\s/.test(attributesString[i])) {
103
+ i++;
104
+ }
105
+
106
+ if (i >= attributesString.length) break;
107
+
108
+ // 解析属性名
109
+ let key = '';
110
+ while (i < attributesString.length && /[a-zA-Z0-9_$\-]/.test(attributesString[i])) {
111
+ key += attributesString[i];
112
+ i++;
113
+ }
114
+
115
+ if (!key) {
116
+ i++;
117
+ continue;
118
+ }
119
+
120
+ // 跳过空白字符
121
+ while (i < attributesString.length && /\s/.test(attributesString[i])) {
122
+ i++;
123
+ }
124
+
125
+ // 检查是否有等号
126
+ if (i < attributesString.length && attributesString[i] === '=') {
127
+ i++; // 跳过等号
128
+
129
+ // 跳过空白字符
130
+ while (i < attributesString.length && /\s/.test(attributesString[i])) {
131
+ i++;
132
+ }
133
+
134
+ if (i >= attributesString.length) {
135
+ props[key] = true;
136
+ break;
137
+ }
138
+
139
+ // 解析属性值
140
+ const value = parseAttributeValue(attributesString, i, context);
141
+ props[key] = value.value;
142
+ i = value.nextIndex;
143
+ } else {
144
+ // 没有等号,是布尔属性
145
+ props[key] = true;
146
+ }
147
+ }
32
148
  }
33
- /**
34
- * 判断模板是否为自闭合标签
35
- * @param template 模板字符串
36
- */
37
- isClosing(template: string) {
38
- return this[CapWithClose].test(template);
149
+
150
+ // 3. 处理 children(如果不是自闭合标签)
151
+ if (!isSelfClosing && children.trim()) {
152
+ props.children = children;
39
153
  }
40
- /**
41
- * 匹配组件标签
42
- * @param template 模板字符串
43
- * @returns 匹配到的标签内容
44
- */
45
- match(template: string) {
46
- let [match] = this[CapWithChild].exec(template) || [];
47
- if (match) return match;
48
- [match] = this[CapWithClose].exec(template) || [];
49
- return match;
154
+
155
+ // 4. 处理 kebab-case 到 camelCase 的转换
156
+ const camelCaseProps: Record<string, any> = {};
157
+ for (const [key, value] of Object.entries(props)) {
158
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
159
+ camelCaseProps[camelKey] = value;
50
160
  }
51
- /**
52
- * 格式化props配置,生成props数组
53
- */
54
- private formatProps() {
55
- for (const [key, value] of Object.entries(this.$options.props || {})) {
56
- this.formatProp(key, value as any);
161
+
162
+ return camelCaseProps as P;
163
+ }
164
+
165
+ /**
166
+ * 解析属性值
167
+ */
168
+ function parseAttributeValue(str: string, startIndex: number, context?: ComponentContext): { value: any; nextIndex: number } {
169
+ let i = startIndex;
170
+
171
+ // 处理引号包围的字符串
172
+ if (str[i] === '"' || str[i] === "'") {
173
+ const quote = str[i];
174
+ i++; // 跳过开始引号
175
+ let value = '';
176
+
177
+ while (i < str.length && str[i] !== quote) {
178
+ if (str[i] === '\\' && i + 1 < str.length) {
179
+ // 处理转义字符
180
+ i++;
181
+ value += str[i];
182
+ } else {
183
+ value += str[i];
184
+ }
185
+ i++;
186
+ }
187
+
188
+ if (i < str.length) {
189
+ i++; // 跳过结束引号
190
+ }
191
+
192
+ // 检查引号内是否包含表达式(大括号)
193
+ if (value.includes('{') && value.includes('}')) {
194
+ // 包含表达式,进行求值
195
+ const evaluatedValue = evaluateQuotedExpression(value, context);
196
+ return { value: evaluatedValue, nextIndex: i };
57
197
  }
198
+
199
+ return { value, nextIndex: i };
58
200
  }
59
201
 
60
- /**
61
- * 格式化单个prop配置
62
- * @param name 属性名
63
- * @param value 类型或配置
64
- */
65
- private formatProp(name: string, value: Exclude<Component.PropConfig, 'name'> | Component.TypeConstruct) {
66
- if (typeof value === 'function') {
67
- return this.$props.push({
68
- name,
69
- type: value,
70
- default: undefined,
71
- });
202
+ // 处理大括号包围的表达式
203
+ if (str[i] === '{') {
204
+ let braceCount = 0;
205
+ let value = '';
206
+ i++; // 跳过开始大括号
207
+
208
+ while (i < str.length) {
209
+ if (str[i] === '{') {
210
+ braceCount++;
211
+ } else if (str[i] === '}') {
212
+ if (braceCount === 0) {
213
+ i++; // 跳过结束大括号
214
+ break;
215
+ }
216
+ braceCount--;
217
+ }
218
+ value += str[i];
219
+ i++;
72
220
  }
73
- return this.$props.push({
74
- name,
75
- type: value.type,
76
- default: value.default,
77
- });
221
+
222
+ return { value: parseExpressionValue(value, context), nextIndex: i };
223
+ }
224
+
225
+ // 处理无引号的值
226
+ let value = '';
227
+ while (i < str.length && !/\s/.test(str[i]) && str[i] !== '>') {
228
+ value += str[i];
229
+ i++;
78
230
  }
79
231
 
80
- parseProps(template: string) {
81
- const result = Object.fromEntries(
82
- this.$props.map(prop => {
83
- const generateDefault = typeof prop.default === 'function' ? prop.default : () => prop.default;
84
- return [prop.name, generateDefault()];
85
- }),
86
- );
87
- const matchedArr = [...template.matchAll(/([a-zA-Z\-:]+)\s*=\s*(['"])(.*?)\2/g)].filter(Boolean);
88
- if (!matchedArr.length) return result;
89
- for (const [_, key, __, value] of matchedArr) {
90
- Object.defineProperty(result, key, {
91
- enumerable: true,
92
- writable: false,
93
- value,
94
- });
232
+ return { value: parseUnquotedValue(value, context), nextIndex: i };
233
+ }
234
+
235
+ /**
236
+ * 求值引号内的表达式
237
+ */
238
+ function evaluateQuotedExpression(quotedValue: string, context?: ComponentContext): string {
239
+ if (!context) return quotedValue;
240
+
241
+ // 处理引号内的表达式,将 {expr} 格式转换为 ${expr} 格式
242
+ let result = quotedValue;
243
+ const expressionRegex = /\{([^}]+)\}/g;
244
+ let match;
245
+
246
+ while ((match = expressionRegex.exec(quotedValue)) !== null) {
247
+ const expr = match[1];
248
+ try {
249
+ const value = context.getValue(expr);
250
+ if (value !== undefined && value !== expr) {
251
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
252
+ result = result.replace(match[0], stringValue);
253
+ }
254
+ } catch (error) {
255
+ // 如果求值失败,保持原始表达式
95
256
  }
96
- return result;
97
257
  }
98
- parseChildren(template: string): string {
99
- if (this.isClosing(template)) return '';
100
- const matched = template.match(/<[^>]+>([^<]*?)<\/[^?]+>/);
101
- if (!matched) return '';
102
- return matched[1];
258
+
259
+ return result;
260
+ }
261
+
262
+ /**
263
+ * 解析表达式值
264
+ */
265
+ function parseExpressionValue(expr: string, context?: ComponentContext): any {
266
+ expr = expr.trim();
267
+
268
+ // 处理字符串字面量
269
+ if ((expr.startsWith('"') && expr.endsWith('"')) ||
270
+ (expr.startsWith("'") && expr.endsWith("'"))) {
271
+ return expr.slice(1, -1);
272
+ }
273
+
274
+ // 处理数字
275
+ if (/^-?\d+(\.\d+)?$/.test(expr)) {
276
+ return parseFloat(expr);
277
+ }
278
+
279
+ // 处理布尔值
280
+ if (expr === 'true') return true;
281
+ if (expr === 'false') return false;
282
+ if (expr === 'null') return null;
283
+ if (expr === 'undefined') return undefined;
284
+
285
+ // 处理数组
286
+ if (expr.startsWith('[') && expr.endsWith(']')) {
287
+ try {
288
+ return JSON.parse(expr);
289
+ } catch {
290
+ // 如果JSON解析失败,尝试手动解析简单数组
291
+ const items = expr.slice(1, -1).split(',').map(item =>
292
+ parseExpressionValue(item.trim(), context)
293
+ );
294
+ return items;
295
+ }
103
296
  }
104
- async render(template: string, context: Component.Context): Promise<SendContent> {
105
- const props = this.parseProps(template);
106
- const assignValue = () => {
107
- for (const key of keys) {
108
- if (!key.startsWith(':')) continue;
109
- Object.defineProperty(props, key.slice(1), {
110
- value: getValueWithRuntime(Reflect.get(props, key), context.parent),
111
- });
112
- Reflect.deleteProperty(props, key);
297
+
298
+ // 处理对象 - 改进的嵌套大括号处理
299
+ if (expr.startsWith('{') && expr.endsWith('}')) {
300
+ try {
301
+ return JSON.parse(expr);
302
+ } catch {
303
+ // 如果JSON解析失败,尝试手动解析简单对象
304
+ try {
305
+ return parseSimpleObject(expr);
306
+ } catch {
307
+ // 如果都失败,返回原始字符串
308
+ return expr;
113
309
  }
114
- };
115
- const keys = Object.keys(props).map(key => {
116
- const newKey = key.replace(/(\w)+-(\w)/g, function (_, char, later) {
117
- return `${char}${later.toUpperCase()}`;
118
- });
119
- if (key !== newKey) {
120
- Object.defineProperty(props, newKey, {
121
- value: Reflect.get(props, key),
122
- enumerable: true,
123
- });
124
- Reflect.deleteProperty(props, key);
310
+ }
311
+ }
312
+
313
+ // 处理表达式 - 在沙盒中执行
314
+ if (context) {
315
+ try {
316
+ const result = context.getValue(expr);
317
+ // 如果结果是 undefined,说明表达式被阻止或求值失败,返回原始表达式
318
+ if (result === undefined) {
319
+ return expr;
125
320
  }
126
- return newKey;
127
- });
128
- assignValue();
129
- const data = this.$options.data ? this.$options.data.apply(props as P) : ({} as D);
130
- for (const key of keys) {
131
- if (key === 'vFor') {
132
- const { 'vFor': expression, 'v-for': _, ...rest } = props as any;
133
- const { name, value, ...other } = Component.fixLoop(expression);
134
- const list = value === '__loop__' ? other[value] : getValueWithRuntime(value, context);
135
- const fnStr = `
136
- const result=[];\n
137
- for(const ${name} of list){\n
138
- result.push(render(props,{\n
139
- ...context,\n
140
- children:'',\n
141
- $origin:'${template.replace(/'/g, "'")}',
142
- parent:{\n
143
- ...context.parent,\n
144
- ${name}:list[${name}]
145
- }\n
146
- }))\n
321
+ return result;
322
+ } catch (error) {
323
+ // 如果执行失败,返回原始表达式
324
+ return expr;
325
+ }
326
+ }
327
+
328
+ // 如果没有上下文,返回原始表达式
329
+ return expr;
330
+ }
331
+
332
+ /**
333
+ * 解析简单对象(处理嵌套大括号和方括号)
334
+ */
335
+ function parseSimpleObject(objStr: string): any {
336
+ const result: any = {};
337
+ let i = 1; // 跳过开始的 {
338
+ let depth = 0;
339
+ let bracketDepth = 0;
340
+ let key = '';
341
+ let value = '';
342
+ let inKey = true;
343
+ let inString = false;
344
+ let stringChar = '';
345
+
346
+ while (i < objStr.length - 1) { // 跳过结束的 }
347
+ const char = objStr[i];
348
+
349
+ if (!inString) {
350
+ if (char === '{') {
351
+ depth++;
352
+ value += char;
353
+ } else if (char === '}') {
354
+ depth--;
355
+ value += char;
356
+ } else if (char === '[') {
357
+ bracketDepth++;
358
+ value += char;
359
+ } else if (char === ']') {
360
+ bracketDepth--;
361
+ value += char;
362
+ } else if (char === ':' && depth === 0 && bracketDepth === 0) {
363
+ inKey = false;
364
+ i++;
365
+ continue;
366
+ } else if (char === ',' && depth === 0 && bracketDepth === 0) {
367
+ // 处理键值对
368
+ if (key.trim() && value.trim()) {
369
+ const parsedValue = parseExpressionValue(value.trim());
370
+ result[key.trim()] = parsedValue;
371
+ }
372
+ key = '';
373
+ value = '';
374
+ inKey = true;
375
+ i++;
376
+ continue;
377
+ } else if (char === '"' || char === "'") {
378
+ inString = true;
379
+ stringChar = char;
380
+ if (inKey) {
381
+ key += char;
382
+ } else {
383
+ value += char;
384
+ }
385
+ } else {
386
+ if (inKey) {
387
+ key += char;
388
+ } else {
389
+ value += char;
147
390
  }
148
- return result;`;
149
- const fn = new Function('render,list,props,context', fnStr);
150
- const newTpl = template
151
- .replace(`v-for="${expression}"`, '')
152
- .replace(`v-for='${expression}'`, '')
153
- .replace(`vFor="${expression}"`, '')
154
- .replace(`vFor='${expression}'`, '');
155
- return (await Promise.all(fn(this.render.bind(this), list, newTpl, context))).join('');
156
391
  }
157
- if (key === 'vIf') {
158
- const needRender = getValueWithRuntime(Reflect.get(props as object, 'vIf'), context);
159
- if (!needRender) return '';
392
+ } else {
393
+ if (char === stringChar) {
394
+ inString = false;
395
+ }
396
+ if (inKey) {
397
+ key += char;
398
+ } else {
399
+ value += char;
160
400
  }
161
401
  }
162
- context.children = this.parseChildren(template) || context.children;
163
- const ctx = {
164
- $slots: context.$slots || {},
165
- ...props,
166
- ...data,
167
- $message: context.$message,
168
- render: context.render,
169
- parent: context,
170
- children: context.children,
171
- } as Component.Context<D & P>;
172
- const result = segment.toString(await this.$options.render(props as P, ctx));
173
- context.$root = context.$root.replace(context.$origin || template,result.includes('<')?segment.escape(result):result);
174
- return context.render(context.$root, context);
402
+ i++;
175
403
  }
176
- }
177
- export function defineComponent<P>(render: Component.Render<P, {}>, name?: string): Component<{}, {}, P>;
178
- export function defineComponent<T, D = {}, P = Component.Props<T>>(options: Component.Options<T, D>): Component<T, D, P>;
179
- export function defineComponent<T = {}, D = {}, P = Component.Props<T>>(options: Component.Options<T, D, P> | Component.Render<P, D>, name = options.name) {
180
- if (typeof options === 'function')
181
- options = {
182
- name,
183
- render: options,
184
- };
185
- return new Component(options);
404
+
405
+ // 处理最后一个键值对
406
+ if (key.trim() && value.trim()) {
407
+ const parsedValue = parseExpressionValue(value.trim());
408
+ result[key.trim()] = parsedValue;
409
+ }
410
+
411
+ return result;
186
412
  }
187
413
 
188
- export namespace Component {
189
- export type TypeConstruct<T = any> = {
190
- new (): T;
191
- readonly prototype: T;
192
- };
193
- export type PropConfig<T extends TypeConstruct = TypeConstruct> = {
194
- name: string;
195
- type: T;
196
- default: Prop<T>;
197
- };
414
+ /**
415
+ * 解析无引号的值
416
+ */
417
+ function parseUnquotedValue(value: string, context?: ComponentContext): any {
418
+ // 检查是否是大括号表达式
419
+ if (value.startsWith('{') && value.endsWith('}')) {
420
+ const expr = value.slice(1, -1); // 移除大括号
421
+ return parseExpressionValue(expr, context);
422
+ }
423
+
424
+ // 处理布尔值
425
+ if (value === 'true') return true;
426
+ if (value === 'false') return false;
198
427
 
199
- export interface Options<T, D, P = Props<T>> {
200
- name: string;
201
- props?: T;
202
- data?: (this: P) => D;
203
- render: Render<P, D>;
428
+ // 处理数字
429
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
430
+ return parseFloat(value);
204
431
  }
205
432
 
206
- export type Context<T = {}> = {
207
- $slots: Dict<Render<any, any>>;
208
- $message: Message;
209
- $origin?: string;
210
- $root: string;
211
- parent: Context;
212
- render(template: string, context: Context): Promise<SendContent>;
213
- children?: string;
214
- } & T;
215
- export type Render<P = {}, D = {}> = (props: P, context: Context<P & D>) => MaybePromise<SendContent>;
216
- export type Props<T> = {
217
- [P in keyof T]: Prop<T[P]>;
218
- };
219
- export type PropWithDefault<T> = {
220
- type: T;
221
- default?: DefaultValue<T>;
222
- };
223
- type DefaultValue<T> = T extends ObjectConstructor | ArrayConstructor ? () => Prop<T> : Prop<T>;
224
- export type Prop<T> = T extends BooleanConstructor
225
- ? boolean
226
- : T extends StringConstructor
227
- ? string
228
- : T extends NumberConstructor
229
- ? number
230
- : T extends ArrayConstructor
231
- ? any[]
232
- : T extends ObjectConstructor
233
- ? Dict
234
- : T extends PropWithDefault<infer R>
235
- ? Prop<R>
236
- : unknown;
237
- export const fixLoop = (loop: string) => {
238
- let [_, name, value] = /(\S+)\sin\s(\S+)/.exec(loop) || [];
239
- if (/\d+/.test(value))
240
- value = `[${new Array(+value)
241
- .fill(0)
242
- .map((_, i) => i)
243
- .join(',')}]`;
244
- if (/^\[.+]$/.test(value)) {
245
- return { name, value: '__loop__', __loop__: JSON.parse(value) };
246
- }
247
- return { name, value };
248
- };
433
+ // 处理 null/undefined
434
+ if (value === 'null') return null;
435
+ if (value === 'undefined') return undefined;
249
436
 
250
- export async function render(componentMap:Map<string,Component>,options:SendOptions):Promise<SendOptions>{
251
- if(!componentMap.size) return options;
252
- const components=[
253
- ...componentMap.values(),
254
- Template,
255
- Slot,
256
- ]
257
- const createContext = (runtime: Dict = {}, parent: Component.Context, $root: string): Component.Context => {
437
+ // 其他情况作为字符串处理
438
+ return value;
439
+ }
440
+
441
+ // 创建组件上下文的工厂函数
442
+ export function createComponentContext(
443
+ props: Dict = {},
444
+ parent?: ComponentContext,
445
+ root: string = ''
446
+ ): ComponentContext {
258
447
  return {
259
- $slots: {},
260
- ...runtime,
261
- $message: parent.$message,
262
- $root,
263
- parent,
264
- render: (template: string, context) => {
265
- return renderWithRuntime(template, context, context.$root);
266
- },
267
- };
268
- };
269
- const renderWithRuntime = async (template: string, runtime: Dict, $root: string):Promise<SendContent> => {
270
- const ctx = createContext(runtime, runtime as Component.Context, $root);
271
- template = compiler(template, runtime);
448
+ // 基础渲染能力
449
+ render: async (template: string, context?: Partial<ComponentContext>) => {
450
+ // 这里需要实现渲染逻辑
451
+ return template;
452
+ },
453
+
454
+ // 数据访问(只读)
455
+ props: Object.freeze({ ...props }),
456
+
457
+ // 父组件上下文(只读)
458
+ parent: parent ? Object.freeze(parent) : undefined,
459
+
460
+ // 根模板(只读)
461
+ root,
462
+
463
+
464
+ // 子组件内容(React 概念)
465
+ children: undefined,
466
+ getValue: (template: string) => getValueWithRuntime(template, props),
467
+ compile: (template: string) => compiler(template, props),
468
+ };
469
+ }
470
+ export async function renderComponent<P = any>(component: Component<P>, template: string, context: ComponentContext): Promise<SendContent> {
471
+ const props = getProps(component, template, context);
472
+ return component(props, context);
473
+ }
474
+ // 渲染函数 - 支持新的组件系统
475
+ export async function renderComponents(
476
+ componentMap: Map<string, Component>,
477
+ options: SendOptions,
478
+ customContext?: ComponentContext
479
+ ): Promise<SendOptions> {
480
+ if (!componentMap.size) return options;
481
+
482
+ const components = [...Array.from(componentMap.values()), Fetch, Fragment];
483
+
484
+ // 创建根上下文
485
+ const rootContext = customContext || createComponentContext(
486
+ options,
487
+ undefined,
488
+ typeof options.content === 'string' ? options.content : segment.toString(options.content as MessageElement)
489
+ );
490
+
491
+ // 实现渲染逻辑
492
+ const renderWithContext = async (template: string, context: ComponentContext): Promise<SendContent> => {
493
+ let result = template;
494
+ let hasChanges = true;
495
+ let iterations = 0;
496
+ const maxIterations = 10; // 防止无限循环
497
+
498
+ // 编译模板
499
+ result = context.compile(result);
500
+
501
+ // 递归处理所有组件,直到没有更多组件需要渲染
502
+ while (hasChanges && iterations < maxIterations) {
503
+ hasChanges = false;
504
+ iterations++;
505
+
272
506
  for (const comp of components) {
273
- const match = comp.match(template);
274
- if (!match) continue;
275
- return await comp.render(match, ctx);
507
+ const match = matchComponent(comp, result);
508
+ if (match) {
509
+ // 创建组件特定的上下文
510
+ const componentContext = createComponentContext(
511
+ context.props,
512
+ context,
513
+ result
514
+ );
515
+
516
+ const rendered = await renderComponent(comp, match, componentContext);
517
+ const renderedString = typeof rendered === 'string' ? rendered : segment.toString(rendered as MessageElement);
518
+ result = result.replace(match, renderedString);
519
+ hasChanges = true;
520
+ break; // 处理一个组件后重新开始循环
521
+ }
276
522
  }
277
- return template;
278
- };
279
- const template=segment.toString(options.content);
280
- const output=await renderWithRuntime(template, options, template)
281
- const content=segment.from(output)
523
+ }
524
+
525
+ return result;
526
+ };
527
+
528
+ // 更新根上下文的渲染函数
529
+ rootContext.render = async (template: string, context?: Partial<ComponentContext>) => {
530
+ return await renderWithContext(template, rootContext);
531
+ };
532
+
533
+ // 渲染模板
534
+ const output = await renderWithContext(rootContext.root, rootContext);
535
+ const content = typeof output === 'string' ? segment.from(output) : output as MessageElement[];
536
+
282
537
  return {
283
538
  ...options,
284
539
  content
285
540
  };
286
541
  }
287
542
 
288
- export const Template=defineComponent({
289
- name: 'template',
290
- render(props, context) {
291
- const keys = Object.keys(props);
292
- if (!keys.length) keys.push('#default');
293
- for (const key of Object.keys(props)) {
294
- if (key.startsWith('#')) {
295
- context.parent.$slots[key.slice(1)] = (async p => {
296
- return await context.render(context.children || '', { ...context, ...p });
297
- }) as Render;
298
- }
299
- }
300
- return '';
301
- },
302
- })
303
- export const Slot=defineComponent({
304
- name: 'slot',
305
- props: {
306
- name: String,
307
- },
308
- render({ name, ...props }, context) {
309
- name = name || 'default';
310
- if (!context.parent) return '';
311
- if (context.parent.$slots[name]) return context.parent.$slots[name](props, context) as string;
312
- return context.children || '';
313
- },
314
- })
315
- }
316
- process.on('unhandledRejection',e=>{
317
- // console.error 已替换为注释
318
- })
543
+ // 内置组件
544
+ export const Fragment = defineComponent(async (props: { children?: SendContent }, context: ComponentContext) => {
545
+ let children = props.children || '';
546
+ if (Array.isArray(children)) {
547
+ return children.join('');
548
+ }
549
+ if (typeof children === 'string') {
550
+ try{
551
+ const parsed = JSON.parse(segment.unescape(children));
552
+ return context.render(parsed || '', context);
553
+ }catch{
554
+ return context.render(children || '', context);
555
+ }
556
+ }
557
+ return String(children);
558
+ }, 'Fragment');
559
+ export const Fetch = defineComponent(async ({ url }) => {
560
+ return await fetch(url).then((r) => r.text());
561
+ }, 'fetch');