- 创建 patch-deps.js 脚本用于修补 node_modules 中的依赖问题 - 修复 xe-utils hasOwnProp.js 的 Vue 3 Proxy 兼容性问题 - 为 element-plus form-label-wrap.mjs 添加 NaN 防护和生命周期守卫 - 实现幂等性确保可安全重复执行 - 添加自动跳过已修补文件的检查机制
138 lines
5.4 KiB
JavaScript
138 lines
5.4 KiB
JavaScript
/**
|
||
* patch-deps.js — 统一修补 node_modules 依赖
|
||
*
|
||
* 解决 Vue 3 + Vite 项目中依赖兼容性问题。
|
||
* 每次 npm install 后由 postinstall 自动执行,也可手动运行:
|
||
* node scripts/patch-deps.js
|
||
*
|
||
* 修补清单:
|
||
* 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容
|
||
* 2. element-plus form-label-wrap.mjs — NaN 防护 + 生命周期守卫
|
||
*
|
||
* 特性:幂等(safe to run multiple times)
|
||
*/
|
||
|
||
import fs from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
|
||
function patchFile(filePath, marker, patcher) {
|
||
if (!fs.existsSync(filePath)) {
|
||
console.log(`[patch-deps] SKIP (not found): ${path.relative(ROOT, filePath)}`);
|
||
return false;
|
||
}
|
||
const code = fs.readFileSync(filePath, 'utf-8');
|
||
if (code.includes(marker)) {
|
||
console.log(`[patch-deps] OK (already patched): ${path.relative(ROOT, filePath)}`);
|
||
return false;
|
||
}
|
||
const patched = patcher(code);
|
||
if (patched === code) {
|
||
console.log(`[patch-deps] SKIP (no change): ${path.relative(ROOT, filePath)}`);
|
||
return false;
|
||
}
|
||
fs.writeFileSync(filePath, patched, 'utf-8');
|
||
console.log(`[patch-deps] PATCHED: ${path.relative(ROOT, filePath)}`);
|
||
return true;
|
||
}
|
||
|
||
// ──────────────────────────────────────────────
|
||
// 1. xe-utils/hasOwnProp.js — Vue 3 Proxy 兼容
|
||
// ──────────────────────────────────────────────
|
||
// 根因:Object.prototype.hasOwnProperty.call(proxyObj, key) 在 Vue 3 reactive
|
||
// Proxy 上会触发 reactivity 拦截,抛出 "obj.hasOwnProperty is not a function"。
|
||
// 修复:try-catch + key-in fallback。
|
||
// ──────────────────────────────────────────────
|
||
patchFile(
|
||
path.join(ROOT, 'node_modules/xe-utils/hasOwnProp.js'),
|
||
'[vue3-proxy-safe]',
|
||
() => `/**
|
||
* Check if object has own property - Vue 3 Proxy safe [vue3-proxy-safe]
|
||
*
|
||
* Patched by scripts/patch-deps.js — do not edit manually.
|
||
* Re-run: node scripts/patch-deps.js
|
||
*/
|
||
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
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = hasOwnProp
|
||
`
|
||
);
|
||
|
||
// ──────────────────────────────────────────────
|
||
// 2. element-plus form-label-wrap.mjs — NaN + 生命周期守卫
|
||
// ──────────────────────────────────────────────
|
||
// 根因:vxe-table 展开行收起时 el-form 组件已卸载,但 nextTick/onUpdated
|
||
// 回调仍尝试访问已销毁的 formContext,导致 NaN width 和各种 teardown 错误。
|
||
// 修复:NaN 防护 + _isMounted 生命周期守卫 + try-catch。
|
||
// ──────────────────────────────────────────────
|
||
patchFile(
|
||
path.join(ROOT, 'node_modules/element-plus/es/components/form/src/form-label-wrap.mjs'),
|
||
'_isMounted',
|
||
(code) => {
|
||
let patched = 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\tconst updateLabelWidth = (action = "update") => {'
|
||
)
|
||
// nextTick 回调前置守卫
|
||
.replace(
|
||
'nextTick(() => {',
|
||
'nextTick(() => {\n\t\t\t\tif (!_isMounted) return;'
|
||
)
|
||
// try-catch 包裹核心逻辑
|
||
.replace(
|
||
'if (slots.default && props.isAutoWidth) {',
|
||
'try {\n\t\t\t\tif (slots.default && props.isAutoWidth) {'
|
||
)
|
||
.replace(
|
||
'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);',
|
||
'else if (action === "remove") formContext?.deregisterLabelWidth(computedWidth.value);\n\t\t\t\t}\n\t\t\t} catch (e) { /* teardown race */ }'
|
||
)
|
||
// onBeforeUnmount 设置守卫
|
||
.replace(
|
||
'onBeforeUnmount(() => {',
|
||
'onBeforeUnmount(() => {\n\t\t\t_isMounted = false;'
|
||
)
|
||
// onUpdated 守卫
|
||
.replace(
|
||
'onUpdated(() => updateLabelWidthFn())',
|
||
'onUpdated(() => { if (_isMounted) updateLabelWidthFn(); })'
|
||
)
|
||
// watch 守卫
|
||
.replace(
|
||
'if (props.updateAll) formContext?.registerLabelWidth(val, oldVal);',
|
||
'if (_isMounted && props.updateAll) formContext?.registerLabelWidth(val, oldVal);'
|
||
)
|
||
// render 函数守卫
|
||
.replace(
|
||
'return () => {',
|
||
'return () => {\n\t\t\tif (!_isMounted) return null;'
|
||
);
|
||
return patched;
|
||
}
|
||
);
|
||
|
||
// ──────────────────────────────────────────────
|
||
// 完成
|
||
// ──────────────────────────────────────────────
|
||
console.log('[patch-deps] Done.'); |