如何优雅的修改第三方包

官方介绍

patch-package 给开发者提供了通过打“补丁”的方式,使得重新安装依赖包时能够保留之前对第三方依赖包的修改的一种解决方案。

应用场景

我们在使用第三方依赖包时如果遇到了 bug,通常解决的方式都是绕过这个问题,使用其他方式解决,较为麻烦。或者给作者提个 issue 或者 PR,然后等待作者的修复。等待的时间不可控,此时就可以借助 patch-package 自己动手去修复该 bug,感觉是不是很棒。并且还可以在第三方依赖包上,根据业务需求扩展能力。

最好还是扩展一些通用性比较高的能力,如果是比较通用且该能力大多数开发者都有这种诉求的话可以给第三方依赖包提个 PR。

使用方法

安装

都是在项目里自行安装

npm

npm i patch-package

yarn

yarn add patch-package postinstall-postinstall

为什么 yarn 要添加 postinstall-postinstall 包

yarn 在 yarn、yarn install 和 yarn add <package> 之后运行 postinstall 脚本,但在 yarn remove <package> 之后不运行。如果您将此包添加到您的项目中,即使在 yarn remove <package> 之后,它也会执行您项目的 postinstall 钩子。这需要你的 postinstall 脚本是幂等的,因为它会为 yarn、yarn install 和 yarn add <package> 运行两次

修改 package.json

依赖包在安装完之后会执行 postinstall 命令

"scripts": {
    ***,
+   "postinstall": "patch-package"
}

修改依赖包源码

生成补丁

yarn patch-package package-name(修改的包名)
# 或者
npx patch-package package-name(npm版本 > 5.2)

执行npx patch-package antd结果

npx patch-package antd
Need to install the following packages:
  patch-package
Ok to proceed? (y) y
patch-package 6.4.7
• Creating temporary folder
• Installing antd@4.17.3 with yarn
• Diffing your files with clean files
✔ Created file patches/antd+4.17.3.patch

💡 antd is on GitHub! To draft an issue based on your patch run

    yarn patch-package antd --create-issue

可以看到patch-package已经为我们创建了一个补丁。

默认会在我们的根目录下创建一个patches文件夹。在patches文件夹下会创建依赖包名+版本号.patch的文件,文件描述了我们修改了什么,第几行,有点像git的提交记录。

