```
feat(build): 添加 Vue 3 兼容性补丁插件 - 实现了 Vite 插件来拦截依赖模块加载并返回兼容 Vue 3 的补丁版本 - 添加 xe-utils hasOwnProp 补丁解决 Proxy 兼容性问题 - 添加 element-plus form-label-wrap 补丁防止 NaN 宽度和生命周期错误 - 实现虚拟模块系统避免修改 node_modules 文件 - 添加 _isMounted 守卫防止组件卸载后访问已销毁的上下文 - 实现缓存机制优化补丁代码加载性能 ```
This commit is contained in:
143
openhis-ui-vue3/src/patches/patch-deps-plugin.js
Normal file
143
openhis-ui-vue3/src/patches/patch-deps-plugin.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* Vite 插件:在构建时拦截依赖模块加载,返回兼容 Vue 3 的补丁版本。
|
||||||
|
*
|
||||||
|
* ⚠️ 不修改 node_modules 中的任何文件。
|
||||||
|
*
|
||||||
|
* 拦截清单:
|
||||||
|
* 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容
|
||||||
|
* 2. element-plus form-label-wrap.mjs — NaN 防护 + 生命周期守卫
|
||||||
|
*/
|
||||||
|
|
||||||
|
const VIRTUAL_HASOWNPROP = '\0patched:xe-utils/hasOwnProp'
|
||||||
|
const VIRTUAL_FORM_LABEL = '\0patched:element-plus/form-label-wrap'
|
||||||
|
|
||||||
|
export default function patchDepsPlugin() {
|
||||||
|
return {
|
||||||
|
name: 'patch-deps-vue3-compat',
|
||||||
|
enforce: 'pre',
|
||||||
|
|
||||||
|
// ── resolveId: 拦截模块 ID,返回虚拟模块路径 ──
|
||||||
|
resolveId(source, importer) {
|
||||||
|
// 拦截 xe-utils 内部的 require('./hasOwnProp')
|
||||||
|
if (
|
||||||
|
source === './hasOwnProp' &&
|
||||||
|
importer &&
|
||||||
|
importer.includes('xe-utils')
|
||||||
|
) {
|
||||||
|
return VIRTUAL_HASOWNPROP
|
||||||
|
}
|
||||||
|
// 拦截 element-plus form-label-wrap(完整路径匹配)
|
||||||
|
if (
|
||||||
|
source.includes('element-plus') &&
|
||||||
|
source.includes('form-label-wrap')
|
||||||
|
) {
|
||||||
|
return VIRTUAL_FORM_LABEL
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── load: 对被拦截的模块返回补丁代码 ──
|
||||||
|
load(id) {
|
||||||
|
if (id === VIRTUAL_HASOWNPROP) {
|
||||||
|
return PATCHED_HASOWNPROP
|
||||||
|
}
|
||||||
|
if (id === VIRTUAL_FORM_LABEL) {
|
||||||
|
return PATCHED_FORM_LABEL_WRAP
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// 补丁 1:xe-utils hasOwnProp — Proxy 兼容
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// 根因:Object.prototype.hasOwnProperty.call(proxyObj, key)
|
||||||
|
// 在 Vue 3 reactive Proxy 上触发 reactivity 拦截,
|
||||||
|
// 抛出 "obj.hasOwnProperty is not a function"。
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
const PATCHED_HASOWNPROP = `
|
||||||
|
function hasOwnProp(obj, key) {
|
||||||
|
if (obj == null) return false
|
||||||
|
try {
|
||||||
|
return Object.prototype.hasOwnProperty.call(obj, key)
|
||||||
|
} catch (e) {
|
||||||
|
try { return key in Object(obj) } catch (e2) { return false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default hasOwnProp
|
||||||
|
export { hasOwnProp }
|
||||||
|
`
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// 补丁 2:element-plus form-label-wrap
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
// 根因:vxe-table 展开行收起时 el-form 组件已卸载,
|
||||||
|
// 但 nextTick/onUpdated 回调仍访问已销毁的 formContext,
|
||||||
|
// 导致 NaN width 和 teardown 错误。
|
||||||
|
//
|
||||||
|
// 策略:从原始文件读取内容,在运行时用正则替换。
|
||||||
|
// 这样不依赖 node_modules 中的修改。
|
||||||
|
// ═══════════════════════════════════════════════
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
let cachedFormLabelWrap = null
|
||||||
|
|
||||||
|
function getFormLabelWrapCode() {
|
||||||
|
if (cachedFormLabelWrap) return cachedFormLabelWrap
|
||||||
|
|
||||||
|
const filePath = path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
'node_modules/element-plus/es/components/form/src/form-label-wrap.mjs'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.warn('[patch-deps] form-label-wrap.mjs not found, skipping')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = fs.readFileSync(filePath, 'utf-8')
|
||||||
|
|
||||||
|
cachedFormLabelWrap = code
|
||||||
|
// NaN 防护
|
||||||
|
.replace(
|
||||||
|
'return Math.ceil(Number.parseFloat(width))',
|
||||||
|
'return Math.ceil(Number.parseFloat(width)) || 0'
|
||||||
|
)
|
||||||
|
// _isMounted 守卫
|
||||||
|
.replace(
|
||||||
|
'const updateLabelWidth = (action = "update") => {',
|
||||||
|
'let _isMounted = true;\n const updateLabelWidth = (action = "update") => {'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'nextTick(() => {',
|
||||||
|
'nextTick(() => {\n if (!_isMounted) return;'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'if (slots.default && props.isAutoWidth) {',
|
||||||
|
'try {\n if (slots.default && props.isAutoWidth) {'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);',
|
||||||
|
'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);\n }\n } catch (e) { /* teardown race */ }'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'onBeforeUnmount(() => {',
|
||||||
|
'onBeforeUnmount(() => {\n _isMounted = false;'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'onUpdated(() => updateLabelWidthFn())',
|
||||||
|
'onUpdated(() => { if (_isMounted) updateLabelWidthFn(); })'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'if (props.updateAll) formContext?.registerLabelWidth(val, oldVal);',
|
||||||
|
'if (_isMounted && props.updateAll) formContext?.registerLabelWidth(val, oldVal);'
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'return () => {',
|
||||||
|
'return () => {\n if (!_isMounted) return null;'
|
||||||
|
)
|
||||||
|
|
||||||
|
return cachedFormLabelWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
const PATCHED_FORM_LABEL_WRAP = getFormLabelWrapCode()
|
||||||
Reference in New Issue
Block a user