@wsxjs/wsx-core 0.0.17 → 0.0.18

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.
@@ -78,6 +78,12 @@ export abstract class BaseComponent extends HTMLElement {
78
78
  */
79
79
  private _pendingRerender: boolean = false;
80
80
 
81
+ /**
82
+ * 正在渲染标志(防止在 _rerender() 执行期间再次触发 scheduleRerender())
83
+ * @internal
84
+ */
85
+ protected _isRendering: boolean = false;
86
+
81
87
  /**
82
88
  * 子类应该重写这个方法来定义观察的属性
83
89
  * @returns 要观察的属性名数组
@@ -126,6 +132,12 @@ export abstract class BaseComponent extends HTMLElement {
126
132
  */
127
133
  protected onConnected?(): void;
128
134
 
135
+ /**
136
+ * 可选生命周期钩子:组件渲染完成后调用
137
+ * 在 DOM 更新完成后调用,适合执行需要访问 DOM 的操作(如语法高亮、初始化第三方库等)
138
+ */
139
+ protected onRendered?(): void;
140
+
129
141
  /**
130
142
  * 处理 blur 事件,在用户停止输入时执行待处理的重渲染
131
143
  * @internal
@@ -146,9 +158,14 @@ export abstract class BaseComponent extends HTMLElement {
146
158
 
147
159
  // 延迟一小段时间后重渲染,确保 blur 事件完全处理
148
160
  requestAnimationFrame(() => {
149
- if (this._pendingRerender && this.connected) {
161
+ if (this._pendingRerender && this.connected && !this._isRendering) {
150
162
  this._pendingRerender = false;
151
- this.rerender();
163
+ // 设置渲染标志,防止在 _rerender() 执行期间再次触发
164
+ // 注意:_isRendering 标志会在 _rerender() 的 onRendered() 调用完成后清除
165
+ this._isRendering = true;
166
+ // 调用 _rerender() 执行实际渲染(不再调用 rerender(),避免循环)
167
+ // _isRendering 标志会在 _rerender() 完成所有异步操作后清除
168
+ this._rerender();
152
169
  }
153
170
  });
154
171
  }
@@ -229,6 +246,11 @@ export abstract class BaseComponent extends HTMLElement {
229
246
  return;
230
247
  }
231
248
 
249
+ // 如果正在渲染,跳过本次调度(防止无限循环)
250
+ if (this._isRendering) {
251
+ return;
252
+ }
253
+
232
254
  // 检查是否有需要持续输入的元素获得焦点(input、textarea、select、contenteditable)
233
255
  // 按钮等其他元素应该立即重渲染,以反映状态变化
234
256
  const root = this.getActiveRoot();
@@ -273,18 +295,50 @@ export abstract class BaseComponent extends HTMLElement {
273
295
  // 对于按钮等其他元素,或者有 data-wsx-force-render 属性的输入元素,继续执行重渲染(不跳过)
274
296
  }
275
297
 
276
- // 没有焦点元素,立即重渲染(使用 queueMicrotask 批量处理)
298
+ // 没有焦点元素,立即重渲染(使用 requestAnimationFrame 确保在下一个渲染帧执行)
277
299
  // 如果有待处理的重渲染,也立即执行
278
300
  if (this._pendingRerender) {
279
301
  this._pendingRerender = false;
280
302
  }
281
- queueMicrotask(() => {
282
- if (this.connected) {
283
- this.rerender();
303
+ // 使用 requestAnimationFrame 而不是 queueMicrotask,确保在渲染帧中执行
304
+ // 这样可以避免在 render() 执行期间触发的 scheduleRerender() 立即执行
305
+ requestAnimationFrame(() => {
306
+ if (this.connected && !this._isRendering) {
307
+ // 设置渲染标志,防止在 _rerender() 执行期间再次触发
308
+ // 注意:_isRendering 标志会在 _rerender() 的 onRendered() 调用完成后清除
309
+ this._isRendering = true;
310
+ // 调用 _rerender() 执行实际渲染(不再调用 rerender(),避免循环)
311
+ // _isRendering 标志会在 _rerender() 完成所有异步操作后清除
312
+ this._rerender();
313
+ } else if (!this.connected) {
314
+ // 如果组件已断开,确保清除渲染标志
315
+ this._isRendering = false;
284
316
  }
285
317
  });
286
318
  }
287
319
 
320
+ /**
321
+ * 调度重渲染(公开 API)
322
+ *
323
+ * 与 scheduleRerender() 对齐:所有重渲染都通过统一的调度机制
324
+ * 使用异步调度机制,自动处理防抖和批量更新
325
+ *
326
+ * 注意:此方法现在是异步的,使用调度机制
327
+ * 如果需要同步执行,使用 _rerender()(不推荐,仅内部使用)
328
+ */
329
+ protected rerender(): void {
330
+ // 对齐到 scheduleRerender(),统一调度机制
331
+ this.scheduleRerender();
332
+ }
333
+
334
+ /**
335
+ * 内部重渲染实现(同步执行)
336
+ * 由 scheduleRerender() 在适当时机调用
337
+ *
338
+ * @internal - 子类需要实现此方法
339
+ */
340
+ protected abstract _rerender(): void;
341
+
288
342
  /**
289
343
  * 清理资源(在组件断开连接时调用)
290
344
  * @internal
@@ -312,11 +366,6 @@ export abstract class BaseComponent extends HTMLElement {
312
366
  document.addEventListener("blur", this.handleGlobalBlur, true);
313
367
  }
314
368
 
315
- /**
316
- * 重新渲染组件(子类需要实现)
317
- */
318
- protected abstract rerender(): void;
319
-
320
369
  /**
321
370
  * 获取配置值
322
371
  *
package/src/index.ts CHANGED
@@ -4,7 +4,9 @@ export { LightComponent } from "./light-component";
4
4
  export { autoRegister, registerComponent } from "./auto-register";
5
5
  export { h, h as jsx, h as jsxs, Fragment } from "./jsx-factory";
6
6
  export { StyleManager } from "./styles/style-manager";
7
- export { WSXLogger, logger, createLogger } from "./utils/logger";
7
+ // Re-export logger from wsx-logger for backward compatibility
8
+ export { WSXLogger, logger, createLogger, createLoggerWithConfig } from "@wsxjs/wsx-logger";
9
+ export type { Logger, LogLevel } from "@wsxjs/wsx-logger";
8
10
 
9
11
  // Reactive exports - Decorator-based API
10
12
  export { state } from "./reactive-decorator";
@@ -13,5 +15,4 @@ export { state } from "./reactive-decorator";
13
15
  export type { WebComponentConfig } from "./web-component";
14
16
  export type { LightComponentConfig } from "./light-component";
15
17
  export type { JSXChildren } from "./jsx-factory";
16
- export type { Logger, LogLevel } from "./utils/logger";
17
18
  export type { ReactiveCallback } from "./utils/reactive";
@@ -12,6 +12,7 @@
12
12
  // JSX 类型声明已移至 types/wsx-types.d.ts
13
13
 
14
14
  import { createElement, shouldUseSVGNamespace, getSVGAttributeName } from "./utils/svg-utils";
15
+ import { parseHTMLToNodes } from "./utils/dom-utils";
15
16
 
16
17
  // JSX子元素类型
17
18
  export type JSXChildren =
@@ -132,12 +133,52 @@ export function h(
132
133
  return element;
133
134
  }
134
135
 
136
+ /**
137
+ * 检测字符串是否包含HTML标签
138
+ * 使用更严格的检测:必须包含完整的 HTML 标签(开始和结束标签,或自闭合标签)
139
+ */
140
+ function isHTMLString(str: string): boolean {
141
+ const trimmed = str.trim();
142
+ if (!trimmed) return false;
143
+
144
+ // 更严格的检测:必须包含完整的 HTML 标签
145
+ // 1. 必须以 < 开头
146
+ // 2. 后面跟着字母(标签名)
147
+ // 3. 必须包含 > 来闭合标签
148
+ // 4. 排除单独的 < 或 > 符号(如数学表达式 "a < b")
149
+ const htmlTagPattern = /<[a-z][a-z0-9]*(\s[^>]*)?(\/>|>)/i;
150
+
151
+ // 额外检查:确保不是纯文本中的 < 和 >(如 "a < b" 或 "x > y")
152
+ // 如果字符串看起来像数学表达式或纯文本,不应该被检测为 HTML
153
+ const looksLikeMath = /^[^<]*<[^>]*>[^>]*$/.test(trimmed) && !htmlTagPattern.test(trimmed);
154
+ if (looksLikeMath) return false;
155
+
156
+ return htmlTagPattern.test(trimmed);
157
+ }
158
+
135
159
  /**
136
160
  * 扁平化子元素数组
161
+ * 自动检测HTML字符串并转换为DOM节点
162
+ *
163
+ * @param children - 子元素数组
164
+ * @param skipHTMLDetection - 是否跳过HTML检测(用于已解析的节点,避免无限递归)
165
+ * @param depth - 当前递归深度(防止无限递归,最大深度为 10)
137
166
  */
138
167
  function flattenChildren(
139
- children: JSXChildren[]
168
+ children: JSXChildren[],
169
+ skipHTMLDetection: boolean = false,
170
+ depth: number = 0
140
171
  ): (string | number | HTMLElement | SVGElement | DocumentFragment | boolean | null | undefined)[] {
172
+ // 防止无限递归:如果深度超过 10,停止处理
173
+ if (depth > 10) {
174
+ console.warn(
175
+ "[WSX] flattenChildren: Maximum depth exceeded, treating remaining children as text"
176
+ );
177
+ return children.filter(
178
+ (child): child is string | number =>
179
+ typeof child === "string" || typeof child === "number"
180
+ );
181
+ }
141
182
  const result: (
142
183
  | string
143
184
  | number
@@ -153,7 +194,44 @@ function flattenChildren(
153
194
  if (child === null || child === undefined || child === false) {
154
195
  continue;
155
196
  } else if (Array.isArray(child)) {
156
- result.push(...flattenChildren(child));
197
+ // 递归处理数组,保持 skipHTMLDetection 状态,增加深度
198
+ result.push(...flattenChildren(child, skipHTMLDetection, depth + 1));
199
+ } else if (typeof child === "string") {
200
+ // 如果跳过HTML检测,直接添加字符串(避免无限递归)
201
+ if (skipHTMLDetection) {
202
+ result.push(child);
203
+ } else if (isHTMLString(child)) {
204
+ // 自动检测HTML字符串并转换为DOM节点
205
+ // 使用 try-catch 防止解析失败导致崩溃
206
+ try {
207
+ const nodes = parseHTMLToNodes(child);
208
+ // 递归处理转换后的节点数组,标记为已解析,避免再次检测HTML
209
+ // parseHTMLToNodes 返回的字符串是纯文本节点,不应该再次被检测为HTML
210
+ // 但是为了安全,我们仍然设置 skipHTMLDetection = true
211
+ if (nodes.length > 0) {
212
+ // 直接添加解析后的节点,不再递归处理(避免无限递归)
213
+ // parseHTMLToNodes 已经完成了所有解析工作
214
+ for (const node of nodes) {
215
+ if (typeof node === "string") {
216
+ // 文本节点直接添加,不再检测 HTML(已解析)
217
+ result.push(node);
218
+ } else {
219
+ // DOM 元素直接添加
220
+ result.push(node);
221
+ }
222
+ }
223
+ } else {
224
+ // 如果解析失败,回退到纯文本
225
+ result.push(child);
226
+ }
227
+ } catch (error) {
228
+ // 如果解析失败,回退到纯文本,避免崩溃
229
+ console.warn("[WSX] Failed to parse HTML string, treating as text:", error);
230
+ result.push(child);
231
+ }
232
+ } else {
233
+ result.push(child);
234
+ }
157
235
  } else {
158
236
  result.push(child);
159
237
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { h, type JSXChildren } from "./jsx-factory";
11
11
  import { BaseComponent, type BaseComponentConfig } from "./base-component";
12
- import { createLogger } from "./utils/logger";
12
+ import { createLogger } from "@wsxjs/wsx-logger";
13
13
 
14
14
  const logger = createLogger("LightComponent");
15
15
 
@@ -59,8 +59,9 @@ export abstract class LightComponent extends BaseComponent {
59
59
  this.applyScopedStyles(styleName, stylesToApply);
60
60
  }
61
61
 
62
- // 检查是否有实际内容(排除样式元素)
62
+ // 检查是否有实际内容(排除样式元素和 slot 元素)
63
63
  // 错误元素:如果存在错误信息,需要重新渲染以恢复正常
64
+ // Slot 元素:不算"内容",因为 slot 的内容在 Light DOM 中(通过 JSX children 传递)
64
65
  const styleElement = this.querySelector(
65
66
  `style[data-wsx-light-component="${styleName}"]`
66
67
  ) as HTMLStyleElement | null;
@@ -71,14 +72,16 @@ export abstract class LightComponent extends BaseComponent {
71
72
  child.style.color === "red" &&
72
73
  child.textContent?.includes("Component Error")
73
74
  );
75
+ // 排除样式元素和 slot 元素
74
76
  const hasActualContent = Array.from(this.children).some(
75
- (child) => child !== styleElement
77
+ (child) => child !== styleElement && !(child instanceof HTMLSlotElement)
76
78
  );
77
79
 
78
80
  // 如果有错误元素,需要重新渲染以恢复正常
79
81
  // 如果有实际内容且没有错误,跳过渲染(避免重复元素)
80
82
  if (hasActualContent && !hasErrorElement) {
81
- // 已经有内容且没有错误,跳过渲染(避免重复元素)
83
+ // 已经有内容(JSX children),标记它们
84
+ this.markJSXChildren(); // 标记 JSX children,以便在 _rerender() 中保留
82
85
  // 但确保样式元素在正确位置
83
86
  if (styleElement && styleElement !== this.firstChild) {
84
87
  this.insertBefore(styleElement, this.firstChild);
@@ -106,6 +109,14 @@ export abstract class LightComponent extends BaseComponent {
106
109
 
107
110
  // 调用子类的初始化钩子(无论是否渲染,都需要调用,因为组件已连接)
108
111
  this.onConnected?.();
112
+
113
+ // 如果进行了渲染,调用 onRendered 钩子
114
+ if (hasActualContent === false || hasErrorElement) {
115
+ // 使用 requestAnimationFrame 确保 DOM 已完全更新
116
+ requestAnimationFrame(() => {
117
+ this.onRendered?.();
118
+ });
119
+ }
109
120
  } catch (error) {
110
121
  logger.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
111
122
  this.renderError(error);
@@ -144,29 +155,33 @@ export abstract class LightComponent extends BaseComponent {
144
155
  }
145
156
 
146
157
  /**
147
- * 重新渲染组件
158
+ * 内部重渲染实现
159
+ * 包含从 rerender() 方法迁移的实际渲染逻辑
160
+ * 处理 JSX children 的保留(Light DOM 特有)
161
+ *
162
+ * @override
148
163
  */
149
- protected rerender(): void {
164
+ protected _rerender(): void {
150
165
  if (!this.connected) {
151
- logger.warn(
152
- `[${this.constructor.name}] Component is not connected, skipping rerender.`
153
- );
166
+ // 如果组件未连接,清除渲染标志
167
+ this._isRendering = false;
154
168
  return;
155
169
  }
156
170
 
157
171
  // 1. 捕获焦点状态(在 DOM 替换之前)
158
172
  const focusState = this.captureFocusState();
159
- // 保存到实例变量,供 render() 使用(如果需要)
160
173
  this._pendingFocusState = focusState;
161
174
 
175
+ // 2. 保存 JSX children(通过 JSX factory 直接添加的 children)
176
+ // 这些 children 不是 render() 返回的内容,应该保留
177
+ const jsxChildren = this.getJSXChildren();
178
+
162
179
  try {
163
- // 重新渲染JSX内容
180
+ // 3. 重新渲染JSX内容
164
181
  const content = this.render();
165
182
 
166
- // 在添加到 DOM 之前恢复值,避免浏览器渲染状态值
167
- // 这样可以确保值在元素添加到 DOM 之前就是正确的
183
+ // 4. 在添加到 DOM 之前恢复值,避免浏览器渲染状态值
168
184
  if (focusState && focusState.key && focusState.value !== undefined) {
169
- // 在 content 树中查找目标元素
170
185
  const target = content.querySelector(
171
186
  `[data-wsx-key="${focusState.key}"]`
172
187
  ) as HTMLElement;
@@ -181,10 +196,10 @@ export abstract class LightComponent extends BaseComponent {
181
196
  }
182
197
  }
183
198
 
184
- // 确保样式元素存在
199
+ // 5. 确保样式元素存在
185
200
  const stylesToApply = this._autoStyles || this.config.styles;
201
+ const styleName = this.config.styleName || this.constructor.name;
186
202
  if (stylesToApply) {
187
- const styleName = this.config.styleName || this.constructor.name;
188
203
  let styleElement = this.querySelector(
189
204
  `style[data-wsx-light-component="${styleName}"]`
190
205
  ) as HTMLStyleElement;
@@ -201,27 +216,29 @@ export abstract class LightComponent extends BaseComponent {
201
216
  }
202
217
  }
203
218
 
204
- // 使用 requestAnimationFrame 批量执行 DOM 操作,减少重绘
205
- // 在同一帧中完成添加和移除,避免中间状态
219
+ // 6. 使用 requestAnimationFrame 批量执行 DOM 操作
206
220
  requestAnimationFrame(() => {
207
221
  // 先添加新内容
208
222
  this.appendChild(content);
209
223
 
210
- // 立即移除旧内容(在同一帧中,浏览器会批量处理)
224
+ // 移除旧内容(保留 JSX children 和样式元素)
211
225
  const oldChildren = Array.from(this.children).filter((child) => {
212
226
  // 保留新添加的内容
213
227
  if (child === content) {
214
228
  return false;
215
229
  }
216
- // 保留样式元素(如果存在)
230
+ // 保留样式元素
217
231
  if (
218
232
  stylesToApply &&
219
233
  child instanceof HTMLStyleElement &&
220
- child.getAttribute("data-wsx-light-component") ===
221
- (this.config.styleName || this.constructor.name)
234
+ child.getAttribute("data-wsx-light-component") === styleName
222
235
  ) {
223
236
  return false;
224
237
  }
238
+ // 保留 JSX children(关键修复)
239
+ if (child instanceof HTMLElement && jsxChildren.includes(child)) {
240
+ return false;
241
+ }
225
242
  return true;
226
243
  });
227
244
  oldChildren.forEach((child) => child.remove());
@@ -229,28 +246,74 @@ export abstract class LightComponent extends BaseComponent {
229
246
  // 确保样式元素在第一个位置
230
247
  if (stylesToApply && this.children.length > 1) {
231
248
  const styleElement = this.querySelector(
232
- `style[data-wsx-light-component="${this.config.styleName || this.constructor.name}"]`
249
+ `style[data-wsx-light-component="${styleName}"]`
233
250
  );
234
251
  if (styleElement && styleElement !== this.firstChild) {
235
252
  this.insertBefore(styleElement, this.firstChild);
236
253
  }
237
254
  }
238
255
 
239
- // 恢复焦点状态(在 DOM 替换之后)
240
- // 值已经在添加到 DOM 之前恢复了,这里只需要恢复焦点和光标位置
241
- // 使用另一个 requestAnimationFrame 确保 DOM 已完全更新
256
+ // 恢复焦点状态
242
257
  requestAnimationFrame(() => {
243
258
  this.restoreFocusState(focusState);
244
- // 清除待处理的焦点状态
245
259
  this._pendingFocusState = null;
260
+ // 调用 onRendered 生命周期钩子
261
+ this.onRendered?.();
262
+ // 在 onRendered() 完成后清除渲染标志,允许后续的 scheduleRerender()
263
+ this._isRendering = false;
246
264
  });
247
265
  });
248
266
  } catch (error) {
249
- logger.error(`[${this.constructor.name}] Error in rerender:`, error);
267
+ logger.error(`[${this.constructor.name}] Error in _rerender:`, error);
250
268
  this.renderError(error);
269
+ // 即使出错也要清除渲染标志,允许后续的 scheduleRerender()
270
+ this._isRendering = false;
251
271
  }
252
272
  }
253
273
 
274
+ /**
275
+ * 获取 JSX children(通过 JSX factory 直接添加的 children)
276
+ *
277
+ * 在 Light DOM 中,JSX children 是通过 JSX factory 直接添加到组件元素的
278
+ * 这些 children 不是 render() 返回的内容,应该保留
279
+ */
280
+ private getJSXChildren(): HTMLElement[] {
281
+ // 在 connectedCallback 中标记的 JSX children
282
+ // 使用 data 属性标记:data-wsx-jsx-child="true"
283
+ const jsxChildren = Array.from(this.children)
284
+ .filter(
285
+ (child) =>
286
+ child instanceof HTMLElement &&
287
+ child.getAttribute("data-wsx-jsx-child") === "true"
288
+ )
289
+ .map((child) => child as HTMLElement);
290
+
291
+ return jsxChildren;
292
+ }
293
+
294
+ /**
295
+ * 标记 JSX children(在 connectedCallback 中调用)
296
+ */
297
+ private markJSXChildren(): void {
298
+ // 在 connectedCallback 中,如果 hasActualContent 为 true
299
+ // 说明这些 children 是 JSX children,不是 render() 返回的内容
300
+ // 标记它们,以便在 _rerender() 中保留
301
+ const styleName = this.config.styleName || this.constructor.name;
302
+ const styleElement = this.querySelector(
303
+ `style[data-wsx-light-component="${styleName}"]`
304
+ ) as HTMLStyleElement | null;
305
+
306
+ Array.from(this.children).forEach((child) => {
307
+ if (
308
+ child instanceof HTMLElement &&
309
+ child !== styleElement &&
310
+ !(child instanceof HTMLSlotElement)
311
+ ) {
312
+ child.setAttribute("data-wsx-jsx-child", "true");
313
+ }
314
+ });
315
+ }
316
+
254
317
  /**
255
318
  * 渲染错误信息
256
319
  *
@@ -258,6 +321,8 @@ export abstract class LightComponent extends BaseComponent {
258
321
  */
259
322
  private renderError(error: unknown): void {
260
323
  // 清空现有内容
324
+ // Note: innerHTML is used here for framework-level error handling
325
+ // This is an exception to the no-inner-html rule for framework code
261
326
  this.innerHTML = "";
262
327
 
263
328
  const errorElement = h(
@@ -0,0 +1,48 @@
1
+ /**
2
+ * DOM utilities for WSX
3
+ *
4
+ * Provides helper functions for DOM manipulation and HTML parsing
5
+ */
6
+
7
+ /**
8
+ * Convert HTML string to DOM nodes (elements and text)
9
+ *
10
+ * This function parses an HTML string and returns an array of DOM nodes
11
+ * that can be used directly in WSX JSX. Text nodes are converted to strings,
12
+ * while HTML/SVG elements are kept as DOM elements.
13
+ *
14
+ * @param html - HTML string to parse
15
+ * @returns Array of HTMLElement, SVGElement, or string (for text nodes)
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const nodes = parseHTMLToNodes('<p>Hello <strong>World</strong></p>');
20
+ * // Returns: [HTMLElement (<p>), ...]
21
+ *
22
+ * return (
23
+ * <div>
24
+ * {nodes}
25
+ * </div>
26
+ * );
27
+ * ```
28
+ */
29
+ export function parseHTMLToNodes(html: string): (HTMLElement | SVGElement | string)[] {
30
+ if (!html) return [];
31
+
32
+ // Create a temporary container to parse HTML
33
+ // Note: innerHTML is used here for framework-level HTML parsing utility
34
+ // This is an exception to the no-inner-html rule for framework code
35
+ const temp = document.createElement("div");
36
+ temp.innerHTML = html;
37
+
38
+ // Convert all child nodes to array
39
+ // Text nodes are converted to strings, elements are kept as-is
40
+ return Array.from(temp.childNodes).map((node) => {
41
+ if (node instanceof HTMLElement || node instanceof SVGElement) {
42
+ return node;
43
+ } else {
44
+ // Convert text nodes and other node types to strings
45
+ return node.textContent || "";
46
+ }
47
+ });
48
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
- * WSX Framework Logger
2
+ * WSXJS Logger
3
3
  *
4
- * A lightweight logging utility for the WSX framework.
4
+ * A lightweight logging utility for the WSXJS.
5
5
  * Can be extended or replaced by consuming applications.
6
6
  */
7
7
 
@@ -4,7 +4,7 @@
4
4
  * 基于浏览器原生 Proxy API 实现轻量级响应式状态,
5
5
  * 遵循 WSX 设计哲学:信任浏览器,零运行时开销
6
6
  */
7
- import { createLogger } from "./logger";
7
+ import { createLogger } from "@wsxjs/wsx-logger";
8
8
 
9
9
  const logger = createLogger("ReactiveSystem");
10
10