@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/{chunk-AR3DIDLV.mjs → chunk-OXFZ575O.mjs} +223 -38
- package/dist/index.js +238 -42
- package/dist/index.mjs +19 -6
- package/dist/jsx-runtime.js +222 -38
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +222 -38
- package/dist/jsx.mjs +1 -1
- package/package.json +2 -2
- package/src/jsx-factory.ts +66 -2
- package/src/light-component.ts +9 -2
- package/src/utils/element-update.ts +314 -58
- package/src/web-component.ts +22 -5
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
|
-
|
|
722
|
-
|
|
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
|
|
738
|
-
|
|
739
|
-
|
|
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(
|
|
742
|
-
if (
|
|
743
|
-
element.replaceChild(newTextNode,
|
|
798
|
+
const newTextNode = document.createTextNode(newText);
|
|
799
|
+
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
800
|
+
element.replaceChild(newTextNode, oldNode);
|
|
744
801
|
} else {
|
|
745
|
-
element.
|
|
802
|
+
element.insertBefore(newTextNode, oldNode || null);
|
|
746
803
|
}
|
|
747
804
|
}
|
|
748
805
|
} else {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (!shouldPreserveElement(textNode)) {
|
|
752
|
-
element.removeChild(textNode);
|
|
753
|
-
}
|
|
806
|
+
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
807
|
+
element.removeChild(oldNode);
|
|
754
808
|
}
|
|
755
|
-
if (
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
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
|
-
|
|
867
|
+
const newTextNode = document.createTextNode(String(newChild));
|
|
868
|
+
element.insertBefore(newTextNode, oldNode?.nextSibling || null);
|
|
792
869
|
} else if (newChild instanceof DocumentFragment) {
|
|
793
|
-
element.
|
|
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
|
|
807
|
-
element.
|
|
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
|
-
|
|
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 (
|
|
817
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wsxjs/wsx-core",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
51
|
+
"@wsxjs/wsx-logger": "0.0.22"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"tsup": "^8.0.0",
|
package/src/jsx-factory.ts
CHANGED
|
@@ -56,7 +56,44 @@ export function h(
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// 无上下文:使用旧逻辑(向后兼容)
|
|
59
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/src/light-component.ts
CHANGED
|
@@ -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());
|