前端插件二开实战:使用pnpm/npm/yarn打补丁的完整指南

9 阅读15分钟

问题背景

在前端开发中,我们经常会遇到一些UI组件的交互不够友好的情况。最近,我们的产品经理提出了一个关于饿了么UI(Element UI)月份范围选择器的交互问题:

现状问题

当使用Element UI的月份范围选择器,并且设置为只能选择历史月份时,默认显示的是当前年份在左边,下一年在右边。这导致了一个问题:当业务需要选择去年的月份时,用户需要多点一下"去年"按钮,操作不够直观。

image.png

期望效果

产品经理希望优化这个交互,让默认显示效果变为:左边显示去年,右边显示当前年,这样用户就可以直接选择去年的月份,无需额外操作。

546b8a28977f19091003af2282b1ac62.png

解决方案探索

这个问题看似简单,但实际上Element UI并没有暴露相关的配置属性来控制这个行为。我们有几种可能的解决方案:

  1. 直接复制一份源码:这是最直接的方法,但对于一个高级前端开发来说,这种方式不够优雅,而且难以维护。

  2. 使用CSS或JS hack:通过CSS或JavaScript来修改组件的行为,但这种方式可能会在组件版本更新时失效。

  3. 使用pnpm打补丁:这是一种更优雅的方式,通过修改依赖包的源码并创建补丁,既可以解决问题,又可以在依赖更新时重新应用补丁。

我们选择了第三种方案,使用pnpm来给Element UI源码打补丁。下面是详细的实现过程。

准备工作

1. 环境要求

在开始之前,确保你的电脑上已经安装了以下软件:

  • Node.js 14+(可以在Node.js官网下载安装)
  • pnpm 6+(可以通过 npm install -g pnpm 命令安装)
  • Element UI 2.15.14(我们将在后面的步骤中安装)

2. 安装依赖

首先,打开命令行工具(Windows系统可以使用CMD或PowerShell,Mac系统可以使用Terminal),进入你的项目目录,然后运行以下命令安装Element UI:

# 进入项目目录
cd 你的项目目录

# 安装Element UI 2.15.14
pnpm add element-ui@2.15.14

安装完成后,你可以在项目的 package.json 文件中看到Element UI已经被添加到依赖中。

调试Element UI源码

1. 定位相关代码

要修改月份范围选择器的默认显示行为,我们需要找到相关的源码。

首先,打开你的项目文件夹,找到 node_modules 目录,然后进入 element-ui 文件夹,再进入 lib 目录,找到 element-ui.common.js 文件。这个文件包含了Element UI的所有组件代码,但它是压缩过的,看起来会比较乱。

不过别担心,我们可以使用文本编辑器的搜索功能来找到我们需要的代码。打开 element-ui.common.js 文件,然后搜索 month-range 或者 calcDefaultValue,你会找到月份范围选择器的相关代码。

2. 分析代码逻辑

找到 calcDefaultValue 函数后,我们来分析一下它的逻辑:

var month_rangevue_type_script_lang_js_calcDefaultValue = function calcDefaultValue(val) {
  var _calcDefaultValue = calcDefaultValue(val),
      left = _calcDefaultValue[0],
      right = _calcDefaultValue[1];

  this.leftDate = left;
  this.rightDate = val && val[1] && left.getFullYear() !== right.getFullYear() && this.unlinkPanels ? right : Object(date_util_["nextYear"])(this.leftDate);
};

这段代码的逻辑是:

  1. 计算默认值,得到左右两个日期
  2. 将左边日期(this.leftDate)设置为当前日期
  3. 将右边日期(this.rightDate)设置为下一年

这就是为什么默认显示当前年在左边,下一年在右边的原因。

3. 确定修改方案

我们需要修改这段代码,添加一个 history 属性,当该属性为 true 时,默认将左侧日期设置为上一年,右侧日期设置为当前年。这样,当用户打开月份范围选择器时,就可以直接看到去年和今年的月份,无需额外点击"去年"按钮。

打补丁过程

1. 安装patch-package

首先,我们需要安装 patch-package 工具,用于创建和应用补丁。打开命令行工具,进入项目目录,运行以下命令:

# 进入项目目录
cd 你的项目目录

# 安装patch-package作为开发依赖
pnpm add -D patch-package

安装完成后,你可以在项目的 package.json 文件中看到 patch-package 已经被添加到开发依赖中。

2. 修改源码

现在,我们需要修改Element UI的源码。按照之前的步骤,找到 node_modules/element-ui/lib/element-ui.common.js 文件,定位到 calcDefaultValue 函数,然后进行修改:

修改前的代码

