安装和运行Node.js bin脚本

1,287 阅读5分钟

package.json 属性"bin" 让一个npm包指定它提供哪些shell脚本(更多信息,见"用Node.js为Unix和Windows创建基于ESM的shell脚本")。如果我们安装了这样的包,Node.js会确保我们可以从命令行访问这些shell脚本(所谓的bin脚本)。在这篇博文中,我们将探讨两种用bin脚本安装软件包的方法。

  • 本地安装带有 bin 脚本的软件包意味着将其作为一个软件包的依赖项来安装。脚本只能在该包内访问。

  • 全局安装带有 bin 脚本的软件包是指将其安装在一个 "全局位置",这样脚本就可以在任何地方被访问--无论是当前用户还是系统的所有用户(取决于 npm 的设置方式)。

我们将探讨这一切意味着什么,以及在安装后如何运行bin脚本。

全局安装npm注册表包

cowsay,其属性如下:package.json

"bin": {
  "cowsay": "./cli.js",
  "cowthink": "./cli.js"
},

要在全球范围内安装这个包,我们使用npm install -g

npm install -g cowsay

注意事项:在Unix上,我们可能要使用sudo (我们很快就会知道如何避免这种情况)。

sudo npm install -g cowsay

之后,我们可以在命令行中使用cowsaycowthink 命令。

注意,只有bin脚本是全局可用的。当Node.js在node_modules 目录中查找裸模块指定符时,这些包会被忽略。

哪些包是全局安装的? npm ls -g

我们可以检查哪些包是全局安装的,以及安装在哪里。

% npm ls -g
/usr/local/lib
├── corepack@0.12.1
├── cowsay@1.5.0
└── npm@8.15.0

在Windows上,安装路径是%AppData%\npm ,例如。

>echo %AppData%\npm
C:\Users\jane\AppData\Roaming\npm

全局安装的软件包在哪里? npm root -g

macOS上的结果。

% npm root -g
/usr/local/lib/node_modules

Windows上的结果。

>npm root -g
C:\Users\jane\AppData\Roaming\npm\node_modules

全局性的shell脚本安装在哪里? npm bin -g

npm bin -g 告诉我们npm把shell脚本安装在全局的什么地方。它还确保该目录在shell的PATH中是可用的。

macOS上的结果。

% npm bin -g
/usr/local/bin

% which cowsay
/usr/local/bin/cowsay

在Windows命令壳上的结果。

>npm bin -g
C:\Users\jane\AppData\Roaming\npm

>where cowsay
C:\Users\jane\AppData\Roaming\npm\cowsay
C:\Users\jane\AppData\Roaming\npm\cowsay.cmd

没有文件名扩展的可执行cowsay ,用于基于Unix的Windows环境,如Cygwin、MinGW和MSYS。

Windows PowerShell为gcm cowsay ,返回这个路径。

C:\Users\jane\AppData\Roaming\npm\cowsay.ps1

包在全球范围内安装在哪里?npm的安装前缀

npm的安装前缀决定了包和bin脚本在全球范围内的安装位置。

这就是macOS上的安装前缀。

% npm config get prefix
/usr/local

相应地。

  • 软件包被安装在/usr/local/lib/node_modules
  • bin 脚本安装在/usr/local/bin

这是Windows上的安装前缀。

>npm config get prefix
C:\Users\jane\AppData\Roaming\npm

相应地。

  • 软件包被安装在C:\Users\jane\AppData\Roaming\npm\node_modules
  • Bin 脚本安装在C:\Users\jane\AppData\Roaming\npm

改变软件包的全局安装位置

在这一节中,我们将研究两种改变软件包全局安装位置的方法。

  • 改变npm的安装前缀
  • 使用 Node.js 版本管理器

改变npm的安装前缀

改变软件包的全局安装位置的一种方法是改变npm的安装前缀。

Unix。

mkdir ~/npm-global
npm config set prefix '~/npm-global'

Windows Command shell。

mkdir "%UserProfile%\npm-global"
npm config set prefix "%UserProfile%\npm-global"

Windows PowerShell。

mkdir "$env:UserProfile\npm-global"
npm config set prefix "$env:UserProfile\npm-global"

配置数据被保存到主目录下的一个文件.npmrc

从现在开始,全局安装将被添加到我们刚才指定的目录中。

之后,我们仍然要把npm bin -g 目录添加到我们的shell PATH中,这样我们的shell才能找到我们全局安装的bin脚本。

**改变npm前缀的一个坏处是:**如果我们告诉它自己升级,npm现在也会被安装在新的位置。

使用Node.js版本管理器

Node.js版本管理器可以让我们同时安装多个Node.js版本并在它们之间切换。流行的有:。

