原文:https://hirok.io/posts/avoid-npm-link
本文主要介绍使用npm link 的风险以及我们为什么使用npx link 来替代它
先抛结论
使用npm install 或者npx link 去软连接一个本地包作为依赖而不是使用npm link
$ npx link <package-path>
npm link是hiroki osame开发的一个更安全、更可预测的npm link 的替代品
因为如下原因我们应该避免使用npm link
-
多个 Node.js 版本同时使用容易出错 -
link 失败不会报错并且会回退到直接从 npm 仓库进行安装 -
会有预期之外的二进制可执行文件安装 -
不符合预期的软链接删除
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 link 将my-library 安装到全局。这样才能让其它本地项目有办法链接到这个包。npm link 的行为其实等同于npm install --global
$ cd ./my-library
$ npm link
在my-application 中执行npm link my-library 去链接这个包
$ cd ./my-application
$ npm link my-library
npm link <package-path>
也可以直接执行npm link <package-path> 命令实现上述两步
例子如下:
$ cd ./my-application
$ npm link ../my-library
使用npm link <package-path> 更加方便和不易出错,因为它是需要显式指定链接的包的路径
4 个使用npm link 的缺点
如果开发环境中使用类似nvm 的版本管理工具安装多个 Node.js 版本的话,需要确保npm link 的执行是在同一个 node 版本
像上文所说,第一步执行npm link 其实是将包安装全局。但是因为多个版本的 Node.js 的全局安装路径是互相独立的。如果在不同版本中使用,包查找会失败
可以使用以下命令查看全局包的安装路径。如果 Node.js 的版本出现在打印的路径中,则全局包安装路径在不同 Node.js 版本下是独立的
$ npm root -g
~/.nvm/versions/node/v14.16.1/lib/node_modules
在不同的终端中处理多个包的时候很容易忽略不同终端下的 Node.js 版本是否一致。并且这个版本差异也很难发现,因为npm link 在无法找到要链接的本地包时也不会报错
- link 失败不会报错并且会回退到直接从 npm 仓库进行安装
如果尝试在一个包中执行npm link a ,就算这个包之前并没有注册为全局链接,这个命令执行也不会报错
$ 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 这个包才会失败
$ 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.
要判断链接是否真正成功,可以检查输出是否有打印两个-> ,注意上面的错误链接只有一个-> 。两个-> 说明创建了一个指向全局包软链接,然后链接向本地包
$ 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 开始已经不可能确定是链接本地包成功还是、安装和链接了一个包
$ npm link a
up to date, audited 3 packages in 671ms
found 0 vulnerabilities
也可以使用realpath 命令验证一个包是否链接成功
$ 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 中指定了bin 的random-command
{
"name": "my-package",
"bin": {
"random-command": "bin.js"
}
}
执行npm link 就也会全局安装可执行的random-command
$ 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
$ 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 可以卸载,但它只会删除本地的软链接,而不会删除全局安装的二进制文件
卸载全局包和它的二进制执行文件需要使用:
$ npm uninstall --global a
链接多个包时,将删除先前链接的包。这个行为是 npm v7 中引入的
以下例子pkg-a 已经被链接过并且存在于node_modules 中了。但当链接了第二个包pkg-b 后,pkg-a 就不在node_modules 中了
$ 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
$ 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 的可用性很差
潜在风险
As with any popular package registry, npm has a diverse collection with no standard for quality.
作为一个流行的包管理工具,npm 有一个各种各样的包但却没有统一的质量标准
这里列举了一些恶意包,但这里提到的风险不仅限于攻击。当不清楚是否安装了正确的软件包时是有可能发生意外的
npm 上的许多包是用来更改文件的,例如rimraf或代码 linter 工具。运行文件中被更改的代码可能是有可能有问题的
npm install 也有可能安装错误的包,但是了解到上面提到的npm link 会有一些预料之外的行为时,npm link 带来的风险会更高。如下:
-
包名称可能会发生冲突。可能使用了一个 npm 仓库上已有的包名字去链接本地的包。在意识到名称已被占用之前,开发和测试新的或私有包可能会遇到 -
本地链接失败不会报错。如果被链接的包无法在本地找到,将从 npm 仓库下中查找。如果找到相同名称的包,则可能会意外地安装到全局 -
二进制可执行文件被安装。如果安装了错误的包,很难发现二进制执行文件也会被全局安装并且很难意识到需要全局卸载这个二进制执行文件。这就会留下这个不符合预期的可执行文件被可能被意外执行
使用npm install 作为替代
npm link 的一种替代方法是使用指定包路径的npm install
$ npm install --no-save <package-path-a> <package-path-b> ...
执行这个命令会创建一个指向包的软链接而不是全局安装。这种行为就和我们使用npm link 进行测试包的初衷差不多了。 加上--no-save 是为了防止包的路径保存在package.json 中
但是npm install 也是有缺点的。和npm link 一样,执行npm install 多次是会先移除之前的软链接。如果我们想一次链接多个包的话,必须一次将需要链接的多个包作为参数传入
$ npm install --no-save <package-path-a> <package-path-b> ...
npx link
npx link ,一个小工具用来替代npm link ,并且能解决上面提到的npm link 的缺点
使用起来也很简单
$ npx link <package-path>
[npm link](https://github.com/privatenumber/link "npm link") 不会全局安装链接的包或二进制执行文件,并且也不会删除以前的软链接,可以在不同版本的 Node.js 中使用。当不能解析包路径时,也会有一个执行失败报错
如果需要执行链接包的二进制文件,执行通过npx 命令或者通过package scripts
|