var month_rangevue_type_script_lang_js_calcDefaultValue = function calcDefaultValue(val) {
  var _calcDefaultValue = calcDefaultValue(val),
      left = _calcDefaultValue[0],
      right = _calcDefaultValue[1];

  this.leftDate = left;
  this.rightDate = val && val[1] && left.getFullYear() !== right.getFullYear() && this.unlinkPanels ? right : Object(date_util_["nextYear"])(this.leftDate);
};

修改后的代码

var month_rangevue_type_script_lang_js_calcDefaultValue = function calcDefaultValue(val) {
  var _calcDefaultValue = calcDefaultValue(val),
      left = _calcDefaultValue[0],
      right = _calcDefaultValue[1];

  if (val) {
    this.leftDate = this.history ? Object(date_util["prevYear"])(left) : left;
    this.rightDate = val && val[1] && left.getFullYear() !== right.getFullYear() && this.unlinkPanels ? right : Object(date_util["nextYear"])(this.leftDate);
  } else {
    if (!this.history) return;
    // 确保value存在且是数组
    if (!this.value || !Array.isArray(this.value)) return;
    const [start, end] = this.value.map(date => new Date(date).getFullYear());
    if (start !== end) return;
    this.leftDate = Object(date_util["prevYear"])(this.leftDate);
    this.rightDate = Object(date_util["nextYear"])(this.leftDate);
  }
};

3. 创建补丁

修改完成后,保存文件,然后回到命令行工具,运行以下命令创建补丁:

# 创建补丁
pnpm patch element-ui

运行这个命令后,pnpm会提示你输入补丁的描述,你可以输入一个简短的描述,比如"优化月份范围选择器默认显示行为",然后按回车键。

这会创建一个补丁文件,位于 patches/element-ui@2.15.14.patch

4. 补丁产物比对

补丁文件是一个文本文件,它记录了我们对Element UI源码的修改。打开 patches/element-ui@2.15.14.patch 文件,你会看到类似以下的内容:

diff --git a/lib/element-ui.common.js b/lib/element-ui.common.js
index 5631486796d454e650722ee90ed15472cc703643..851671b16f4b79d632a7113aed92f89b18560ef2 100644
--- a/lib/element-ui.common.js
+++ b/lib/element-ui.common.js
@@ -20394,8 +20394,14 @@ var month_rangevue_type_script_lang_js_calcDefaultValue = function calcDefaultVa
             left = _calcDefaultValue[0],
             right = _calcDefaultValue[1];
 
-        this.leftDate = left;
+        this.leftDate = this.history ? Object(date_util["prevYear"])(left) : left;
         this.rightDate = val && val[1] && left.getFullYear() !== right.getFullYear() && this.unlinkPanels ? right : Object(date_util["nextYear"])(this.leftDate);
+      }else{
+        if(!this.history) return;
+        const [strat, end] = this.value.map(date => new Date(date).getFullYear());
+        if(strat!==end) return;
+        this.leftDate = Object(date_util["prevYear"])(this.leftDate);
+        this.rightDate = Object(date_util["nextYear"])(this.leftDate);
+      }
     }
   },

这个补丁文件清晰地展示了我们的修改:

  • 我们添加了一个 history 属性的判断
  • historytrue 时,将左侧日期设置为上一年
  • historytrue 且值的年份相同时,也会将左侧日期设置为上一年,右侧日期设置为下一年

通过补丁文件,我们可以清楚地看到修改前后的代码差异,这对于后续的维护和版本更新非常重要。

5. 配置自动应用补丁

为了确保每次安装依赖时都能自动应用补丁,我们需要在 package.json 文件中添加一个 postinstall 脚本:

打开项目的 package.json 文件,找到 scripts 部分,添加以下代码:

"scripts": {
  "postinstall": "patch-package"
}

这样,当运行 pnpm install 时,补丁会自动应用。

6. 使用修改后的组件

现在,我们可以在代码中使用修改后的月份范围选择器,并通过 history 属性来控制默认显示行为:

修改后的demo代码

<template>
  <div class="app">
    <h1>月份范围选择器优化示例</h1>
    
    <!-- 普通模式 -->
    <div class="demo-item">
      <h3>普通模式(默认)</h3>
      <el-date-picker
        v-model="dateRange1"
        type="monthrange"
        range-separator="至"
        start-placeholder="开始月份"
        end-placeholder="结束月份"
        :picker-options="{ disabledDate: (time) => time.getTime() > Date.now() }"
      />
    </div>
    
    <!-- 历史模式 -->
    <div class="demo-item">
      <h3>历史模式(优化后)</h3>
      <el-date-picker
        v-model="dateRange2"
        type="monthrange"
        :history="true"  <!-- 启用历史月份模式 -->
        range-separator="至"
        start-placeholder="开始月份"
        end-placeholder="结束月份"
        :picker-options="{ disabledDate: (time) => time.getTime() > Date.now() }"
      />
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dateRange1: [],
      dateRange2: []
    };
  }
};
</script>