diff --git a/node_modules/antd/lib/button/button.js b/node_modules/antd/lib/button/button.js
index da5fc9a..7eec125 100644
--- a/node_modules/antd/lib/button/button.js
+++ b/node_modules/antd/lib/button/button.js
@@ -143,40 +143,40 @@ var InternalButton = function InternalButton(props, ref) {
   var _classNames;
 
   var _props$loading = props.loading,
-      loading = _props$loading === void 0 ? false : _props$loading,
-      customizePrefixCls = props.prefixCls,
-      type = props.type,
-      danger = props.danger,
-      _props$shape = props.shape,
-      shape = _props$shape === void 0 ? 'default' : _props$shape,
-      customizeSize = props.size,
-      className = props.className,
-      children = props.children,
-      icon = props.icon,
-      _props$ghost = props.ghost,
-      ghost = _props$ghost === void 0 ? false : _props$ghost,
-      _props$block = props.block,
-      block = _props$block === void 0 ? false : _props$block,
-      _props$htmlType = props.htmlType,
-      htmlType = _props$htmlType === void 0 ? 'button' : _props$htmlType,
-      rest = __rest(props, ["loading", "prefixCls", "type", "danger", "shape", "size", "className", "children", "icon", "ghost", "block", "htmlType"]);
+    loading = _props$loading === void 0 ? false : _props$loading,
+    customizePrefixCls = props.prefixCls,
+    type = props.type,
+    danger = props.danger,
+    _props$shape = props.shape,
+    shape = _props$shape === void 0 ? 'default' : _props$shape,
+    customizeSize = props.size,
+    className = props.className,
+    children = props.children,
+    icon = props.icon,
+    _props$ghost = props.ghost,
+    ghost = _props$ghost === void 0 ? false : _props$ghost,
+    _props$block = props.block,
+    block = _props$block === void 0 ? false : _props$block,
+    _props$htmlType = props.htmlType,
+    htmlType = _props$htmlType === void 0 ? 'button' : _props$htmlType,
+    rest = __rest(props, ["loading", "prefixCls", "type", "danger", "shape", "size", "className", "children", "icon", "ghost", "block", "htmlType"]);
 
   var size = React.useContext(_SizeContext["default"]);
 
   var _React$useState = React.useState(!!loading),
-      _React$useState2 = (0, _slicedToArray2["default"])(_React$useState, 2),
-      innerLoading = _React$useState2[0],
-      setLoading = _React$useState2[1];
+    _React$useState2 = (0, _slicedToArray2["default"])(_React$useState, 2),
+    innerLoading = _React$useState2[0],
+    setLoading = _React$useState2[1];
 
   var _React$useState3 = React.useState(false),
-      _React$useState4 = (0, _slicedToArray2["default"])(_React$useState3, 2),
-      hasTwoCNChar = _React$useState4[0],
-      setHasTwoCNChar = _React$useState4[1];
+    _React$useState4 = (0, _slicedToArray2["default"])(_React$useState3, 2),
+    hasTwoCNChar = _React$useState4[0],
+    setHasTwoCNChar = _React$useState4[1];
 
   var _React$useContext = React.useContext(_configProvider.ConfigContext),
-      getPrefixCls = _React$useContext.getPrefixCls,
-      autoInsertSpaceInButton = _React$useContext.autoInsertSpaceInButton,
-      direction = _React$useContext.direction;
+    getPrefixCls = _React$useContext.getPrefixCls,
+    autoInsertSpaceInButton = _React$useContext.autoInsertSpaceInButton,
+    direction = _React$useContext.direction;
 
   var buttonRef = ref || /*#__PURE__*/React.createRef();
   var delayTimeoutRef = React.useRef();
@@ -218,10 +218,11 @@ var InternalButton = function InternalButton(props, ref) {
   React.useEffect(fixTwoCNChar, [buttonRef]);
 
   var handleClick = function handleClick(e) {
+    console.log(234567) // 我的修改
     var _a;
 
     var onClick = props.onClick,
-        disabled = props.disabled; // https://github.com/ant-design/ant-design/issues/30207
+      disabled = props.disabled; // https://github.com/ant-design/ant-design/issues/30207
 
     if (innerLoading || disabled) {
       e.preventDefault();

测试补丁是否有效

  • 手动删除node_modules文件夹,重新执行yarn安装依赖包。可以看到在依赖包安装结束后执行了patch-package命令,之前生成的补丁被应用了。因为我们配置了postinstall脚本,所以会自动执行patch-package命令
$ patch-package
patch-package 6.4.7
Applying patches...
antd@4.17.3 ✔
✨  Done in 3.32s.
  • 查看node-modules中之前修改的antd修改的地方,查看之前修改的代码是否还存在。如果之前修改的代码还存在,说明补丁文件已经生效了,如果不存在,排查下是否哪个步骤出现了问题。

antd/lib/button/button.js

  var handleClick = function handleClick(e) {
    console.log(234567) // Ops! 存在
    var _a;

    var onClick = props.onClick,
      disabled = props.disabled; // https://github.com/ant-design/ant-design/issues/30207

    if (innerLoading || disabled) {
      e.preventDefault();
      return;
    }

    (_a = onClick) === null || _a === void 0 ? void 0 : _a(e);
  };

最后将patches文件夹推送到远端仓库,日后无论是谁拉取代码,安装依赖,我们之前修改的部分都会生效的

注意事项

  1. patch是锁定版本号的,如果升级了版本,patch内容将会失效,最好在package.json能够锁定版本号。

  2. 修改的同时,也局限了升级的能力,尽量还是去提issue和PR。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容