4个避免使用npm link的理由
flytam Lv4

本文主要介绍使用npm link的风险以及我们为什么使用npx link来替代它

先抛结论

使用npm install或者npx link去软连接一个本地包作为依赖而不是使用npm link

1
$ npx link <package-path>

npm linkhiroki osame开发的一个更安全、更可预测的npm link的替代品

因为如下原因我们应该避免使用npm link

  1. 多个 Node.js 版本同时使用容易出错

  2. link 失败不会报错并且会回退到直接从 npm 仓库进行安装

  3. 会有预期之外的二进制可执行文件安装

  4. 不符合预期的软链接删除

npm link是什么

npm link是一个用于开发时直接将本地包链接为依赖项的一个命令行工具。通常用于发布 npm 包之前本地测试使用

更多信息可查看官方文档

使用

假设存在以下模块包

  • my-library: npm 包,需要在其它项目中作为依赖进行测试

需要注意的是这里my-library/package.json中的name属性也是应该my-library

  • my-application: package/my-application需要进行测试的项目

下面演示如何将my-application中的my-library链接到本地

  • 全局安装

my-application中执行npm linkmy-library安装到全局。这样才能让其它本地项目有办法链接到这个包。npm link的行为其实等同于npm install --global

1
2
$ cd ./my-library
$ npm link
  • 安装

my-application中执行npm link my-library去链接这个包

1
2
$ cd ./my-application
$ npm link my-library

也可以直接执行npm link <package-path>命令实现上述两步

例子如下:

1
2
$ cd ./my-application
$ npm link ../my-library

使用npm link <package-path>更加方便和不易出错,因为它是需要显式指定链接的包的路径

4 个使用npm link的缺点

  • 多个 Node.js 版本同时使用容易出错

如果开发环境中使用类似nvm的版本管理工具安装多个 Node.js 版本的话,需要确保npm link的执行是在同一个 node 版本

像上文所说,第一步执行npm link其实是将包安装全局。但是因为多个版本的 Node.js 的全局安装路径是互相独立的。如果在不同版本中使用,包查找会失败

可以使用以下命令查看全局包的安装路径。如果 Node.js 的版本出现在打印的路径中,则全局包安装路径在不同 Node.js 版本下是独立的

1
2
$ npm root -g
~/.nvm/versions/node/v14.16.1/lib/node_modules

在不同的终端中处理多个包的时候很容易忽略不同终端下的 Node.js 版本是否一致。并且这个版本差异也很难发现,因为npm link在无法找到要链接的本地包时也不会报错

  • link 失败不会报错并且会回退到直接从 npm 仓库进行安装

如果尝试在一个包中执行npm link a ,就算这个包之前并没有注册为全局链接,这个命令执行也不会报错

1
2
$ npm link a
~/my-package/node_modules/a -> ~/.nvm/versions/node/v14.16.1/lib/node_modules/a

这是因为npm link的时候没有找到全局的包a,它就会从npm仓库上去全局安装这个包并创建一个软链接到这个包

只有这个包在 npm 远端仓库上没有这个包,npm link这个包才会失败

1
2
3
4
5
6
7
8
9
$ npm link non-existent-package
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/non-existent-package - Not found
npm ERR! 404
npm ERR! 404 'non-existent-package@*' is not in this registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

要判断链接是否真正成功,可以检查输出是否有打印两个->,注意上面的错误链接只有一个->。两个->说明创建了一个指向全局包软链接,然后链接向本地包

1
2
3
# npm v6
$ npm link my-linked-package
~/my-package/node_modules/my-linked-package -> ~/.nvm/versions/node/v14.16.1/lib/node_modules/my-linked-package -> ~/my-linked-package

这种检查方法只能在 npm v6 版本中使用。从 npm v7 开始,链接路径不再被输出到终端了。从下面可以发现 v7 开始已经不可能确定是链接本地包成功还是、安装和链接了一个包

1
2
3
4
5
6
# npm v7
$ npm link a

up to date, audited 3 packages in 671ms

found 0 vulnerabilities

也可以使用realpath命令验证一个包是否链接成功

1
2
$ realpath node_modules/package-name
~/my-linked-package

综上,由于缺少适当的报错,使用npm link带给我们不是很好的体验。特别是在多个 Node.js 版本的情况下

  • 会有预期之外的二进制可执行文件安装

npm link的第一步是将包安装到全局。这个命令是由两步实现

npm install –global …可用于使二进制文件作为系统范围的 cli 命令使用。如果包有bin 字段,通过npm link可以另这个bin中的命令可以直接通过终端执行命令

考虑到npm link通常只是用来在开发中进行包的测试,全局二进制文件的安装可能会有额外的副作用。由于包可以声明具有任意名称的二进制执行文件,这种意外的副作用的影响可能非常严重

下面的例子,在package.json中指定了binrandom-command

1
2
3
4
5
6
{
"name": "my-package",
"bin": {
"random-command": "bin.js"
}
}

执行npm link就也会全局安装可执行的random-command