<style scoped>
.app {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.demo-item {
  margin-bottom: 30px;
  padding: 20px;
  border: 1px solid #eaeaea;
  border-radius: 4px;
}

h1 {
  text-align: center;
  margin-bottom: 30px;
}

h3 {
  margin-bottom: 15px;
  color: #333;
}
</style>

效果对比

  • 普通模式:默认显示当前年在左边,下一年在右边
  • 历史模式:默认显示去年在左边,当前年在右边

现在,当你打开月份范围选择器时,就会看到左边显示去年,右边显示今年,这样就可以直接选择去年的月份,无需额外点击"去年"按钮了。

使用npm和yarn打补丁

除了使用pnpm,我们也可以使用npm或yarn来打补丁。下面是使用npm和yarn打补丁的方法:

使用npm打补丁

  1. 安装patch-package

    npm install --save-dev patch-package
    
  2. 修改源码: 找到 node_modules/element-ui/lib/element-ui.common.js 文件,定位到 calcDefaultValue 函数,进行修改(修改内容与之前相同)。

  3. 创建补丁

    npx patch-package element-ui
    
  4. 配置自动应用补丁: 在 package.json 文件中添加 postinstall 脚本:

    "scripts": {
      "postinstall": "patch-package"
    }
    

使用yarn打补丁

  1. 安装patch-package

    yarn add --dev patch-package
    
  2. 修改源码: 找到 node_modules/element-ui/lib/element-ui.common.js 文件,定位到 calcDefaultValue 函数,进行修改(修改内容与之前相同)。

  3. 创建补丁

    yarn patch-package element-ui
    
  4. 配置自动应用补丁: 在 package.json 文件中添加 postinstall 脚本:

    "scripts": {
      "postinstall": "patch-package"
    }
    

无论使用哪种包管理器,打补丁的基本流程都是相同的:修改源码 -> 创建补丁 -> 配置自动应用补丁。

遇到的问题及解决方法

在打补丁的过程中,你可能会遇到一些问题,下面是一些常见问题的解决方法:

1. 重新下载依赖后补丁失效

问题:当我们重新运行 pnpm install 时,补丁可能会失效,因为依赖会被重新下载,覆盖我们的修改。

解决方法:这就是为什么我们需要在 package.json 中添加 postinstall 脚本的原因。这个脚本会在每次安装依赖后自动运行,重新应用我们的补丁。

2. 补丁文件是压缩的文件,修改起来困难

问题:Element UI的源码在npm包中是压缩的,看起来非常混乱,修改起来比较困难。

解决方法

  • 使用文本编辑器的搜索功能:大多数现代文本编辑器(如VS Code、Sublime Text等)都有强大的搜索功能,你可以使用它来快速找到你需要的代码。
  • 使用在线代码格式化工具:如果你觉得压缩的代码实在难以阅读,可以使用在线代码格式化工具(如 JS Beautifier)来格式化代码,使其更易读。
  • 直接搜索关键词:你可以直接搜索 calcDefaultValuemonth-range 等关键词,找到相关的代码。

3. 版本兼容性问题

问题:当Element UI版本更新时,补丁可能会失效,因为新版本的代码结构可能会发生变化。

解决方法

  • 在更新Element UI版本后,重新创建补丁:当你更新Element UI版本时,需要重新修改源码并创建新的补丁。
  • 记录补丁的作用和修改的内容:在项目文档中记录补丁的作用和修改的内容,以便后续维护。这样,当Element UI版本更新时,你可以快速找到需要修改的部分。
  • 关注Element UI的更新日志:定期查看Element UI的更新日志,了解是否有相关的功能更新或bug修复。

4. 找不到相关的代码

问题:在 element-ui.common.js 文件中找不到 calcDefaultValue 函数。

解决方法

  • 确保你使用的是Element UI 2.15.14版本:不同版本的Element UI代码结构可能会有所不同,确保你使用的是与本文相同的版本。
  • 尝试搜索其他关键词:如果找不到 calcDefaultValue 函数,可以尝试搜索 month-rangeleftDaterightDate 等关键词。
  • 查看Element UI的源码仓库:如果你仍然找不到相关的代码,可以查看Element UI的源码仓库,找到 month-range.vue 文件,查看其中的 calcDefaultValue 函数。

代码解析

让我们来详细解析一下我们的修改,以便你能更好地理解代码的逻辑:

1. 添加history属性

我们添加了一个 history 属性,用于控制是否默认显示上一年和当前年。当这个属性为 true 时,月份范围选择器会默认显示去年在左边,今年在右边;当这个属性为 false 时,保持原来的行为,即默认显示今年在左边,明年在右边。

2. 修改默认值计算逻辑

我们修改了 calcDefaultValue 函数的逻辑,添加了一个条件判断:

  • 当有值传入时:如果 historytrue,则将左侧日期设置为上一年;否则,保持原来的行为。
  • 当没有值传入时:如果 historytrue 且当前值的年份相同,则将左侧日期设置为上一年,右侧日期设置为下一年。

3. 保持向后兼容

我们的修改保持了向后兼容性,当 historyfalse 时,组件的行为与原来一致。这样,即使你不添加 history 属性,组件也能正常工作,不会影响现有的代码。

4. 代码执行流程

当你使用修改后的月份范围选择器时,代码的执行流程如下:

  1. 当你打开月份范围选择器时,calcDefaultValue 函数会被调用。
  2. 函数会检查是否传入了 history 属性。
  3. 如果 historytrue,则将左侧日期设置为上一年,右侧日期设置为今年。
  4. 如果 historyfalse 或未设置,则保持原来的行为,将左侧日期设置为今年,右侧日期设置为明年。

这样,当你需要选择历史月份时,就可以直接看到去年和今年的月份,无需额外点击"去年"按钮了。

打补丁的好处与团队协作

使用打补丁的方式进行插件二次开发,有以下几个明显的好处:

1. 保持代码的可维护性

  • 避免直接修改源码:直接修改 node_modules 中的源码会在依赖更新时被覆盖,而打补丁的方式可以在依赖更新后重新应用补丁。
  • 清晰的修改记录:补丁文件记录了所有的修改内容,使代码变更更加透明和可追溯。
  • 版本兼容性:当插件版本更新时,我们可以根据需要重新创建补丁,确保修改在新版本中仍然有效。

2. 团队协作优势

  • 共享修改:补丁文件可以提交到版本控制系统中,团队成员可以共享和复用这些修改。
  • 统一环境:通过 postinstall 脚本,确保所有团队成员在安装依赖后都能自动应用补丁,避免环境不一致的问题。
  • 代码审查:补丁文件可以作为代码审查的对象,团队成员可以清楚地看到修改的内容和目的。
  • 减少冲突:当多个团队成员需要修改同一个插件时,通过补丁的方式可以减少代码冲突的可能性。

3. 其他优势

  • 灵活性:可以根据项目的具体需求对插件进行定制化修改,而不受插件原有功能的限制。
  • 安全性:修改仅限于项目内部,不会影响其他项目或插件的正常使用。
  • 学习机会:通过分析和修改插件源码,可以深入了解插件的工作原理,提高自己的技术水平。

总结

通过使用pnpm/npm/yarn打补丁的方式,我们成功优化了Element UI月份范围选择器的交互体验,使其在选择历史月份时更加直观。这种方法不仅解决了问题,而且保持了代码的可维护性,是一种优雅的解决方案。

技术要点

  1. 使用patch-package创建和应用补丁:这是一种管理依赖包修改的有效方法,能够在依赖更新后自动重新应用补丁。

  2. 调试压缩的源码:通过搜索和分析压缩的源码,找到需要修改的部分,是前端插件二次开发的必备技能。

  3. 保持向后兼容:在修改依赖包时,要确保修改不会破坏现有功能,保证代码的稳定性。

  4. 文档化修改:记录补丁的作用和修改的内容,以便后续维护和团队协作。

  5. 代码安全性:在修改源码时,要注意添加必要的安全检查,确保代码在各种情况下都能正常运行。

后续建议

  1. 提交PR:如果你的修改对其他开发者也有帮助,可以考虑向Element UI官方仓库提交PR,为开源社区贡献力量。

  2. 封装组件:可以将修改后的月份范围选择器封装成一个独立的组件,方便在项目中使用,提高代码的复用性。

  3. 监控依赖更新:定期检查Element UI的更新,确保补丁在新版本中仍然有效,及时更新补丁以适应新版本的变化。

  4. 分享经验:将你的经验分享给团队成员,提高团队的整体技术水平,促进团队协作。

通过这种方法,我们不仅解决了产品提出的交互问题,还学习了如何使用pnpm/npm/yarn打补丁来修改依赖包,这是一种非常实用的前端开发技能。希望这篇文章对你有所帮助!