@wsxjs/wsx-core 0.0.21 → 0.0.22

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/jsx.js CHANGED
@@ -170,6 +170,10 @@ var CACHE_KEY_PROP = "__wsxCacheKey";
170
170
  function markElement(element, cacheKey) {
171
171
  element[CACHE_KEY_PROP] = cacheKey;
172
172
  }
173
+ function getElementCacheKey(element) {
174
+ const key = element[CACHE_KEY_PROP];
175
+ return key !== void 0 ? String(key) : null;
176
+ }
173
177
  function isCreatedByH(element) {
174
178
  if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
175
179
  return false;
@@ -717,9 +721,18 @@ function updateProps(element, oldProps, newProps, tag) {
717
721
  if (oldValue === newValue) {
718
722
  continue;
719
723
  }
724
+ if (oldValue === void 0) {
725
+ applySingleProp2(element, key, newValue, tag, isSVG);
726
+ continue;
727
+ }
720
728
  if (typeof oldValue === "object" && oldValue !== null && typeof newValue === "object" && newValue !== null) {
721
- if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
722
- continue;
729
+ try {
730
+ const oldJson = JSON.stringify(oldValue);
731
+ const newJson = JSON.stringify(newValue);
732
+ if (oldJson === newJson) {
733
+ continue;
734
+ }
735
+ } catch {
723
736
  }
724
737
  }
725
738
  applySingleProp2(element, key, newValue, tag, isSVG);
@@ -729,68 +742,132 @@ function updateChildren(element, oldChildren, newChildren) {
729
742
  const flatOld = flattenChildren(oldChildren);
730
743
  const flatNew = flattenChildren(newChildren);
731
744
  const minLength = Math.min(flatOld.length, flatNew.length);
745
+ let domIndex = 0;
732
746
  for (let i = 0; i < minLength; i++) {
733
747
  const oldChild = flatOld[i];
734
748
  const newChild = flatNew[i];
749
+ let oldNode = null;
750
+ if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
751
+ if (oldChild.parentNode === element) {
752
+ if (!shouldPreserveElement(oldChild)) {
753
+ oldNode = oldChild;
754
+ }
755
+ } else {
756
+ const oldCacheKey = getElementCacheKey(oldChild);
757
+ if (oldCacheKey) {
758
+ for (let j = 0; j < element.childNodes.length; j++) {
759
+ const domChild = element.childNodes[j];
760
+ if (domChild instanceof HTMLElement || domChild instanceof SVGElement) {
761
+ if (shouldPreserveElement(domChild)) {
762
+ continue;
763
+ }
764
+ const domCacheKey = getElementCacheKey(domChild);
765
+ if (domCacheKey === oldCacheKey) {
766
+ oldNode = domChild;
767
+ break;
768
+ }
769
+ }
770
+ }
771
+ }
772
+ }
773
+ } else if (typeof oldChild === "string" || typeof oldChild === "number") {
774
+ while (domIndex < element.childNodes.length) {
775
+ const node = element.childNodes[domIndex];
776
+ if (node.nodeType === Node.TEXT_NODE) {
777
+ oldNode = node;
778
+ domIndex++;
779
+ break;
780
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
781
+ domIndex++;
782
+ } else {
783
+ domIndex++;
784
+ }
785
+ }
786
+ }
735
787
  if (typeof oldChild === "string" || typeof oldChild === "number") {
736
788
  if (typeof newChild === "string" || typeof newChild === "number") {
737
- const textNode = element.childNodes[i];
738
- if (textNode && textNode.nodeType === Node.TEXT_NODE) {
739
- textNode.textContent = String(newChild);
789
+ const oldText = String(oldChild);
790
+ const newText = String(newChild);
791
+ const needsUpdate = oldText !== newText || oldNode && oldNode.nodeType === Node.TEXT_NODE && oldNode.textContent !== newText;
792
+ if (!needsUpdate) {
793
+ continue;
794
+ }
795
+ if (oldNode && oldNode.nodeType === Node.TEXT_NODE) {
796
+ oldNode.textContent = newText;
740
797
  } else {
741
- const newTextNode = document.createTextNode(String(newChild));
742
- if (textNode) {
743
- element.replaceChild(newTextNode, textNode);
798
+ const newTextNode = document.createTextNode(newText);
799
+ if (oldNode && !shouldPreserveElement(oldNode)) {
800
+ element.replaceChild(newTextNode, oldNode);
744
801
  } else {
745
- element.appendChild(newTextNode);
802
+ element.insertBefore(newTextNode, oldNode || null);
746
803
  }
747
804
  }
748
805
  } else {
749
- const textNode = element.childNodes[i];
750
- if (textNode) {
751
- if (!shouldPreserveElement(textNode)) {
752
- element.removeChild(textNode);
753
- }
806
+ if (oldNode && !shouldPreserveElement(oldNode)) {
807
+ element.removeChild(oldNode);
754
808
  }
755
- if (typeof newChild === "string" || typeof newChild === "number") {
756
- element.appendChild(document.createTextNode(String(newChild)));
757
- } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
758
- element.appendChild(newChild);
809
+ if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
810
+ if (newChild.parentNode !== element) {
811
+ element.insertBefore(newChild, oldNode || null);
812
+ }
759
813
  } else if (newChild instanceof DocumentFragment) {
760
- element.appendChild(newChild);
814
+ element.insertBefore(newChild, oldNode || null);
761
815
  }
762
816
  }
763
817
  } else if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
818
+ if (oldNode && shouldPreserveElement(oldNode)) {
819
+ continue;
820
+ }
764
821
  if (newChild === oldChild) {
765
822
  continue;
766
823
  } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
767
- const oldNode = element.childNodes[i];
824
+ const oldCacheKey = oldNode && (oldNode instanceof HTMLElement || oldNode instanceof SVGElement) ? getElementCacheKey(oldNode) : null;
825
+ const newCacheKey = getElementCacheKey(newChild);
826
+ const hasSameCacheKey = oldCacheKey && newCacheKey && oldCacheKey === newCacheKey;
768
827
  if (oldNode) {
769
828
  if (!shouldPreserveElement(oldNode)) {
770
829
  if (oldNode !== newChild) {
771
- element.replaceChild(newChild, oldNode);
830
+ if (newChild.parentNode === element) {
831
+ if (hasSameCacheKey) {
832
+ if (newChild !== oldNode) {
833
+ element.replaceChild(newChild, oldNode);
834
+ }
835
+ } else {
836
+ element.removeChild(newChild);
837
+ element.replaceChild(newChild, oldNode);
838
+ }
839
+ } else if (newChild.parentNode) {
840
+ newChild.parentNode.removeChild(newChild);
841
+ element.replaceChild(newChild, oldNode);
842
+ } else {
843
+ element.replaceChild(newChild, oldNode);
844
+ }
772
845
  }
773
846
  } else {
774
847
  if (newChild.parentNode !== element) {
775
- element.appendChild(newChild);
848
+ if (newChild.parentNode) {
849
+ newChild.parentNode.removeChild(newChild);
850
+ }
851
+ element.insertBefore(newChild, oldNode.nextSibling);
776
852
  }
777
853
  }
778
854
  } else {
779
855
  if (newChild.parentNode !== element) {
856
+ if (newChild.parentNode) {
857
+ newChild.parentNode.removeChild(newChild);
858
+ }
780
859
  element.appendChild(newChild);
781
860
  }
782
861
  }
783
862
  } else {
784
- const oldNode = element.childNodes[i];
785
- if (oldNode) {
786
- if (!shouldPreserveElement(oldNode)) {
787
- element.removeChild(oldNode);
788
- }
863
+ if (oldNode && !shouldPreserveElement(oldNode)) {
864
+ element.removeChild(oldNode);
789
865
  }
790
866
  if (typeof newChild === "string" || typeof newChild === "number") {
791
- element.appendChild(document.createTextNode(String(newChild)));
867
+ const newTextNode = document.createTextNode(String(newChild));
868
+ element.insertBefore(newTextNode, oldNode?.nextSibling || null);
792
869
  } else if (newChild instanceof DocumentFragment) {
793
- element.appendChild(newChild);
870
+ element.insertBefore(newChild, oldNode?.nextSibling || null);
794
871
  }
795
872
  }
796
873
  }
@@ -803,20 +880,85 @@ function updateChildren(element, oldChildren, newChildren) {
803
880
  if (typeof newChild === "string" || typeof newChild === "number") {
804
881
  element.appendChild(document.createTextNode(String(newChild)));
805
882
  } else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
806
- if (newChild.parentNode !== element) {
807
- element.appendChild(newChild);
883
+ if (newChild.parentNode === element) {
884
+ const currentIndex = Array.from(element.childNodes).indexOf(newChild);
885
+ const expectedIndex = element.childNodes.length - 1;
886
+ if (currentIndex !== expectedIndex) {
887
+ element.removeChild(newChild);
888
+ element.appendChild(newChild);
889
+ }
890
+ continue;
891
+ } else if (newChild.parentNode) {
892
+ newChild.parentNode.removeChild(newChild);
808
893
  }
894
+ element.appendChild(newChild);
809
895
  } else if (newChild instanceof DocumentFragment) {
810
896
  element.appendChild(newChild);
811
897
  }
812
898
  }
813
899
  const nodesToRemove = [];
814
- for (let i = flatNew.length; i < element.childNodes.length; i++) {
900
+ const newChildSet = /* @__PURE__ */ new Set();
901
+ const newChildCacheKeyMap = /* @__PURE__ */ new Map();
902
+ for (const child of flatNew) {
903
+ if (child instanceof HTMLElement || child instanceof SVGElement || child instanceof DocumentFragment) {
904
+ newChildSet.add(child);
905
+ if (child instanceof HTMLElement || child instanceof SVGElement) {
906
+ const cacheKey = getElementCacheKey(child);
907
+ if (cacheKey) {
908
+ newChildCacheKeyMap.set(cacheKey, child);
909
+ }
910
+ }
911
+ }
912
+ }
913
+ const processedCacheKeys = /* @__PURE__ */ new Set();
914
+ const newChildToIndexMap = /* @__PURE__ */ new Map();
915
+ for (let i = 0; i < flatNew.length; i++) {
916
+ const child = flatNew[i];
917
+ if (child instanceof HTMLElement || child instanceof SVGElement) {
918
+ newChildToIndexMap.set(child, i);
919
+ }
920
+ }
921
+ for (let i = element.childNodes.length - 1; i >= 0; i--) {
815
922
  const child = element.childNodes[i];
816
- if (!shouldPreserveElement(child)) {
817
- nodesToRemove.push(child);
923
+ if (child instanceof HTMLElement || child instanceof SVGElement) {
924
+ if (shouldPreserveElement(child)) {
925
+ continue;
926
+ }
927
+ const cacheKey = getElementCacheKey(child);
928
+ if (cacheKey && newChildCacheKeyMap.has(cacheKey) && !processedCacheKeys.has(cacheKey)) {
929
+ processedCacheKeys.add(cacheKey);
930
+ const newChild = newChildCacheKeyMap.get(cacheKey);
931
+ if (child !== newChild) {
932
+ if (newChild.parentNode === element) {
933
+ element.replaceChild(newChild, child);
934
+ } else {
935
+ element.replaceChild(newChild, child);
936
+ }
937
+ } else {
938
+ }
939
+ }
818
940
  }
819
941
  }
942
+ for (let i = 0; i < element.childNodes.length; i++) {
943
+ const child = element.childNodes[i];
944
+ if (shouldPreserveElement(child)) {
945
+ continue;
946
+ }
947
+ if (child instanceof HTMLElement || child instanceof SVGElement) {
948
+ if (newChildSet.has(child)) {
949
+ continue;
950
+ }
951
+ const cacheKey = getElementCacheKey(child);
952
+ if (cacheKey && newChildCacheKeyMap.has(cacheKey)) {
953
+ continue;
954
+ }
955
+ } else if (child instanceof DocumentFragment) {
956
+ if (newChildSet.has(child)) {
957
+ continue;
958
+ }
959
+ }
960
+ nodesToRemove.push(child);
961
+ }
820
962
  for (let i = nodesToRemove.length - 1; i >= 0; i--) {
821
963
  const node = nodesToRemove[i];
822
964
  if (node.parentNode === element) {
@@ -828,12 +970,12 @@ function updateElement(element, newProps, newChildren, tag, cacheManager) {
828
970
  const oldMetadata = cacheManager.getMetadata(element);
829
971
  const oldProps = oldMetadata?.props || null;
830
972
  const oldChildren = oldMetadata?.children || [];
831
- updateProps(element, oldProps, newProps, tag);
832
- updateChildren(element, oldChildren, newChildren);
833
973
  cacheManager.setMetadata(element, {
834
974
  props: newProps || {},
835
975
  children: newChildren
836
976
  });
977
+ updateProps(element, oldProps, newProps, tag);
978
+ updateChildren(element, oldChildren, newChildren);
837
979
  }
838
980
 
839
981
  // src/jsx-factory.ts
@@ -847,7 +989,36 @@ function h(tag, props = {}, ...children) {
847
989
  if (context && cacheManager) {
848
990
  return tryUseCacheOrCreate(tag, props, children, context, cacheManager);
849
991
  }
850
- return createElementWithPropsAndChildren(tag, props, children);
992
+ try {
993
+ const nodeEnv = typeof globalThis.process !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
994
+ globalThis.process.env?.NODE_ENV;
995
+ if (nodeEnv === "development") {
996
+ if (!context) {
997
+ logger3.debug(
998
+ `h() called without render context. Tag: "${tag}", ComponentId: "${getComponentId()}"`,
999
+ {
1000
+ tag,
1001
+ props: props ? Object.keys(props) : [],
1002
+ hasCacheManager: !!cacheManager
1003
+ }
1004
+ );
1005
+ } else if (!cacheManager) {
1006
+ logger3.debug(
1007
+ `h() called with context but no cache manager. Tag: "${tag}", Component: "${context.constructor.name}"`,
1008
+ {
1009
+ tag,
1010
+ component: context.constructor.name
1011
+ }
1012
+ );
1013
+ }
1014
+ }
1015
+ } catch {
1016
+ }
1017
+ const element = createElementWithPropsAndChildren(tag, props, children);
1018
+ const componentId = getComponentId();
1019
+ const cacheKey = generateCacheKey(tag, props, componentId, context || void 0);
1020
+ markElement(element, cacheKey);
1021
+ return element;
851
1022
  }
852
1023
  function tryUseCacheOrCreate(tag, props, children, context, cacheManager) {
853
1024
  try {
@@ -857,6 +1028,14 @@ function tryUseCacheOrCreate(tag, props, children, context, cacheManager) {
857
1028
  if (cachedElement) {
858
1029
  const element2 = cachedElement;
859
1030
  updateElement(element2, props, children, tag, cacheManager);
1031
+ const isCustomElement = tag.includes("-") && customElements.get(tag);
1032
+ if (isCustomElement && element2.isConnected) {
1033
+ const parent = element2.parentNode;
1034
+ if (parent) {
1035
+ parent.removeChild(element2);
1036
+ parent.appendChild(element2);
1037
+ }
1038
+ }
860
1039
  return element2;
861
1040
  }
862
1041
  const element = createElementWithPropsAndChildren(tag, props, children);
@@ -880,7 +1059,12 @@ function handleCacheError(error, tag, props, children) {
880
1059
  }
881
1060
  } catch {
882
1061
  }
883
- return createElementWithPropsAndChildren(tag, props, children);
1062
+ const element = createElementWithPropsAndChildren(tag, props, children);
1063
+ const context = RenderContext.getCurrentComponent();
1064
+ const componentId = getComponentId();
1065
+ const cacheKey = generateCacheKey(tag, props, componentId, context || void 0);
1066
+ markElement(element, cacheKey);
1067
+ return element;
884
1068
  }
885
1069
  function Fragment(_props, children) {
886
1070
  const fragment = document.createDocumentFragment();
package/dist/jsx.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Fragment,
3
3
  h
4
- } from "./chunk-AR3DIDLV.mjs";
4
+ } from "./chunk-OXFZ575O.mjs";
5
5
  export {
6
6
  Fragment,
7
7
  h
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-core",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "Core WSXJS - Web Components with JSX syntax",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -48,7 +48,7 @@
48
48
  "custom-elements"
49
49
  ],
50
50
  "dependencies": {
51
- "@wsxjs/wsx-logger": "0.0.21"
51
+ "@wsxjs/wsx-logger": "0.0.22"
52
52
  },
53
53
  "devDependencies": {
54
54
  "tsup": "^8.0.0",
@@ -56,7 +56,44 @@ export function h(
56
56
  }
57
57
 
58
58
  // 无上下文:使用旧逻辑(向后兼容)
59
- return createElementWithPropsAndChildren(tag, props, children);
59
+ // 关键修复:即使没有上下文,也要标记元素,以便框架能够正确管理它
60
+ // 否则,未标记的元素会被 shouldPreserveElement() 保留,导致重复元素
61
+ // 调试日志:记录上下文丢失的情况,帮助定位问题(仅在开发环境输出)
62
+ try {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const nodeEnv = (typeof (globalThis as any).process !== "undefined" &&
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ (globalThis as any).process.env?.NODE_ENV) as string | undefined;
67
+ if (nodeEnv === "development") {
68
+ if (!context) {
69
+ logger.debug(
70
+ `h() called without render context. Tag: "${tag}", ComponentId: "${getComponentId()}"`,
71
+ {
72
+ tag,
73
+ props: props ? Object.keys(props) : [],
74
+ hasCacheManager: !!cacheManager,
75
+ }
76
+ );
77
+ } else if (!cacheManager) {
78
+ logger.debug(
79
+ `h() called with context but no cache manager. Tag: "${tag}", Component: "${context.constructor.name}"`,
80
+ {
81
+ tag,
82
+ component: context.constructor.name,
83
+ }
84
+ );
85
+ }
86
+ }
87
+ } catch {
88
+ // 忽略环境变量检查错误
89
+ }
90
+
91
+ const element = createElementWithPropsAndChildren(tag, props, children);
92
+ // 生成一个简单的 cache key(即使没有上下文)
93
+ const componentId = getComponentId();
94
+ const cacheKey = generateCacheKey(tag, props, componentId, context || undefined);
95
+ markElement(element, cacheKey);
96
+ return element;
60
97
  }
61
98
 
62
99
  /**
@@ -79,7 +116,28 @@ function tryUseCacheOrCreate(
79
116
  // 缓存键已经确保了唯一性(componentId + tag + position/key/index)
80
117
  // 不需要再检查标签名(可能导致错误复用)
81
118
  const element = cachedElement as HTMLElement | SVGElement;
119
+
82
120
  updateElement(element, props, children, tag, cacheManager);
121
+
122
+ // 关键修复(RFC-0039):检测自定义元素(Web Components)并重新触发生命周期
123
+ // 自定义元素有 connectedCallback 和 disconnectedCallback 方法
124
+ // 当它们被缓存复用时,需要模拟断开/重连以触发初始化逻辑
125
+ // 这确保了即使组件缺少 super.onConnected() 调用,框架层面也能保证正确的生命周期
126
+ const isCustomElement = tag.includes("-") && customElements.get(tag);
127
+ if (isCustomElement && element.isConnected) {
128
+ // 临时从 DOM 断开以触发 disconnectedCallback
129
+ const parent = element.parentNode;
130
+ if (parent) {
131
+ parent.removeChild(element);
132
+ // disconnectedCallback 会在 removeChild 时自动调用
133
+
134
+ // 立即重新添加以触发 connectedCallback
135
+ // 这确保生命周期在返回元素之前就已经完成
136
+ parent.appendChild(element);
137
+ // connectedCallback 会在 appendChild 时自动调用
138
+ }
139
+ }
140
+
83
141
  return element;
84
142
  }
85
143
 
@@ -120,7 +178,13 @@ function handleCacheError(
120
178
  } catch {
121
179
  // 忽略环境变量检查错误
122
180
  }
123
- return createElementWithPropsAndChildren(tag, props, children);
181
+ // 关键修复:即使缓存失败,也要标记元素,以便框架能够正确管理它
182
+ const element = createElementWithPropsAndChildren(tag, props, children);
183
+ const context = RenderContext.getCurrentComponent();
184
+ const componentId = getComponentId();
185
+ const cacheKey = generateCacheKey(tag, props, componentId, context || undefined);
186
+ markElement(element, cacheKey);
187
+ return element;
124
188
  }
125
189
 
126
190
  /**
@@ -10,6 +10,7 @@
10
10
  import { h, type JSXChildren } from "./jsx-factory";
11
11
  import { BaseComponent, type BaseComponentConfig } from "./base-component";
12
12
  import { RenderContext } from "./render-context";
13
+ import { shouldPreserveElement } from "./utils/element-marking";
13
14
  import { createLogger } from "@wsxjs/wsx-logger";
14
15
 
15
16
  const logger = createLogger("LightComponent");
@@ -222,7 +223,8 @@ export abstract class LightComponent extends BaseComponent {
222
223
  // 先添加新内容
223
224
  this.appendChild(content);
224
225
 
225
- // 移除旧内容(保留 JSX children 和样式元素)
226
+ // 移除旧内容(保留 JSX children、样式元素和未标记元素)
227
+ // 关键修复:使用 shouldPreserveElement() 来保护手动创建的元素(如第三方库注入的元素)
226
228
  const oldChildren = Array.from(this.children).filter((child) => {
227
229
  // 保留新添加的内容
228
230
  if (child === content) {
@@ -236,10 +238,15 @@ export abstract class LightComponent extends BaseComponent {
236
238
  ) {
237
239
  return false;
238
240
  }
239
- // 保留 JSX children(关键修复)
241
+ // 保留 JSX children(通过 JSX factory 直接添加的 children)
240
242
  if (child instanceof HTMLElement && jsxChildren.includes(child)) {
241
243
  return false;
242
244
  }
245
+ // 保留未标记的元素(手动创建的元素、第三方库注入的元素)
246
+ // 这是 RFC 0037 Phase 5 的核心:保护未标记元素
247
+ if (shouldPreserveElement(child)) {
248
+ return false;
249
+ }
243
250
  return true;
244
251
  });
245
252
  oldChildren.forEach((child) => child.remove());