无题
有时候我们需要修改 node_modules 下的一些代码,但是 node_modules 不会提交到 git 仓库,改动保存不下来,怎么办呢?
这时候可以用 patch-package 这个工具。
比如我对 node_modules 下的 acorn 代码做了一些修改:
加了一个 a.js 的文件:
在项目目录下执行 npx patch-package acorn 之后,就会生成这样一个目录:
在 patches 目录下的 xx.patch 文件里记录着对这个包的改动。
这个 patches 目录是可以提交到 git 仓库的,然后再次把项目拉下来的时候,执行下 npx patch-package 就会应用这次改动。
可以把它配到 postintsll 里,每次安装完依赖自动跑。
这样能保证每次拉取下来的代码都包含了对 node_modules 的改动。
如何使用我们学会了,那它是怎么实现的呢?
这节我们就来调试下 patch-package 的源码。
调试 patch-package 源码
首先把代码下载下来:
1 | git clone git@github.com:ds300/patch-package.git |
安装依赖,然后执行 npm run build,你会在 dist 目录下看到编译产物。
它默认就是有 sourcemap 的,只不过是 base64 的方式内联的:
因为它的编译配置是这样的:
接下来开始调试:
在 package.json 中可以看到 patch-package 命令的入口是 index.js:
也就是 dist/index.js 就是要调试的入口文件。
探究它的实现原理要分为两各方面,一个是 patches 文件怎么生成的,一个是 patches 文件怎么被应用的。
我们分别来看一下:
patches 文件怎么生成的
看 patches 文件的内容就能看出来这是 git 的 diff:
确实,patch-package 就是依赖 git diff 实现的 patches 文件生成。
生成这样的 patch 文件执行的是 patch-package xxx 命令,这里就是 node ./dist/index xxx
你可以先对 node_modules 下的某个包做下改动,然后执行 node ./dist/index xxx 来生成 patches 文件。
然后添加这样一个调试配置:
1 | { |
这里的 args 里填写你修改过的 node_modules 下的包名,我这里改的是 acorn 包下的代码。
看下这个命令的打印:
我们可以通过这些打印信息搜索对应的源码来打断点,比如搜索 Created file
定位到了源码,打个断点,然后 Debug 启动:
没错,这就是打印那行信息的代码。
然后我们往上看一下,你会看到这些代码:
首先 patch-package 会创建一个临时目录:
然后在这个目录写入一个 package.json 文件,dependencies 就是命令行参数指定的包名:
我们去这个目录看一下:
确实,是有这样一个 package.json 的。
然后它会在这个目录下执行 yarn install 或者 npm install(patch-package 现在不支持 pnpm):
之后就进行 git 的 init、add、commit,生成一个基础的 commit。
然后把现在 node_modules 目录下的这个被修改过的包复制过去:
之后再 git add,然后执行 git diff,就能拿到改动的 diff:
这不就是 patches 文件的内容么:
然后写到 patches 目录即可
patches 文件的生成还是挺简单的,就是在临时目录下创建了一个基础 commit,然后把新的内容复制过去,通过 git diff 生成的 patches 内容。
上面这些都是通过单步、断点调试得出的:
那应用 patches 的内容是怎么实现的呢?
patches 如何被应用的?
我又对 acorn 目录下的文件做了些修改,生成的 patches 文件是包含了增删改的:
patches 文件里记录了对哪几行做了新增,哪几行做了删除,哪几行做了修改。
如果人工应用这个 patches 文件的话,不就是找到对应文件的对应行数,做反向的操作就可以了么?
没错,patch-package 也是这样实现的,不过是自动进行的:
应用 patch 是执行 patch-package 命令,这里就是 node ./dist/index
所以添加一个这样的调试配置:
1 | { |
同样,搜索下这行打印信息:
打个断点:
debug 跑起来,
然后 step into 这个函数:
单步执行到这行代码:
再 step into 进入函数内部:
你会发现读取 patch 文件的代码,进入那个 readPatch 内部:
会发现它读取 patches 文件之后会进行 parse:
这个 parse 的实现就是对每一行的字符串做判断,进行不同的处理:
最终能得到一个包含 diff 信息的对象,包含了对什么文件的哪些行做了什么修改:
之后在 executeEffects 函数里对 patch 信息做了相应的处理:
也就是根据不同的类型做了不同的操作:
这样就把 patches 文件里的改动应用到了 node_modules 下的包里。
至此,我们通过调试 patch-package 源码理清了它的实现原理。
前面说 patch-pakckage 不支持 pnpm,其实 pnpm 内置 patch、patch-commit 命令,作用和这个 patch-package 包一样。
总结
当我们需要对 node_modules 下的代码做改动的时候,可以通过 patch-package xxx 生成 patches 文件,它可以被提交到 git 仓库,然后再拉下来的代码就可以通过 patch-package 来应用改动。
实现原理要分为两部分来看:
patches 文件的生成是在临时目录生成 package.json,下载依赖,生成一个 commit,然后把改动的代码复制过去,两者做 gif diff,就可以生成 patches 文件。
patches 文件的应用则是 patch-package 自己实现了它的 parse,拿到对什么文件的哪些行做什么修改的信息,之后根据不同做类型做不同的文件操作就可以了。
如果是 pnpm,那 patch-package 不支持,这时候用内置的 pnpm patch 命令就好了。
当然,更重要的是我们是通过自己调试源码来得出这些信息的,当你会调试源码之后,就可以自己去深入很多技术了。这也是为什么会调试源码是对工程师进阶很重要。