在本地安装npm注册表包

在本地安装一个npm注册表包,如cowsay (放入一个包中),我们要做以下工作。

cd my-package/
npm install cowsay

这将在package.json 中添加以下数据。

"dependencies": {
  "cowsay": "^1.5.0",
  ···
}

此外,该包会被下载到以下目录中。

my-package/node_modules/cowsay/

在Unix上,npm为bin脚本添加这些符号链接。

my-package/node_modules/.bin/cowsay -> ../cowsay/cli.js
my-package/node_modules/.bin/cowthink -> ../cowsay/cli.js

在Windows上,npm将这些文件添加到my-package\node_modules\.bin\

cowsay
cowsay.cmd
cowsay.ps1
cowthink
cowthink.cmd
cowthink.ps1

没有扩展名的文件是基于Unix的Windows环境的脚本,如Cygwin、MinGW和MSYS。

npm bin 告诉我们本地安装的bin脚本的位置--例如。

% npm bin
/Users/john/my-package/node_modules/.bin

注意:在本地,软件包总是安装在一个目录node_modules ,旁边是一个package.json 文件。如果后者在当前目录中不存在,npm会在一个祖先目录中搜索它并在那里安装包。要检查npm会在本地安装软件包的位置,我们可以使用命令npm root - 例如(Unix)。

% cd $HOME
% npm root
/Users/john/node_modules

在John的主目录中没有package.json ,但npm不能在祖先目录中安装任何东西,这就是为什么npm root 显示这个目录。在当前位置本地安装一个包,会导致package.json 被创建,并且安装工作照常进行。

运行本地安装的bin脚本

(本小节中的所有命令都是在目录my-package 内执行的。)

直接运行bin脚本

我们可以在shell中运行cowsay ,如下所示。

./node_modules/.bin/cowsay Hello

在Unix上,我们可以设置一个帮助器。

alias npm-exec='PATH=$(npm bin):$PATH'

那么下面的命令就可以起作用了。

npm-exec cowsay Hello

通过包脚本运行bin脚本

我们还可以在package.json 中添加一个包脚本。

{
  ···
  "scripts": {
    "cowsay": "cowsay"
  },
  ···
}

现在我们可以在shell中执行这个命令。

npm run cowsay Hello

这就可以了,因为在Unix上npm会临时添加以下条目到$PATH

/Users/john/my-package/node_modules/.bin
/Users/john/node_modules/.bin
/Users/node_modules/.bin
/node_modules/.bin

在Windows上,类似的条目被添加到%Path%$env:Path

C:\Users\jane\my-package\node_modules\.bin
C:\Users\jane\node_modules\.bin
C:\Users\node_modules\.bin
C:\node_modules\.bin

下面的命令列出了在包脚本运行时存在的环境变量和它们的值。

npm run env

通过npx运行bin脚本

在一个软件包中,npx可以用来访问bin脚本。

npx cowsay Hello
npx cowthink Hello

以后会有更多关于npx的内容。

安装未发布的软件包

有时,我们有一个还没有发布或永远不会发布的软件包,想安装它。

npm link:在全局范围内安装一个未发布的包

让我们假设我们有一个未发布的软件包,它的名字是@my-scope/unpublished-package ,存放在一个目录下/tmp/unpublished-package/ 。我们可以让它在全局范围内可用,方法如下。

cd /tmp/unpublished-package/
npm link

如果我们这样做。

  • npm为全局的node_modules (由npm root -g 返回)添加一个符号链接--例如。

    /usr/local/lib/node_modules/@my-scope/unpublished-package
    -> ../../../../../tmp/unpublished-package
    
  • 在Unix上,npm也会在每个bin脚本中添加一个来自全局bin目录的符号链接(如由npm bin -g 返回)。这个链接不是直接的,它要经过全局的node_modules 目录。

    /usr/local/bin/my-command
    -> ../lib/node_modules/@my-scope/unpublished-package/src/my-command.js
    
  • 在Windows上,它添加了通常的3个脚本(这些脚本通过相对路径引用链接的软件包到全局node_modules )。

    C:\Users\jane\AppData\Roaming\npm\my-command
    C:\Users\jane\AppData\Roaming\npm\my-command.cmd
    C:\Users\jane\AppData\Roaming\npm\my-command.ps1
    

由于链接的包是如何被提及的,它的任何变化都会立即生效。当它发生变化时,没有必要重新链接它。

为了检查全局安装是否成功,我们可以使用npm ls -g 来列出所有全局安装的软件包。

npm link:在本地安装一个全局链接的软件包

当我们在全局安装了我们的upublished包(见上一小节)后,我们可以选择在本地将其安装在我们的一个包中(可以是已发布的或未发布的)。

