@wiajs/core 1.2.2 → 2.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/dist/core.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * wia core v1.2.1
3
- * (c) 2015-2025 Sibyl Yu and contributors
2
+ * wia core v2.1.0
3
+ * (c) 2015-2026 Sibyl Yu and contributors
4
4
  * Released under the MIT License.
5
5
  */
6
6
  /**
@@ -2073,6 +2073,194 @@ class Event {
2073
2073
  }
2074
2074
  }
2075
2075
 
2076
+ /**
2077
+ * @typedef {Object} LogOptions
2078
+ * @property {string} m - 模块名称
2079
+ */
2080
+
2081
+ /**
2082
+ * @typedef {((...args: any[]) => void) & { debug: (...args: any[])=>void, info: (...args: any[])=>void, warn: (...args: any[])=>void, error: (...args: any[])=>void, err: (...args: any[])=>void }} ScopedLogger
2083
+ */
2084
+
2085
+ /**
2086
+ * @typedef {((...args: any[]) => void) & { log: (...args: any[])=>void, debug: (...args: any[])=>void, info: (...args: any[])=>void, warn: (...args: any[])=>void, error: (...args: any[])=>void, err: (...args: any[])=>void, fn: (name: string) => ScopedLogger }} LoggerFn
2087
+ */
2088
+
2089
+ // 1. 安全地获取开发环境状态(兼容打包工具、浏览器和 Node.js)
2090
+ // 如果 Rspack 注入了 __DEV__,就用注入的值;如果是在 Node.js 下,就回退读取 "production"
2091
+ const isDev = typeof __DEV__ !== 'undefined' ? __DEV__ : "production" !== 'production';
2092
+
2093
+ /**
2094
+ * 前端日志输出,封装 console 日志,简化代码
2095
+ * 自动识别并重排提示符与对象,保持前后端一致性
2096
+ */
2097
+ class Log {
2098
+ /** @type {string} 模块名称 */
2099
+ m = ''
2100
+
2101
+ /** @type {string} 函数名称 */
2102
+ fnName = ''
2103
+
2104
+ /**
2105
+ * @param {string} [m=''] 模块
2106
+ * @param {string} [fnName=''] 函数
2107
+ */
2108
+ constructor(m = '', fnName = '') {
2109
+ this.m = m;
2110
+ this.fnName = fnName;
2111
+ }
2112
+
2113
+ /**
2114
+ * 格式化参数:提取提示符,构建前缀,将对象后置
2115
+ * 格式要求:[模块名称:函数名称]提示符: {对象}
2116
+ * @param {any[]} args
2117
+ * @returns {any[]} 返回处理后传递给 console 的参数数组
2118
+ */
2119
+ formatArgs(args) {
2120
+ let R = '';
2121
+ const _ = this;
2122
+ try {
2123
+ const msgs = [];
2124
+ const objs = [];
2125
+
2126
+ // 1. 将字符串(提示符)和非字符串(对象/数值等)分开
2127
+ for (const arg of args) {
2128
+ if (typeof arg === 'string') msgs.push(arg);
2129
+ else objs.push(arg);
2130
+ }
2131
+
2132
+ let prefix = '';
2133
+
2134
+ // 2. 添加模块与函数前缀
2135
+ if (_.m) prefix = `[${_.m}${_.fnName ? ':' + _.fnName : ''}]`;
2136
+ else if (_.fnName) prefix = `[:${_.fnName}]`;
2137
+
2138
+ // 3. 拼接提示符
2139
+ const msg = msgs.join(' ');
2140
+ if (msg) prefix += msg;
2141
+
2142
+ R = [];
2143
+ if (prefix) R.push(prefix);
2144
+ R.push(...objs);
2145
+ } catch (e) {
2146
+ console.error(`formatArgs exp:${e.message}`);
2147
+ }
2148
+
2149
+ return R
2150
+ }
2151
+
2152
+ /** @param {...any} args */
2153
+ log(...args) {
2154
+ // 生产环境安全剔除 (需 Rspack DefinePlugin 注入 __DEV__)
2155
+ if (isDev) console.log(...this.formatArgs(args));
2156
+ }
2157
+
2158
+ /** @param {...any} args */
2159
+ debug(...args) {
2160
+ if (isDev) console.debug(...this.formatArgs(args));
2161
+ }
2162
+
2163
+ /** @param {...any} args */
2164
+ info(...args) {
2165
+ if (isDev) console.info(...this.formatArgs(args));
2166
+ }
2167
+
2168
+ /** @param {...any} args */
2169
+ warn(...args) {
2170
+ if (isDev) console.warn(...this.formatArgs(args));
2171
+ }
2172
+
2173
+ /** @param {...any} args */
2174
+ trace(...args) {
2175
+ if (isDev) console.trace(...this.formatArgs(args));
2176
+ }
2177
+
2178
+ /** * error 会在生产环境保留
2179
+ * @param {...any} args
2180
+ */
2181
+ error(...args) {
2182
+ console.error(...this.formatArgs(args));
2183
+ }
2184
+
2185
+ /**
2186
+ * 用于 catch(e) log.err(e) 或 log.err("提示符", e) 或 log.err(e, "提示符")
2187
+ * 自动对齐前缀 [模块:函数] 提示符: Error对象,并保留堆栈
2188
+ * @param {...any} args
2189
+ */
2190
+ err(...args) {
2191
+ // 生产环境安全输出,不会被 Tree-shaking 摇掉
2192
+ // formatArgs 会自动分离字符串与 Error 对象,并补全冒号
2193
+ const formattedArgs = this.formatArgs(args);
2194
+
2195
+ // 原生打印,保持 stack trace 可点击跳转!
2196
+ console.error(...formattedArgs);
2197
+ }
2198
+
2199
+ /**
2200
+ * 派生函数级别日志实例 (完美对齐后端 log.fn)
2201
+ * @param {string} name 函数名称
2202
+ * @returns {ScopedLogger}
2203
+ */
2204
+ fn(name) {
2205
+ const scopedLog = new Log(this.m, name);
2206
+
2207
+ /** @type {any} */
2208
+ const R = (...args) => scopedLog.log(...args);
2209
+
2210
+ R.debug = scopedLog.debug.bind(scopedLog);
2211
+ R.info = scopedLog.info.bind(scopedLog);
2212
+ R.warn = scopedLog.warn.bind(scopedLog);
2213
+ R.trace = scopedLog.trace.bind(scopedLog);
2214
+ R.error = scopedLog.error.bind(scopedLog);
2215
+ R.err = scopedLog.err.bind(scopedLog);
2216
+
2217
+ return /** @type {ScopedLogger} */ (R)
2218
+ }
2219
+ }
2220
+
2221
+ // 实例化唯一的全局日志单例
2222
+ const _log = new Log();
2223
+
2224
+ /**
2225
+ * 标准日志输出或构建模块日志类实例,用于模块中带[m:xxx]标记日志输出
2226
+ * 传入 { m: '模块名' } 构建实例,或直接传入参数输出全局日志
2227
+ * @param {...any} args - params
2228
+ * returns {*}
2229
+ */
2230
+ function log(...args) {
2231
+ const last = args.at(-1);
2232
+
2233
+ // 1. 如果只有一个参数且带 m 属性,说明是构造 Logger 实例 (如: const log = Log({m: 'User'}))
2234
+ if (args.length === 1 && typeof last === 'object' && last !== null && 'm' in last) {
2235
+ const lg = new Log(last.m);
2236
+
2237
+ /** @type {any} */
2238
+ const R = (...args2) => lg.log(...args2);
2239
+
2240
+ R.log = lg.log.bind(lg);
2241
+ R.debug = lg.debug.bind(lg);
2242
+ R.info = lg.info.bind(lg);
2243
+ R.warn = lg.warn.bind(lg);
2244
+ R.trace = lg.trace.bind(lg);
2245
+ R.error = lg.error.bind(lg);
2246
+ R.err = lg.err.bind(lg);
2247
+ R.fn = lg.fn.bind(lg); // 暴露派生函数作用域方法
2248
+
2249
+ return /** @type {LoggerFn} */ (R)
2250
+ }
2251
+
2252
+ // 2. 否则作为全局普通日志直接输出
2253
+ _log.log(...args);
2254
+ }
2255
+
2256
+ // 绑定全局快捷方法,方便直接调用 log.err(e) 等
2257
+ log.err = _log.err.bind(_log);
2258
+ log.error = _log.error.bind(_log);
2259
+ log.warn = _log.warn.bind(_log);
2260
+ log.info = _log.info.bind(_log);
2261
+ log.debug = _log.debug.bind(_log);
2262
+ log.trace = _log.trace.bind(_log);
2263
+
2076
2264
  /**
2077
2265
  * 所有页面从该类继承,并必须实现 load 事件!
2078
2266
  * 事件
@@ -2101,7 +2289,6 @@ class Event {
2101
2289
  *
2102
2290
  */
