相关文章

之前提过两种方式,详见:
ChatGPT 教我如何修改 node_modules 里的代码 - Jeff Tian的文章 - 知乎
https://zhuanlan.zhihu.com/p/642937928

今天再重新梳理一下,并隆重推荐我新学会的一种方式,那就是使用 patch-package。为什么会有修改 node_modules 的情况呢?因为不想重新造轮子,但是现有的轮子又不太好用,就不得不基于不太好用的轮子,做一些修改和适配。

一、发私包

这是最简单直接的,如果直接依赖一个包,而需要对这个包进行修改,那么可以将 package.json 里的 name 修改一个名字,做了其他修改之后,直接 yarn publish一个新的,然后在项目中引入这个新的包即可。

案例一:vitepress

代码改动见:https://github.com/vuejs/vitepress/commit/b35de817436d57dee84cc369bb28b7ae1bef0493
最初发现 vitepress 是一个令人惊艳的静态网站生成器,但是使用时,发现不支持嵌套包含。虽然给官方提了 PR,并且最终很快得到了合并。但我当时想立即使用嵌套包含的功能,所以就发了一个自己的私包,直接使用了,从而不用等待官方的发版。

案例二:umi-plugin-oauth2

https://github.com/Jeff-Tian/umi-plugin-oauth2
因为工作中有几个项目都是基于 umijs 开发,并且需要对接身份认证平台,为了省事,就需要一个 umi 插件来做这件事儿。从网上找到了一个 umi-plugin-oauth2-client,但是实际使用下来有一些问题。于是我就基于它做了一些修改,也给原作者提了 PR,但是一直没有回音,可能是没有维护了。为了不用等待,就直接将修改后的版本,改了个名字发到 npm registry,其他项目都直接引用这个被修改过的版本。
通过发私包,不仅可以解决自己的问题,还能让别人受益,这就很有趣了。
image.png

二、postinstall 大法

发私包虽然好,但并不是银弹。假如项目不是直接依赖该库,而是需要修改依赖链条上很深的一个依赖,那么通过发私包的方式就不可行了。如果非要用发私包的方式的话,就得将依赖链条上的每一个库都发一个私包,并且对所有涉及到的库的依赖进行连环修改,工作量非常大,不值得。
这时候就可以通过 postinstall 脚本来对最根本的依赖直接做修改。

案例:vuejs/core

我在使用 vitepress 的过程中,发现了一个问题(https://github.com/vuejs/vitepress/issues/2596):如果 markdown 里引用了一个文件名中带有空格的图片,开发模式下发现不了问题。
image.png

但是在上生产时却会出现编译失败。

image.png

这让人很沮丧,对问题跟踪了一番,发现竟然是一个存在了 2 年多的 vuejs/core 的问题导致的。由于非常底层,所以不适合通过发私包来解决。比如我发一个 vuejs-core-2 来修复这个问题,就还得再发一个 vuejs-2,还得再发一个 vuepress-2,如此下去。
于是我想到了使用 postinstall,通过亲测验证,效果非常好。我将这个方案分享到了 https://github.com/vuejs/vitepress/issues/573#issuecomment-1629971757
即在项目中创建 postinstall.js 文件: javascript const fs = require(fs);

const filePath = node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js;

fs.readFile(filePath, utf8, (err, data) => { if (err) { console.error(err); return; }

const modifiedData = data.replace(
    /context.imports.push({s*exp,s*path:s*path2s*});/g,
    context.imports.push({ exp, path: path2 && path2[0] === / ? decodeURIComponent(path2) : path2 });
);

fs.writeFile(filePath, modifiedData, utf8, (err) => {
    if (err) {
        console.error(err);
        return;
    }

    console.log(File modified successfully!);
});

});

并在 package.json 里增加 postinstall 命令: diff

  • postinstall: node postinstall.js
    

尽管我同时也提交了一个 PR 给到 vuejs/core,并且最终被尤雨溪大佬亲自合并了这个 PR,并且最终在 3.3.6 版本中带上了这个修复,但是这个版本要求 esmodule,而我的项目中还没有这个改造计划,所以直到如今,我还在使用上述的 postinstall 方案,实在是非常香。

三、patch-package

虽然 postinstall 大法非常好用,并且 patch-package 从根本上也是使用了 postinstall 方法,但是我仍然强烈推荐使用它来进行 node_modules 修改!
我们再来仔细审视一下 postinstall 方案:首先,需要在本地的 node_modules 目录下找到目标库,并且进行实际的修改和验证。在验证通过后,然后就得将手动定位、修改的过程,用代码写出来。这非常啰嗦,并且不能规模化。
在上面的案例中,它之所以香,是因为只改动了一个文件的一个位置,如果需要改动多个地方,在手动改完后,还需要记住这些位置,并且再将手动改完的步骤用代码表达一遍,相当于每个改动得做 2 次,并且很容易在第 2 次用代码表达时出现错误。
而使用 patch-package,则只需要手动修改目标文件,再执行 yarn patch-package target-package-1 target-package-2就可以了!
并且,在后续要向目标库提交 PR 或者提 Issue 时,可以直接使用 yarn patch-package target-package --create-issue的方式,省心!

案例

最近用它解决了一个非常大的问题,可惜目前还在私有库里,不便公开展示。不如留给读者朋友们,在后续使用了它之后留言分享更多的案例吧!

总结

发私包和朴素的 postinstall 方案,没有其他依赖,简单直接,但不是以一种工程化的方式来完成的。而 patch-package 则是一种工程化、科学的对 node_modules 里的包做修改的优雅方式,推荐你在碰到需要修改 node_modules 里的包时采用。