1
2
3
4
5
6
7
8
9
10
$ random-command
zsh: command not found: random-command

$ cd my-package && npm link
added 1 package, and audited 3 packages in 548ms

found 0 vulnerabilities

$ random-command
Suddenly works!

全局安装也会覆盖已经存在的的全局可执行命令(取决于PATH配置–终端查找命令的环境变量)。如果使用nvm则可能会受到影响

下面的例子,覆盖了标准的 Unix 命令cat

1
2
3
4
5
6
7
8
9
10
11
$ type cat
cat is /bin/cat

$ cd my-package && npm link
added 1 package, and audited 3 packages in 230ms

found 0 vulnerabilities

$ hash cash
$ type cat
cat is ~/.nvm/versions/node/v16.14.0/bin/cat

在包安装方面,这些风险对于包管理很普遍,从安全角度来看,这些风险并不算太高

npm link本身不是一个包安装工具。它是一个开发时进行软链接的工具。通过上文,我们了解到这种行为是会导致不少预期外的行为以及可能导致的一些错误

顺便提下上面提到的运行npm link a,则二进制执行命令a已安装到系统中。可能会认为 npm unlink a可以卸载,但它只会删除本地的软链接,而不会删除全局安装的二进制文件

卸载全局包和它的二进制执行文件需要使用:

1
$ npm uninstall --global a
  • 不符合预期的软链接删除

链接多个包时,将删除先前链接的包。这个行为是 npm v7 中引入的

以下例子pkg-a已经被链接过并且存在于node_modules中了。但当链接了第二个包pkg-b后,pkg-a就不在node_modules中了

1
2
3
4
5
6
7
8
9
10
11
12
13
$ npm link ../pkg-a
added 1 package, and audited 5 packages in 684ms
found 0 vulnerabilities

$ ls node_modules
pkg-a

$ npm link ../pkg-b
added 1 package, removed 1 package, and audited 5 packages in 703ms
found 0 vulnerabilities

$ ls node_modules
pkg-b

使用多个包进行链接时,npn link删除之前的链接包通常是不符合预期的。一般在链接第二个包之后,我们会继续运行代码并认为之前的软链接是应该不变的

如果要链接多个包就必须将所有包路径一次传递给npm link

1
2
3
4
5
6
$ npm link ../pkg-a ../pkg-b
added 1 package, and audited 6 packages in 645ms
found 0 vulnerabilities

$ ls node_modules
pkg-a pkg-b

虽然可行但这并不是一个很好的开发体验。在开发中,我们并不总是提前知道所有需要链接的包或以前链接过的包

这种令人困惑的行为说明了npm link的可用性很差

潜在风险

作为一个流行的包管理工具,npm 有一个各种各样的包但却没有统一的质量标准

这里列举了一些恶意包,但这里提到的风险不仅限于攻击。当不清楚是否安装了正确的软件包时是有可能发生意外的

npm 上的许多包是用来更改文件的,例如rimraf或代码 linter 工具。运行文件中被更改的代码可能是有可能有问题的

npm install也有可能安装错误的包,但是了解到上面提到的npm link会有一些预料之外的行为时,npm link带来的风险会更高。如下:

  • 包名称可能会发生冲突。可能使用了一个 npm 仓库上已有的包名字去链接本地的包。在意识到名称已被占用之前,开发和测试新的或私有包可能会遇到

  • 本地链接失败不会报错。如果被链接的包无法在本地找到,将从 npm 仓库下中查找。如果找到相同名称的包,则可能会意外地安装到全局

  • 二进制可执行文件被安装。如果安装了错误的包,很难发现二进制执行文件也会被全局安装并且很难意识到需要全局卸载这个二进制执行文件。这就会留下这个不符合预期的可执行文件被可能被意外执行

使用npm install作为替代

npm link的一种替代方法是使用指定包路径的npm install

1
$ npm install --no-save <package-path-a> <package-path-b> ...

执行这个命令会创建一个指向包的软链接而不是全局安装。这种行为就和我们使用npm link进行测试包的初衷差不多了。 加上--no-save是为了防止包的路径保存在package.json

但是npm install也是有缺点的。和npm link一样,执行npm install多次是会先移除之前的软链接。如果我们想一次链接多个包的话,必须一次将需要链接的多个包作为参数传入

1
$ npm install --no-save <package-path-a> <package-path-b> ...

npx link,一个小工具用来替代npm link,并且能解决上面提到的npm link的缺点

使用起来也很简单

1
$ npx link <package-path>

[npm link](https://github.com/privatenumber/link "npm link")不会全局安装链接的包或二进制执行文件,并且也不会删除以前的软链接,可以在不同版本的 Node.js 中使用。当不能解析包路径时,也会有一个执行失败报错

如果需要执行链接包的二进制文件,执行通过npx命令或者通过package scripts

  • Post title:4个避免使用npm link的理由
  • Post author:flytam
  • Create time:2022-04-30 11:14:15
  • Post link:https://blog.flytam.vip/4个避免使用npm link的理由.html
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.