2103
2291
 
2104
-
2105
2292
  class Page extends Event {
2106
2293
  constructor(app, name, title, style) {
2107
2294
  super(null, [app]);
@@ -2143,16 +2330,16 @@ class Page extends Event {
2143
2330
  * 在已经加载就绪的视图上操作
2144
2331
  * @param {*} view 页面层的 Dom 对象,已经使用`$(#page-name)`,做了处理
2145
2332
  * @param {*} param go 函数的参数,或 网址中 url 中的参数
2146
- * @param {*} back 是否为回退,A->B, B->A,这种操作属于回退
2333
+ * @param {*} lastPath 前路由的路径,go 函数的参数,或 网址中 url 中的参数
2147
2334
  */
2148
- ready(view, param, back) {
2335
+ ready(view, param, lastPath) {
2149
2336
  // $.assign(this, {page, param, back});
2150
2337
  // $.assign(this.data, param);
2151
2338
  // 隐藏所有模板
2152
2339
  this.init();
2153
- this.emit('local::ready', view, param, back);
2340
+ this.emit('local::ready', view, param, lastPath);
2154
2341
  // 向上触发跨页面事件,存在安全问题
2155
- this.emit('pageReady', this, view, param, back);
2342
+ this.emit('pageReady', this, view, param, lastPath);
2156
2343
  }
2157
2344
 
2158
2345
  /**
@@ -2166,29 +2353,29 @@ class Page extends Event {
2166
2353
 
2167
2354
  // 显示已加载的页面
2168
2355
  // view:页面Dom层,param:参数
2169
- show(view, param) {
2356
+ show(view, param, lastPath) {
2170
2357
  // 隐藏所有模板
2171
2358
  view.qus('[name$=-tp]').hide();
2172
2359
  // 防止空链接,刷新页面
2173
2360
  view.qus('a[href=""]').attr('href', 'javascript:;');
2174
2361
  // this.init();
2175
2362
  if (this.reset) this.reset();
2176
- this.emit('local::show', view, param);
2363
+ this.emit('local::show', view, param, lastPath);
2177
2364
  // 向上触发跨页面事件,存在安全问题
2178
- this.emit('pageShow', this, view, param);
2365
+ this.emit('pageShow', this, view, param, lastPath);
2179
2366
  }
2180
2367
 
2181
2368
  // 回退显示已加载的页面
2182
2369
  // view:页面Dom层,param:参数
2183
- back(view, param) {
2370
+ back(view, param, lastPath) {
2184
2371
  // 隐藏所有模板
2185
2372
  view.qus('[name$=-tp]').hide();
2186
2373
  // 防止空链接,刷新页面
2187
2374
  view.qus('a[href=""]').attr('href', 'javascript:;');
2188
2375
 
2189
- this.emit('local::back', view, param);
2376
+ this.emit('local::back', view, param, lastPath);
2190
2377
  // 向上触发跨页面事件,存在安全问题
2191
- this.emit('pageBack', this, view, param);
2378
+ this.emit('pageBack', this, view, param, lastPath);
2192
2379
  }
2193
2380
 
2194
2381
  change(view, param, lastParam) {
@@ -2210,6 +2397,77 @@ class Page extends Event {
2210
2397
  }
2211
2398
  }
2212
2399
 
2400
+ /**
2401
+ * 页面工厂函数
2402
+ * 自动处理类的继承、日志注入、生命周期事件绑定
2403
+ * @param {Object} def - 页面默认配置
2404
+ * @param {Object} hooks - 业务钩子函数集合 { init, bind, show... }
2405
+ */
2406
+ function page(def, hooks = {}) {
2407
+ // 自动根据配置创建当前页面的专属日志实例
2408
+ const _log = log({m: `${def.name}`});
2409
+
2410
+ return class extends Page {
2411
+ constructor(opts = {}) {
2412
+ const opt = {...def, ...opts};
2413
+ super(opt.app, opt.name, opt.title);
2414
+ this.opt = opt;
2415
+
2416
+ // ✨ 魔法 1:利用 page.js 内置的 Event 机制,全自动挂载无侵入日志!
2417
+ this.on('local::load', param => _log({param}, 'load'));
2418
+ this.on('local::ready', (v, param, lastPath) => _log({v, param, lastPath, id: this.id}, 'ready'));
2419
+ this.on('local::show', (v, param, lastPath) => _log({v, param, lastPath, id: this.id}, 'show'));
2420
+ this.on('local::change', (v, param, lastParam) => _log({v, param, lastParam}, 'change'));
2421
+ this.on('local::back', (v, param, lastPath) => _log({v, param, lastPath, id: this.id}, 'back'));
2422
+ this.on('local::hide', v => _log({v, id: this.id}, 'hide'));
2423
+ this.on('local::unload', v => _log({v, id: this.id}, 'unload'));
2424
+ }
2425
+
2426
+ // 映射其他生命周期到业务 hooks
2427
+ load(param) {
2428
+ super.load(param);
2429
+ if (hooks.load) hooks.load({param, pg: this, log: _log});
2430
+ }
2431
+
2432
+ // ✨ 魔法 2:统一调度业务代码的 init 和 bind,提供优雅的上下文
2433
+ async ready(v, param, lastPath) {
2434
+ super.ready(v, param, lastPath);
2435
+
2436
+ // 组装上下文对象供业务使用
2437
+ const ctx = {v, pg: this, param, lastPath, log: _log};
2438
+
2439
+ // 自动按顺序执行业务层的初始化和事件绑定
2440
+ if (hooks.init) await hooks.init(ctx);
2441
+ if (hooks.bind) hooks.bind(ctx);
2442
+ }
2443
+
2444
+ show(v, param, lastPath) {
2445
+ super.show(v, param, lastPath);
2446
+ if (hooks.show) hooks.show({v, pg: this, param, lastPath, log: _log});
2447
+ }
2448
+
2449
+ change(v, param, lastParam) {
2450
+ super.change(v, param, lastParam);
2451
+ if (hooks.change) hooks.change({v, pg: this, param, lastParam, log: _log});
2452
+ }
2453
+
2454
+ back(v, param, lastPath) {
2455
+ super.back(v, param, lastPath);
2456
+ if (hooks.back) hooks.back({v, pg: this, param, lastPath, log: _log});
2457
+ }
2458
+
2459
+ hide(v, param) {
2460
+ super.hide(v, param);
2461
+ if (hooks.hide) hooks.hide({v, pg: this, param, log: _log});
2462
+ }
2463
+
2464
+ unload(v) {
2465
+ super.unload(v);
2466
+ if (hooks.unload) hooks.unload({v, pg: this, log: _log});
2467
+ }
2468
+ }
2469
+ }
2470
+
2213
2471
  /**
2214
2472
  * Wia app、router等继承类,通过模块化扩展类功能
2215
2473
  * 使用 use 装载,注解可能完成类似功能
@@ -4402,4 +4660,4 @@ class Modal extends Event {
4402
4660
  const Support = $.support;
4403
4661
  const Device = $.device;
4404
4662
 
4405
- export { Ajax, App, Constructors, Device, Event, Lazy, Modal, Modals, Module, Page, Resize, SW$1 as SW, Support, Utils, jsx, loadModule };
4663
+ export { Ajax, App, Constructors, Device, Event, Lazy, Modal, Modals, Module, Page, Resize, SW$1 as SW, Support, Utils, jsx, loadModule, page };
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * wia core v1.2.1
3
- * (c) 2015-2025 Sibyl Yu and contributors
2
+ * wia core v2.1.0
3
+ * (c) 2015-2026 Sibyl Yu and contributors
4
4
  * Released under the MIT License.
5
5
  */
6
6
  'use strict';
@@ -10,128 +10,150 @@ Object.defineProperty(exports, '__esModule', { value: true });
10
10
  var jsxRuntime = {};
11
11
 
12
12
  // runtime: 'automatic', // automatic or classic automatic 使用 JSX 运行时(在React 17 中引入)
13
- // HTML 属性值转义函数
14
- function escapeAttrValue(value) {
15
- return String(value)
16
- .replace(/&/g, "&")
17
- .replace(/"/g, """)
18
- .replace(/</g, "&lt;")
19
- .replace(/>/g, "&gt;");
13
+
14
+ // 1. 完善的 HTML 转义,防止 XSS 攻击
15
+ function escapeHtml(str) {
16
+ if (typeof str !== 'string') str = String(str);
17
+ return str
18
+ .replace(/&/g, '&amp;')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;')
21
+ .replace(/"/g, '&quot;')
22
+ .replace(/'/g, '&#39;') // 补充单引号转义
20
23
  }
21
24
 
22
- function jsx(tag, {children, ...props} = {}) {
23
- let R = '';
25
+ // 2. React 规范中无需添加 px 的免单位 CSS 属性集合(使用 Set,O(1) 查询效率最高)
26
+ const unitlessNumbers = new Set([
27
+ 'animationIterationCount', 'borderImageOutset', 'borderImageSlice', 'borderImageWidth',
28
+ 'boxFlex', 'boxFlexGroup', 'boxOrdinalGroup', 'columnCount', 'columns', 'flex',
29
+ 'flexGrow', 'flexPositive', 'flexShrink', 'flexNegative', 'flexOrder', 'gridRow',
30
+ 'gridRowEnd', 'gridRowSpan', 'gridRowStart', 'gridColumn', 'gridColumnEnd',
31
+ 'gridColumnSpan', 'gridColumnStart', 'fontWeight', 'lineClamp', 'lineHeight',
32
+ 'opacity', 'order', 'orphans', 'tabSize', 'widows', 'zIndex', 'zoom',
33
+ 'fillOpacity', 'floodOpacity', 'stopOpacity', 'strokeDasharray', 'strokeDashoffset',
34
+ 'strokeMiterlimit', 'strokeOpacity', 'strokeWidth'
35
+ ]);
24
36
 
25
- if (typeof tag === 'function') R = tag({children, ...props});
26
- else if (typeof tag === 'string') {
27
- const attrs = props || {};
28
- const attrsArray = [];
37
+ // 3. 自闭合标签(使用 Set 提升性能)
38
+ const voidElements = new Set([
39
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta',
40
+ 'param', 'source', 'track', 'wbr', 'path', 'circle', 'polygon', 'line',
41
+ 'rect', 'ellipse', 'use', 'stop'
42
+ ]);
29
43
 
30
- // 特殊处理 className 和 htmlFor
31
- if (attrs.className) {
32
- attrsArray.push(`class="${escapeAttrValue(attrs.className)}"`);
33
- delete attrs.className;
44
+ // 4. 深度递归渲染子节点 (支持多维嵌套、剔除空值、文本安全转义)
45
+ function renderChildren(children) {
46
+ if (children == null || typeof children === 'boolean') return ''
47
+
48
+ if (Array.isArray(children)) {
49
+ let out = '';
50
+ for (let i = 0; i < children.length; i++) {
51
+ out += renderChildren(children[i]); // 递归拍平
34
52
  }
53
+ return out
54
+ }
55
+
56
+ // 如果子节点本身已经是被 jsx() 处理过的字符串(或者是数字等),对其进行处理
57
+ // 注意:在我们这个极简架构中,jsx() 返回的就是拼接好的字符串,所以直接当作安全 HTML 对待
58
+ // 如果传入的是普通的字符串,为了安全起见,理论上应该转义。
59
+ // 为了区分“原生组件返回的 HTML 字符串”和“用户直接写入的文本”,通常会通过一个特殊对象包裹。
60
+ // 在当前纯字符串拼接的架构下,我们默认纯数字/纯字符串进入这里是被信任或已转义的,或者是组件生成的。
61
+ // 如果需要极其严格的 XSS 保护,建议对确认为“文本节点”的项调用 escapeHtml(children)。
62
+ return typeof children === 'string' ? children : escapeHtml(children)
63
+ }
64
+
65
+ function jsx(type, props = {}) {
66
+ // 处理函数组件: <MyComponent foo="bar" />
67
+ if (typeof type === 'function') {
68
+ return type(props)
69
+ }
70
+
71
+ // 处理原生 HTML 标签
72
+ let html = `<${type}`;
73
+ let innerHTML = '';
74
+ const children = props.children;
75
+
76
+ for (const key in props) {
77
+ if (!Object.prototype.hasOwnProperty.call(props, key)) continue
78
+ if (key === 'children') continue
35
79
 
36
- if (attrs.htmlFor) {
37
- attrsArray.push(`for="${escapeAttrValue(attrs.htmlFor)}"`);
38
- delete attrs.htmlFor;
80
+ const val = props[key];
81
+
82
+ // 过滤 React 保留属性和空值
83
+ if (key === 'key' || key === 'ref') continue
84
+
85
+ // 过滤掉绑定的事件函数(如 onClick, onChange),静态 HTML 不渲染这些
86
+ if (typeof val === 'function' && key.startsWith('on')) continue
87
+
88
+ // 支持 React 的 dangerouslySetInnerHTML
89
+ if (key === 'dangerouslySetInnerHTML') {
90
+ if (val && val.__html) innerHTML = val.__html;
91
+ continue
39
92
  }
40
93
 
41
- // 处理 style 对象
42
- if (attrs.style && typeof attrs.style === 'object') {
43
- const styleArray = [];
44
- for (const [key, value] of Object.entries(attrs.style)) {
45
- if (value == null) continue;
46
-
47
- // 转换驼峰式属性为CSS属性(backgroundColor -> background-color)
48
- const cssProp = key.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
49
-
50
- // 处理数值属性(添加px单位)
51
- let cssValue = value;
52
- if (typeof value === 'number' &&
53
- // 这些属性不需要单位
54
- !['zIndex', 'opacity', 'fontWeight', 'flex', 'order', 'zoom'].includes(key)) {
55
- cssValue = `${value}px`;
94
+ let attrName = key;
95
+ let attrValue = val;
96
+
97
+ // React 特有属性向原生 HTML 映射
98
+ if (attrName === 'className') attrName = 'class';
99
+ else if (attrName === 'htmlFor') attrName = 'for';
100
+ else if (attrName === 'defaultValue') attrName = 'value'; // 表单非受控默认值
101
+ else if (attrName === 'defaultChecked') attrName = 'checked';// 表单非受控默认选中
102
+
103
+ // 处理内联 Style 对象
104
+ if (attrName === 'style' && typeof attrValue === 'object') {
105
+ let styleStr = '';
106
+ for (const styleKey in attrValue) {
107
+ let styleVal = attrValue[styleKey];
108
+ if (styleVal == null || typeof styleVal === 'boolean' || styleVal === '') continue
109
+
110
+ let propName = styleKey;
111
+ // 处理 CSS 变量(如 --theme-color),不转驼峰,不加 px
112
+ if (styleKey.startsWith('--')) {
113
+ styleStr += `${propName}:${styleVal};`;
114
+ } else {
115
+ // 驼峰转中划线 (backgroundColor -> background-color)
116
+ propName = styleKey.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
117
+ // 自动补全 px
118
+ if (typeof styleVal === 'number' && styleVal !== 0 && !unitlessNumbers.has(styleKey)) {
119
+ styleVal = `${styleVal}px`;
120
+ }
121
+ styleStr += `${propName}:${styleVal};`;
56
122
  }
57
-
58
- styleArray.push(`${cssProp}:${cssValue}`);
59
- }
60
- attrsArray.push(`style="${escapeAttrValue(styleArray.join(';'))}"`);
61
- delete attrs.style;
62
- }
63
-
64
- // 处理其他属性
65
- for (const [key, value] of Object.entries(attrs)) {
66
- // 跳过特殊处理的属性
67
- if (key === 'children' || key === 'className' || key === 'htmlFor') continue;
68
-
69
- // 处理布尔属性(如 disabled, checked 等)
70
- if (typeof value === 'boolean') {
71
- if (value) attrsArray.push(key);
72
- }
73
- // 处理动态值属性
74
- else if (value != null) {
75
- attrsArray.push(`${key}="${escapeAttrValue(value)}"`);
76
123
  }
124
+ if (styleStr) html += ` style="${escapeHtml(styleStr)}"`;
125
+ continue
77
126
  }
78
127
 
79
- const attrsString = attrsArray.join(' ');
128
+ // 处理布尔值属性 ( disabled, checked)
129
+ if (typeof attrValue === 'boolean') {
130
+ if (attrValue) html += ` ${attrName}`;
131
+ } else if (attrValue != null) {
132
+ // 普通属性,安全转义输出
133
+ html += ` ${attrName}="${escapeHtml(attrValue)}"`;
134
+ }
135
+ }
80
136
 
81
- /* const attrsString = Object.keys(attrs)
82
- .map(attr => {
83
- if (attr[0] === '_') {
84
- if (attrs[attr]) return attr.replace('_', '');
85
- return '';
86
- }
87
- return `${attr}="${attrs[attr]}"`;
88
- })
89
- .filter(attr => !!attr)
90
- .join(' ');
91
- */
92
- // 自闭合标签处理
93
- const voidElements = ['input', 'img', 'br', 'hr', 'meta', 'link', 'area', 'base', 'col', 'embed', 'param', 'source', 'track', 'wbr', 'path', 'circle', 'polygon', 'line', 'rect', 'ellipse', 'use', 'stop'];
94
- if (voidElements.includes(tag))
95
- R = `<${tag}${attrsString ? ' ' + attrsString : ''} />`;
96
- else {
97
- const childrenContent = Array.isArray(children)
98
- ? children
99
- .filter(c => c != null) // 只过滤 null/undefined
100
- .map(c => {
101
- if (Array.isArray(c)) return c.join('');
102
-
103
- // 特殊处理布尔值(不渲染)
104
- if (typeof c === 'boolean') return '';
105
-
106
- return c;
107
- })
108
- .join('')
109
- : children != null
110
- ? (typeof children === 'boolean' ? '' : children)
111
- : '';
112
-
113
- R = `<${tag}${attrsString ? ' ' + attrsString : ''}>${childrenContent}</${tag}>`;
137
+ // 闭合标签处理
138
+ if (voidElements.has(type)) {
139
+ html += ` />`;
140
+ } else {
141
+ html += `>`;
142
+ // 渲染内容:如果存在 dangerouslySetInnerHTML,则优先渲染,否则渲染 children
143
+ if (innerHTML) {
144
+ html += innerHTML;
145
+ } else {
146
+ html += renderChildren(children);
114
147
  }
148
+ html += `</${type}>`;
115
149
  }
116
150
 
117
- return R;
151
+ return html
118
152
  }
119
153
 
154
+ // Fragment `<> ... </>` 只负责渲染并拍平内部子节点,不产生外层标签
120
155
  function Fragment({children} = {}) {
121
- const R = Array.isArray(children)
122
- ? children
123
- .filter(c => c != null) // 只过滤 null/undefined
124
- .map(c => {
125
- if (Array.isArray(c)) return c.join('');
126
- if (typeof c === 'boolean') return '';
127
- return c;
128
- })
129
- .join('')
130
- : children != null
131
- ? (typeof children === 'boolean' ? '' : children)
132
- : '';
133
-
134
- return R;
156
+ return renderChildren(children)
135
157
  }
136
158
 
137
159
  var Fragment_1 = jsxRuntime.Fragment = Fragment;