脚本

npm 如何处理 "scripts" 字段

选择 CLI 版本

描述

package.json 文件的 "scripts" 属性支持许多内置脚本及其预设生命周期事件,以及任意脚本。所有这些都可以通过运行 npm run-script <stage> 或简写为 npm run <stage> 来执行。具有匹配名称的 前置后置 命令也将为其运行(例如 premyscriptmyscriptpostmyscript)。依赖项中的脚本可以使用 npm explore <pkg> -- npm run <stage> 运行。

前置和后置脚本

要为 package.json"scripts" 部分中定义的任何脚本创建 "pre" 或 "post" 脚本,只需创建另一个脚本(使用匹配的名称),并在它们开头添加 "pre" 或 "post"。

{
"scripts": {
"precompress": "{{ executes BEFORE the `compress` script }}",
"compress": "{{ run command to compress files }}",
"postcompress": "{{ executes AFTER `compress` script }}"
}
}

在此示例中,npm run compress 将按所述执行这些脚本。

生命周期脚本

有一些特殊的生命周期脚本,它们只在特定情况下发生。这些脚本除了 pre<event>post<event><event> 脚本之外还会发生。

  • prepareprepublishprepublishOnlyprepackpostpackdependencies

prepare (从 [email protected] 开始)

  • 在打包之前运行,即在 npm publishnpm pack 期间运行

  • 在本地 npm install 上运行,没有任何参数

  • prepublish 之后,但在 prepublishOnly 之前运行

  • 注意:如果通过 git 安装的包包含 prepare 脚本,则在打包和安装该包之前,将安装其 dependenciesdevDependencies,并且将运行 prepare 脚本。

  • npm@7 开始,这些脚本在后台运行。要查看输出,请使用以下命令运行:--foreground-scripts

prepublish (已弃用)

  • npm publish 期间不运行,但在 npm cinpm install 期间运行。有关更多信息,请参见下文。

prepublishOnly

  • 在准备和打包包之前运行,仅在 npm publish 上运行。

prepack

  • 在打包 tarball 之前运行(在 "npm pack"、"npm publish" 和安装 git 依赖项时)。
  • 注意:"npm run pack" 与 "npm pack" 不相同。"npm run pack" 是一个任意的用户定义脚本名称,而 "npm pack" 是一个 CLI 定义的命令。

postpack

  • 在生成 tarball 后,但在将其移动到最终目标位置之前运行(如果存在,则 publish 不会在本地保存 tarball)。

dependencies

  • 在任何修改 node_modules 目录的操作之后运行,前提是发生了更改。
  • 在全局模式下不运行

准备和预发布

弃用说明:prepublish

[email protected] 开始,npm CLI 针对 npm publishnpm install 都运行了 prepublish 脚本,因为它是一种方便的方式来准备包以供使用(以下部分描述了一些常见的用例)。在实践中,它也已被证明是 非常令人困惑的。从 [email protected] 开始,引入了一个新的事件 prepare,它保留了此现有行为。添加了一个新的 事件 prepublishOnly 作为一种过渡策略,允许用户避免现有 npm 版本的混乱行为,并且仅在 npm publish 上运行(例如,最后一次运行测试以确保它们处于良好状态)。

有关此更改的更详细的理由,以及进一步的阅读,请参见 https://github.com/npm/npm/issues/10074

用例

如果你需要在你的包被使用之前对它进行操作,并且这种操作不依赖于目标系统的操作系统或架构,可以使用 prepublish 脚本。这包括以下任务:

  • 将 CoffeeScript 源代码编译成 JavaScript。
  • 创建 JavaScript 源代码的压缩版本。
  • 获取你的包将使用的远程资源。

prepublish 时间进行这些操作的优势在于,它们可以一次性在同一个地方完成,从而减少复杂性和可变性。此外,这意味着

  • 你可以将 coffee-script 作为 devDependency,因此你的用户不需要安装它。
  • 你不需要将压缩器包含在你的包中,从而减小了你的用户需要下载的大小。
  • 你不需要依赖于你的用户在目标机器上安装 curlwget 或其他系统工具。

依赖项

当任何 npm 命令导致 node_modules 目录发生变化时,都会运行 dependencies 脚本。它在更改应用之后以及 package.jsonpackage-lock.json 文件更新后运行。

生命周期操作顺序

npm cache add

  • prepare

npm ci

  • preinstall
  • install
  • postinstall
  • prepublish
  • preprepare
  • prepare
  • postprepare

这些脚本都在模块实际安装到 node_modules 目录之后按顺序运行,并且脚本之间没有内部操作。

npm diff

  • prepare

npm install

这些脚本在运行 npm install -g <pkg-name> 时也会运行。

  • preinstall
  • install
  • postinstall
  • prepublish
  • preprepare
  • prepare
  • postprepare

如果你的包的根目录下存在 binding.gyp 文件,并且你没有定义自己的 installpreinstall 脚本,npm 会将 install 命令默认为使用 node-gyp 通过 node-gyp rebuild 进行编译。

这些脚本从 <pkg-name> 的脚本中运行。

npm pack

  • prepack
  • prepare
  • postpack

npm publish

  • prepublishOnly
  • prepack
  • prepare
  • postpack
  • publish
  • postpublish

npm rebuild

  • preinstall
  • install
  • postinstall
  • prepare

prepare 脚本仅在当前目录是符号链接(例如,使用链接的包)时运行。