cd /tmp/other-package/
npm link @my-scope/unpublished-package

这就创建了以下链接。

/tmp/other-package/node_modules/@my-scope/unpublished-package
-> ../../../unpublished-package

默认情况下,未发布的包不会被添加为package.json 的依赖关系。这背后的原理是,npm link 经常被用来暂时与一个未发布的注册表包版本一起工作--它不应该出现在依赖关系中。

npm link:撤消链接

撤销本地链接。

cd /tmp/other-package/
npm uninstall @my-scope/unpublished-package

撤销全局链接。

cd /tmp/unpublished-package/
npm uninstall -g

通过本地路径安装未发布的包

另一种在本地安装未发布软件包的方法,是使用npm install 并通过本地路径(而不是通过其软件包名称)来引用它。

cd /tmp/other-package/
npm install ../unpublished-package

这有两个效果。

首先,下面的符号链接被创建。

/tmp/other-package/node_modules/@my-scope/unpublished-package
-> ../../../unpublished-package

第二,一个依赖关系被添加到package.json

"dependencies": {
  "@my-scope/unpublished-package": "file:../unpublished-package",
  ···
}

这种安装未发布软件包的方式也适用于全球。

cd /tmp/unpublished-package/
npm install -g .

安装未发布包的其他方式

  • Yalc允许我们将软件包发布到本地的 "Yalc仓库"(想想本地注册表)。从该仓库中,我们可以安装软件包作为依赖,例如,一个软件包my-package/ 。它们被复制到目录my-package/.yalcfile:link: 的依赖关系被添加到package.json

  • relative-deps支持"relativeDependencies" ,在package.json ,这(如果它们存在)覆盖了正常的依赖关系。与npm link 和本地路径安装相反。

    • 正常的依赖关系不需要被改变。
    • 相对依赖关系的安装就像它们来自npm注册表一样(不是通过符号链接)。

    relative-deps 这也有助于保持本地安装的相对依赖项和它们的原件同步。

  • npx link是一个更安全的npm link ,不需要全局安装,还有其他好处。

npx运行npm包中的bin脚本而不安装它们

npx是一个用于运行bin脚本的shell命令,与npm捆绑在一起。

它最常见的用法是。

npx  arg1 arg2 ...

这个命令在npx缓存中安装名称为package-name 的包,并运行与该包同名的bin脚本--比如说。

npx cowsay Hello

这意味着我们可以在不安装bin脚本的情况下运行它们。npx对bin脚本的一次性调用最有用--例如,许多框架为设置新项目提供了bin脚本,这些脚本经常通过npx运行。

在npx第一次使用一个软件包后,它在其缓存中是可用的,随后的调用会快得多。然而,我们不能确定一个软件包在缓存中停留了多长时间。因此,npx并不能替代全局或局部安装bin脚本的做法。

如果一个软件包附带的bin脚本的名称与它的软件包名称不同,我们可以像这样访问它们。

npx --package=  arg1 arg2 ...

比如说。

npx --package=cowsay cowthink Hello

npx 缓存

npx的缓存在哪里?

在Unix上,我们可以通过下面的命令找到它。

npx --package=cowsay node -p \
  "process.env.PATH.split(':').find(p => p.includes('_npx'))"

这将返回一个类似于这个的路径。

/Users/john/.npm/_npx/8f497369b2d6166e/node_modules/.bin

在Windows上,我们可以用(一行分成两行)。

npx --package=cowsay node -p
  "process.env.Path.split(';').find(p => p.includes('_npx'))"

它返回的路径与此相似(单行路径分成两行)。

C:\Users\jane\AppData\Local\npm-cache\_npx\
  8f497369b2d6166e\node_modules\.bin

注意,npx的缓存与npm为其安装的模块使用的缓存不同。

  • Unix。

    • npm cache。$HOME/.npm/_cacache/
    • npx cache。$HOME/.npm/_npx/
  • Windows(PowerShell)。

    • npm cache:$env:UserProfile\AppData\Local\npm-cache\_npx\
    • npx cache。$env:UserProfile\AppData\Local\npm-cache\_cacache\

两个缓存的父目录可以通过确定。

npm config get cache

关于npm缓存的更多信息,请参见npm文档

与npx缓存不同的是,数据永远不会从npm缓存中删除,只是添加。我们可以在Unix上按如下方式检查其大小。

du -sh $(npm config get cache)/_cacache/

以及在Windows PowerShell上。

DiskUsage /d:0 "$(npm config get cache)\_cacache"

进一步阅读

这篇博文是关于Node.js shell脚本系列的一部分。

你可能还对以下内容感兴趣 我即将出版的书 "Shell scripting with Node.js"。.