Browse Source

♻️ refactor(renderer): optimize record-id injection mechanism

- replace Vue-specific VNode hooks with framework-agnostic DOM creation hijacking
- implement document.createElement override to add record-id to all new elements
- add innerHTML setter hijacking to handle dynamically created content
- implement insertAdjacentHTML override for complete DOM manipulation coverage
- use setTimeout with 0 delay to ensure elements are inserted before setting record-id
- remove Vue 2 and Vue 3 specific VNode mounting hooks
- update console log message to reflect new DOM hijacking approach
yourname 1 month ago
parent
commit
f0ba84ff96
1 changed files with 50 additions and 31 deletions
  1. 50 31
      src/server/renderer.tsx

+ 50 - 31
src/server/renderer.tsx

@@ -785,39 +785,58 @@ export const Rooter = () => {
               return \`rec-\${tag}-\${contentHash}\${parentPath}\`;
             }
 
-            // 2. 注入固定 record-id(劫持 Vue 元素创建)
-            // Vue 2 适配
-            if (window.Vue && Vue.prototype._c) {
-              const originalCreateElement = Vue.prototype._c;
-              Vue.prototype._c = function(tag, data = {}, children, normalizationType) {
-                const vnode = originalCreateElement.call(this, tag, data, children, normalizationType);
-                const originalMounted = vnode.hook?.insert || vnode.hook?.mounted;
-                vnode.hook = vnode.hook || {};
-                vnode.hook.insert = vnode.hook.mounted = function(el) {
-                  originalMounted?.call(this, el);
-                  if (!el.hasAttribute('record-id')) el.setAttribute('record-id', getFixedRecordId(el));
-                };
-                return vnode;
-              };
-            }
-            console.debug('window.Vue', window.Vue)
-            // Vue 3 适配
-            if (window.Vue && Vue.createVNode) {
-              const originalCreateVNode = Vue.createVNode;
-              Vue.createVNode = function(type, props = {}, children, patchFlag, dynamicProps, shapeFlag) {
-                const vnode = originalCreateVNode.call(this, type, props, children, patchFlag, dynamicProps, shapeFlag);
-                const originalMounted = vnode.mount;
-                vnode.mount = function(el) {
-                  const result = originalMounted?.call(this, el) || el;
-                  const targetEl = result.el || result;
-                  if (targetEl && !targetEl.hasAttribute('record-id')) targetEl.setAttribute('record-id', getFixedRecordId(targetEl));
-                  return result;
-                };
-                return vnode;
-              };
+            // 2. 劫持原生 DOM 创建方法(框架无关方案)
+            const originalCreateElement = document.createElement;
+            document.createElement = function(tagName, options) {
+              const element = originalCreateElement.call(this, tagName, options);
+
+              // 延迟设置 record-id,确保元素已插入 DOM
+              setTimeout(() => {
+                if (element.parentNode && !element.hasAttribute('record-id')) {
+                  element.setAttribute('record-id', getFixedRecordId(element));
+                }
+              }, 0);
+
+              return element;
+            };
+
+            // 3. 劫持 innerHTML 设置,为动态创建的元素添加 record-id
+            const originalSetInnerHTML = Element.prototype.__lookupSetter__('innerHTML');
+            if (originalSetInnerHTML) {
+              Element.prototype.__defineSetter__('innerHTML', function(html) {
+                originalSetInnerHTML.call(this, html);
+
+                // 为新创建的元素添加 record-id
+                setTimeout(() => {
+                  const elements = this.querySelectorAll('*');
+                  elements.forEach(el => {
+                    if (!el.hasAttribute('record-id')) {
+                      el.setAttribute('record-id', getFixedRecordId(el));
+                    }
+                  });
+                }, 0);
+              });
             }
 
-            // 3. Broadcast Channel 同步点击
+            // 4. 劫持 insertAdjacentHTML
+            const originalInsertAdjacentHTML = Element.prototype.insertAdjacentHTML;
+            Element.prototype.insertAdjacentHTML = function(position, html) {
+              originalInsertAdjacentHTML.call(this, position, html);
+
+              // 为新创建的元素添加 record-id
+              setTimeout(() => {
+                const elements = this.querySelectorAll('*');
+                elements.forEach(el => {
+                  if (!el.hasAttribute('record-id')) {
+                    el.setAttribute('record-id', getFixedRecordId(el));
+                  }
+                });
+              }, 0);
+            };
+
+            console.log('DOM 元素创建劫持已启用');
+
+            // 5. Broadcast Channel 同步点击
             const bc = new BroadcastChannel('vue-click-sync'); // 同一频道名实现通信
 
             // 监听自身点击,发送到另一个窗口