npm restart

如果定义了 restart 脚本,就会运行这些事件,否则如果存在 stopstart 脚本,就会运行这两个脚本,包括它们的 prepost 迭代。

  • prerestart
  • restart
  • postrestart

npm run <user defined>

  • pre<user-defined>
  • <user-defined>
  • post<user-defined>

npm start

  • prestart
  • start
  • poststart

如果你的包的根目录下存在 server.js 文件,那么 npm 会将 start 命令默认为 node server.js。在这种情况下,prestartpoststart 仍然会运行。

npm stop

  • prestop
  • stop
  • poststop

npm test

  • pretest
  • test
  • posttest

npm version

  • preversion
  • version
  • postversion

关于缺少 npm uninstall 脚本的说明

虽然 npm v6 有 uninstall 生命周期脚本,但 npm v7 没有。删除包的原因有很多,目前还没有明确的方法可以为脚本提供足够的上下文信息以使其有用。

删除包的原因包括

  • 用户直接卸载了这个包
  • 用户卸载了一个依赖包,因此这个依赖项被卸载了
  • 用户卸载了一个依赖包,但另一个包也依赖于这个版本
  • 这个版本与另一个版本合并为重复版本
  • 等等。

由于缺少必要的上下文信息,uninstall 生命周期脚本没有实现,并且不会起作用。

用户

当 npm 以 root 身份运行时,脚本总是以工作目录所有者的有效 uid 和 gid 运行。

环境

包脚本在许多信息可用于了解 npm 设置和当前进程状态的环境中运行。

路径

如果你依赖于定义可执行脚本的模块,比如测试套件,那么这些可执行文件将被添加到执行脚本的 PATH 中。因此,如果你的 package.json 具有以下内容

{
"name": "foo",
"dependencies": {
"bar": "0.1.x"
},
"scripts": {
"start": "bar ./test"
}
}

那么你可以运行 npm start 来执行 bar 脚本,该脚本在 npm install 时被导出到 node_modules/.bin 目录中。

package.json 变量

package.json 字段被附加到 npm_package_ 前缀。例如,如果你的 package.json 文件中包含 {"name":"foo", "version":"1.2.5"},那么你的包脚本将拥有名为 npm_package_name 的环境变量,其值为 "foo",而名为 npm_package_version 的环境变量其值为 "1.2.5"。你可以使用 process.env.npm_package_nameprocess.env.npm_package_version 在你的代码中访问这些变量,其他字段也以此类推。

有关包配置的更多信息,请参阅 package.json

当前生命周期事件

最后,npm_lifecycle_event 环境变量被设置为正在执行的周期的哪个阶段。因此,你可以在进程的不同部分使用同一个脚本,根据当前发生的事情进行切换。

对象按照以下格式展开,因此如果你在你的 package.json 中有 {"scripts":{"install":"foo.js"}},那么你将在脚本中看到以下内容

process.env.npm_package_scripts_install === "foo.js"

示例

例如,如果你的 package.json 包含以下内容

{
"scripts": {
"install": "scripts/install.js",
"postinstall": "scripts/install.js"
}
}

那么 scripts/install.js 将在生命周期的安装和安装后阶段被调用。由于 scripts/install.js 正在运行两个不同的阶段,因此在这种情况下,明智的做法是查看 npm_lifecycle_event 环境变量。

如果你想运行一个 make 命令,你可以这样做。这是有效的

{
"scripts": {
"preinstall": "./configure",
"install": "make && make install",
"test": "make test"
}
}

退出

脚本是通过将行作为脚本参数传递给 sh 来运行的。

如果脚本以非 0 代码退出,那么这将中止进程。

请注意,这些脚本文件不必是 Node.js 或甚至 JavaScript 程序。它们只需要是某种可执行文件。

最佳实践

  • 除非你真的有必要,否则不要以非零错误代码退出。如果失败是轻微的或只会阻止一些可选功能,那么最好只打印警告并成功退出。
  • 尝试不要使用脚本来做 npm 可以为你做的事情。阅读 package.json 以查看所有你可以通过简单地适当地描述你的包来指定和启用的功能。总的来说,这将导致更健壮和一致的状态。
  • 检查 env 以确定将东西放在哪里。例如,如果 npm_config_binroot 环境变量被设置为 /home/user/bin,那么不要尝试将可执行文件安装到 /usr/local/bin。用户很可能出于某种原因将其设置成这样。
  • 不要在你的脚本命令前加上 "sudo"。如果出于某种原因需要 root 权限,那么它将以该错误失败,用户将对要执行的 npm 命令使用 sudo。
  • 不要使用 install。使用 .gyp 文件进行编译,使用 prepare 进行其他操作。你几乎永远不需要显式设置 preinstall 或 install 脚本。如果你正在这样做,请考虑是否有其他选项。使用 installpreinstall 脚本的唯一有效用途是编译,编译必须在目标架构上完成。
  • 脚本从包文件夹的根目录运行,与调用 npm 时当前工作目录是什么无关。如果你希望你的脚本根据你所在的子目录使用不同的行为,可以使用 INIT_CWD 环境变量,该变量保存你运行 npm run 时的完整路径。

另请参阅

在 GitHub 上编辑此页面
4 位贡献者rveerdjpg619ericmuttalukekarrys
最后一次编辑者:rveerd 2024年2月27日