在开发会被其他项目依赖的 npm 包时,有时需要在其他项目下安装测试。为了方便测试,我们通常会将包本地安装或者发布到 npm 仓库下再安装。但是实际上前者会有很多 npm 处理依赖的坑,而后者即便可以用 canary
通道,某些精神洁癖的同学还是会不太乐意。
那么我们可以换种思路,为何我们不在本地自建一个 npm registry?这样可以随意发布测试,不会踩到本地安装 npm 的坑,还不会影响到线上包。Verdaccio 就是一个随开随用甚至无需配置的 npm 仓库,用户不需要自己搭建数据库,只需要一行指令即可在本地启动一个 npm 仓库。
太长不看版
本地安装的坑
或许你会好奇,npm 是支持本地安装包的,原理就是在 node_modules
下添加一个 symlink,指向对应的目录:
同时刷新 package.json
的依赖:
这样安装在一般情况下是没问题的,node 在运行时能找到对应的包。问题在于这样的操作只是添加一个 symlink 而已,而这个包里的依赖不一定会同步复制到当前的 node_modules
下。如果这个包里的依赖只是在这个包用上的话,一般不需要考虑这个问题。但是如果这个包下的依赖同时是项目下的其他依赖需要的依赖,那么就会遇到找不到依赖的问题。
具体到实际的例子,我们正在维护一份包含 ESLint 等 lint 规则的包,这个包会包含 lint 规则所需的所有依赖(如 eslint-plugin-react
、eslint-plugin-import
等),这样用户不需要手动安装这些依赖。然而由于本地安装不会处理本地包所使用的依赖,也就是不会复制到 node_modules
目录下,而这些依赖是被项目的 ESLint 等依赖使用的,最终会导致 ESLint 等会找不到对应的插件。
这种情况的解决方法只能是手动将对应的依赖复制到 node_modules
下,但是一个个手动复制未免也过于繁琐了。所幸已经有人开发了 npm-install-offline
这样的包来解决这种问题:
它的原理就是将项目所需要的上游依赖包复制到当前项目的 node_modules
下,这样项目在运行时就能找到这些依赖了。但是这个包在应对 lerna 这类 monorepo 时会有意想不到的 bug,lerna 在 bootstrap 后会将关联的包在 node_modules
下用 symlink 连接起来,而这个包无法处理 symlink,导致复制对应的依赖时会卡住。虽然这种情况下 npm-install-offline
还可以指定 --symlink
参数也使用 symlink 连接:
这种情况下依赖就能顺利链接到 node_modules
下了,一般也能解决不少问题。然而对于之前提到的实际中的例子,包里的依赖其实不仅仅是被项目依赖如 ESLint 调用的,同时自身也需要调用项目中的依赖,例如 eslint-plugin-import
就需要 require('eslint')
。然而 symlink 的依赖在寻找上游依赖时可能就犯了难,会抛出 Cannot find module 'eslint'
的错误,盲猜是依赖执行时并不是 symlink 后的项目所在的 node_modules
下寻找,而是在原来的目录下寻找,最终导致找不到依赖。
总之 npm 处理本地包的依赖大概就是一坨💩,如果你的包也是这样复杂,建议尽早放弃本地安装的思路,否则可能还会有更多的坑。
自建 npm 仓库
既然本地安装目录不靠谱,那么 npm pack
呢?不好意思,npm pack
并不会打包 node_modules
,安装后依赖还是从远程仓库上拉的,对于 monorepo 并不友好。所以此时更好的方法应该是直接发包并安装测试,可是这样有可能会影响已经发布的包版本。那么我们何不自己搭建一个 npm 仓库?
自建 npm 仓库是可行的,最常见的方案就是 cnpm,而自己搭建 cnpm 则还需要准备数据库等环境依赖。那么有更快更简单的方法吗?当然有,比如这次要用上的 Verdaccio,它是一个开源轻量免配置的 npm 私服,用户不需要准备本地的使用环境,只需要安装即可使用:
启动后 Verdaccio 会默认运行在 127.0.0.1:4873
上,你可以打开浏览器确认:
此时我们只需要将 npm 的 registry 指向本地,就可以开始操作了。
首先我们需要登录本地仓库的 registry,用户名密码其实可以随意:
然后我们就可以将包发布到本地的仓库下了:
如果不出意外,你可以在刚才的 web 页面上看到刚刚发布的包了。
在其他项目下测试时,只需要在安装时指定 registry 即可:
这样你就在本地搭建了一个运行在本地的 npm 仓库,现在你可以随意折腾你的包并随意发布了,本地发布到 Verdaccio 的包是不会发布到上游的。
进阶配置
Verdaccio 的配置文件默认放在用户目录下,一般是取 $XDG_DATA_HOME
环境变量,Windows 是取 %APPDATA%
环境变量。此外在启动 Verdaccio 时也会输出配置文件的位置,个人实践的配置文件位置如下:
开放公网访问
Verdaccio 默认是绑定在 127.0.0.1:4873
下的,如果需要绑定到公网 IP 可以改为监听 0.0.0.0
,此外也可以修改端口。
自动代理上游的包
如果你是采用全局配置 npm registry 的话,如果使用了公司内部的包,那么本地安装上游的包时可能会遇到找不到依赖的问题,因为 Verdaccio 虽然默认会代理上游 npmjs 的包,但是私有仓库是不支持的。此时你需要在安装原始项目依赖时用默认的 registry,然后安装测试的依赖时指定 registry。其实我们可以通过配置上行链路让 Verdaccio 自动查找上游的包,这样就只需要将 registry 指向本地即可。
Verdaccio 的默认上行链路配置如下:
我们可以在 uplinks
里指定上行链路,然后在 packages
里指定对应的 scope 走对应的链路。例如我们可以将公司的私有 scope @foo
指向公司的 cnpm,然后默认的依赖走淘宝的中转,配置可以写成这样:
然后我们可以在需要测试的项目下删除原有的 .npmrc
,然后安装依赖时直接使用本地的 Verdaccio,或者直接将 .npmrc
指向本地的 Verdaccio,它会帮我们代理好上游的包,不再需要来回切换 registry 了: