一、前言
广东靓仔之前修改过element的代码,然后固定本地版本。隔了这么久,广东靓仔前阵子在看修改npm包方面的内容发现一个有趣的,且听广东靓仔徐徐道来。
之前为什么会需要修改element的代码呢?原因很简单,因为组件库不满足业务需要。
同理啦,如果是Ant Design不满足我们也是可以修改的。往大的来说,所有的npm包我们其实都是可以修改的。
那么问题来了,如何改?业界其实是有很多种方案,我们一一来看。
二、一个简单的场景
修改antd组件库button
咋们先来个简单的场景:组件库里面的button不满足我们了,需要改成span(夸张点)。
第一步:找到node_modules里面的antd
路径:node_modules -> antd -> es -> button -> button.js
这里稍微补充下:
1. CommonJS模块是对象,是运行时加载,运行时才把模块挂载在exports之上。
2.ES Module不是对象,是编译时加载,使用export指定导出,再通过import引入。
我们把button修改为span即可
- var buttonNode = /*#__PURE__*/React.createElement("button", _extends({}, rest, { + var buttonNode = /*#__PURE__*/React.createElement("span", _extends({}, rest, { type: htmlType, className: classes, onClick: handleClick, ref: buttonRef }), iconNode, kids)
第二步:修改npm包,常见有4种,这里罗列对比下,推荐方法4
- 方法一:单文件修改法
使用postinstall
这个勾子,执行cp
修改过的文件 ./node_modules/antd/button.js
拷贝过去,最终node_modules
下的文件就变成了修改后的文件了,代码如下:
"scripts": { "postinstall": "cp ./patches/upload/* ./node_modules/antd/lib/" }
在每次install包后执行用修改后文件覆盖原始文件逻辑。
- 方法二:整体copy项目法
将需要修改的包的项目源码整个拷贝下来,进行修改,然后使用
- 直接引用法
直接使用完成的源码,不再通过npm包方式引用。 - 发布私库法
适合一个npm包几个项目在用的场景,可以把修改后的源码发布到私有的npm仓库上,供项目使用,这样多个项目就只需要修改一次源码 - 方法三:外部代码修改法
这个方法就是不直接修改 node_modules
的源码,而是利用js特性,在执行时,修改这个包的内部属性。
比如利用defineProperty
、prototype
等特性修改包内的类,举个不恰当的例子,如Vue2.0
中使用defineProperty
给组件实例做数据劫持和代理
。在vue项目中我们也经常在main.js
中给Vue根实例
通过Vue.prototype.xxx=xxxx
挂一些全局属性和方法。
- 方法四:patch-package
patch-package有如下特性:
- 版本试错
如果你装的包版本和你之前生成的补丁中记录的版本不一样,npx patch-package
会直接报错**ERROR** Failed to apply patch for package xxxx at path
,通过提示你可以更方便的定位问题 - 节省空间
使用git diff
来记录补丁比起重写一份源码的方法更节省空间,即安全
,又便捷
综合上面4种,最后我们采用第四种来修改npm包。step 1: 安装
yarn add antd patch-package postinstall-postinstall -D
setp 2: 在package.json文件script中添加脚本命令
"scripts": { + "postinstall": "patch-package" }
step 3: yarn patch-package antd打补丁最后会生成一个antd+4.17.5.patch的包
通过查看这个包的代码,我们发现是通过diff --git来对比不同的。
.patch
文件其实就是一些git diff
记录描述,补丁原理— patch-package
会将当前node_modules
下的源码与原始源码进行git diff
,并在项目根目录下生成一个patch
补丁文件。
把这个补丁包上传到仓库后,其他人安装依赖的时候,node_module文件对应的文件也保留了修改。
拓展一下,其他组件库,也同样可以这样子来修改,比如@alifd/next。
三、一个复杂的场景
修改antd组件库table
广东靓仔随便弄了个图,可以看出来就是在table里每条信息插入一行其他信息。这种需求确实很常见,靓仔遇到过很多这种需求。
按照前面第一个例子,思路差不多。
我们都知道antd的table内核使用的是rc-table这个库,因此我们修改node_modules里面的rc-table源代码即可。
路径:node_modules -> rc-table-> es -> Body -> BodyRow.js
在表格每行内容前插入一行信息,所以我们需要自定义一个变量来初始化插入的内容,这里不写具体代码,讲思路。
第一步: 在body的行增加一个变量"变量名"用来插入我们需要的渲染内容
第二步: 参考rc-table本身插入节点的expandRowNode,编写我们需要插入的内容
参考代码:
+ var 变量名; + + if (typeof 变量名 === 'function') { + ...... + }
第三步: 在return那里把我们新加的“变量”返回
- return /*#__PURE__*/React.createElement(React.Fragment, null, baseRowNode, expandRowNode, nestRowNode); + return /*#__PURE__*/React.createElement(React.Fragment, null, 变量, baseRowNode, expandRowNode, nestRowNode);
最后:在Table.js里面把useMemo的依赖加上我们新加的变量
路径:node_modules -> rc-table-> es -> Table.js
在BodyContextValue把我们加上“变量”
var BodyContextValue = React.useMemo(function () { ... - }, [columnContext, mergedTableLayout, rowClassName, expandedRowClassName, componentWidth, fixHeader, fixColumn, horizonScroll, mergedExpandIcon, expandableType, expandRowByClick, expandedRowRender, onTriggerExpand, expandIconColumnIndex, indentSize]); + }, [columnContext, mergedTableLayout, rowClassName, expandedRowClassName, componentWidth, fixHeader, fixColumn, horizonScroll, mergedExpandIcon, expandableType, expandRowByClick, expandedRowRender, 变量, onTriggerExpand, expandIconColumnIndex, indentSize]);
这个array 控制useMemo重新执⾏行的数组,array改变时才会 重新执行useMemo。也就是当依赖对应的值发生变化时,才会重新计算。
生成补丁包的方法跟上面那个例子是一样的,这里就不重复了。
四、最后
补丁虽然能解决一些问题,但是这其实不是很好的方案。最好呢是用官方提供的升级版本来解决问题。
最后需要注意一下,当我们升级了我们的版本后,这个patch就会失效哦~