```
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