scripts

npm 如何处理“scripts”字段

选择命令行版本:

描述

🌐 Description

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

前后脚本

🌐 Pre & Post Scripts

要为在 package.json"scripts" 部分定义的任何脚本创建“前置”或“后置”脚本,只需创建另一个 同名 脚本,并在其名称前加上“pre”或“post”。

🌐 To create "pre" or "post" scripts for any scripts defined in the "scripts" section of the package.json, simply create another script with a matching name and add "pre" or "post" to the beginning of them.

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

在这个例子中,npm run compress 将按照描述执行这些脚本。

🌐 In this example npm run compress would execute these scripts as described.

生命周期脚本

🌐 Life Cycle Scripts

有一些特殊的生命周期脚本只在某些情况下发生。这些脚本是除了 pre<event>post<event><event> 脚本之外发生的。

🌐 There are some special life cycle scripts that happen only in certain situations. These scripts happen in addition to the pre<event>, post<event>, and <event> scripts.

  • prepareprepublishprepublishOnlyprepackpostpackdependencies

准备(自 npm@4.0.0 起)

  • 在打包之前运行,即在 npm publishnpm pack 期间
  • 在本地 npm install 上运行,不使用软件包参数(可以使用像 --production--omit=dev 这样的标志运行,但在安装特定软件包如 npm install express 时无法运行)
  • prepublishOnlyprepack 之后运行,但在 postpack 之前
  • 如果通过 npm install <folder> 作为链接安装包,则会运行
  • 注意:如果通过 git 安装的包包含 prepare 脚本,其 dependenciesdevDependencies 将会被安装,并且在打包和安装该包之前,prepare 脚本将会运行。
  • npm@7 开始,这些脚本在后台运行。要查看输出,请使用:--foreground-scripts 运行。
  • 在工作区中,准备在所有包中并行运行的脚本。如果你有相互依赖的包,其中一个必须在另一个之前构建,可以考虑使用 --foreground-scripts(可以在 .npmrc 中通过 foreground-scripts=true 设置)来顺序运行脚本,或者采用不同的构建结构。

prepublish(已弃用)

  • npm publish期间不会运行,但在 npm cinpm install期间会运行。详细信息见下文。

仅供预发布

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

prepack

  • 在打包 tarball 之前运行(在“npm pack”、“npm publish”以及安装 git 依赖时)。
  • 注意:npm run packnpm pack 不同。npm run pack 是用户自定义的任意脚本名称,而 npm pack 是 CLI 定义的命令。

postpack

  • 在生成 tarball 之后但在它被移动到最终目的地之前运行(如果有的话,publish 不会在本地保存 tarball)

dependencies

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

准备和预发布

🌐 Prepare and Prepublish

弃用说明:prepublish

自从 npm@1.1.71 起,npm CLI 就会为 npm publishnpm install 运行 prepublish 脚本,因为这是准备一个包以供使用的方便方法(一些常见用例在下文部分中有描述)。实际上,这也被证明是 非常令人困惑 的。从 npm@4.0.0 起,引入了一个新事件 prepare,它保留了现有的行为。一个 新的 事件 prepublishOnly 被添加作为过渡策略,以允许用户避免现有 npm 版本的混乱行为,并且只在 npm publish 上运行(例如,最后运行测试以确保它们状态良好)。

🌐 Since npm@1.1.71, the npm CLI has run the prepublish script for both npm publish and npm install, because it's a convenient way to prepare a package for use (some common use cases are described in the section below). It has also turned out to be, in practice, very confusing. As of npm@4.0.0, a new event has been introduced, prepare, that preserves this existing behavior. A new event, prepublishOnly has been added as a transitional strategy to allow users to avoid the confusing behavior of existing npm versions and only run on npm publish (for instance, running the tests one last time to ensure they're in good shape).

有关此更改的更长解释以及进一步阅读,请参见 https://github.com/npm/npm/issues/10074

🌐 See https://github.com/npm/npm/issues/10074 for a much lengthier justification, with further reading, for this change.

使用案例

使用 prepare 脚本来执行与平台无关且需要在使用你的包之前运行的构建任务。这包括以下任务:

🌐 Use a prepare script to perform build tasks that are platform-independent and need to run before your package is used. This includes tasks such as:

  • 将 TypeScript 或其他源代码编译为 JavaScript。
  • 创建 JavaScript 源代码的缩小版本。
  • 获取你的包将使用的远程资源。

prepare 脚本中运行这些构建任务可以确保它们只执行一次,在一个地方完成,从而降低复杂性和可变性。此外,这意味着:

🌐 Running these build tasks in the prepare script ensures they happen once, in a single place, reducing complexity and variability. Additionally, this means that:

  • 你可以依赖像 devDependencies 这样的构建工具,因此你的用户无需安装它们。
  • 你不需要在你的包中包含缩小器,从而为你的用户减少大小。
  • 你不需要依赖用户在目标机器上拥有 curlwget 或其他系统工具。

依赖

🌐 Dependencies

dependencies 脚本会在任何 npm 命令对 node_modules 目录进行更改时运行。它在更改应用并且 package.jsonpackage-lock.json 文件已更新之后运行。

🌐 The dependencies script is run any time an npm command causes changes to the node_modules directory. It is run AFTER the changes have been applied and the package.json and package-lock.json files have been updated.

生命周期操作顺序

🌐 Life Cycle Operation Order

npm cache add

  • prepare

npm ci

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

这些都在模块实际安装到 node_modules 之后按顺序运行,中间不会发生任何内部操作

🌐 These all run after the actual installation of modules into node_modules, in order, with no internal actions happening in between

npm diff

  • prepare

npm install

当你运行 npm install -g <pkg-name> 时,这些也会运行

🌐 These also run when you run npm install -g <pkg-name>

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

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

🌐 If there is a binding.gyp file in the root of your package and you haven't defined your own install or preinstall scripts, npm will default the install command to compile using node-gyp via node-gyp rebuild

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

🌐 These are run from the scripts of <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 迭代

🌐 If there is a restart script defined, these events are run; otherwise, stop and start are both run if present, including their pre and post iterations)

  • 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 仍然可以运行。

🌐 If there is a server.js file in the root of your package, then npm will default the start command to node server.js. prestart and poststart will still run in this case.

npm stop

  • prestop
  • stop
  • poststop

npm test

  • pretest
  • test
  • posttest

npm version

  • preversion
  • version
  • postversion

关于缺少 npm uninstall 脚本的说明

🌐 A Note on a lack of npm uninstall scripts

虽然 npm v6 有 uninstall 生命周期脚本,但 npm v7 没有。卸载一个包可能有各种各样的原因,目前没有明确的方法来为脚本提供足够的上下文以使其有用。

🌐 While npm v6 had uninstall lifecycle scripts, npm v7 does not. Removal of a package can happen for a wide variety of reasons, and there's no clear way to currently give the script enough context to be useful.

删除包的原因包括:

🌐 Reasons for a package removal include:

  • 用户直接卸载了这个包
  • 用户卸载了一个依赖包,因此该依赖也被卸载了
  • 用户卸载了一个依赖包,但另一个包也依赖于此版本
  • 此版本已作为副本与另一个版本合并
  • etc.

由于缺乏必要的上下文,uninstall 生命周期脚本未被实现,且无法运行。

🌐 Due to the lack of necessary context, uninstall lifecycle scripts are not implemented and will not function.

脚本工作目录

🌐 Working Directory for Scripts

脚本总是从包文件夹的根目录运行,无论调用 npm 时的当前工作目录是什么。这意味着你的脚本可以可靠地假设它们是在包根目录中运行的。

🌐 Scripts are always run from the root of the package folder, regardless of what the current working directory is when npm is invoked. This means your scripts can reliably assume they are running in the package root.

如果你希望你的脚本根据运行 npm 时所在的目录表现不同,你可以使用 INIT_CWD 环境变量,它保存了你运行 npm run 时所在的完整路径。

🌐 If you want your script to behave differently based on the directory you were in when you ran npm, you can use the INIT_CWD environment variable, which holds the full path you were in when you ran npm run.

旧版 npm 中的历史行为

🌐 Historical Behavior in Older npm Versions

对于 npm v6 及更早版本,脚本通常从包的根目录运行,但在较旧版本中有一些罕见情况和漏洞无法保证这一点。如果你的包必须支持非常老的 npm 版本,可能需要在脚本中添加安全措施(例如,通过检查 process.cwd())。

🌐 For npm v6 and earlier, scripts were generally run from the root of the package, but there were rare cases and bugs in older versions where this was not guaranteed. If your package must support very old npm versions, you may wish to add a safeguard in your scripts (for example, by checking process.cwd()).

更多详情,请参阅:

🌐 For more details, see:

用户

🌐 User

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

🌐 When npm is run as root, scripts are always run with the effective uid and gid of the working directory owner.

环境

🌐 Environment

包脚本在一个环境中运行,其中提供了许多关于 npm 设置和进程当前状态的信息。

🌐 Package scripts run in an environment where many pieces of information are made available regarding the setup of npm and the current state of the process.

path

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

🌐 If you depend on modules that define executable scripts, like test suites, then those executables will be added to the PATH for executing the scripts. So, if your package.json has this:

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

然后你可以运行 npm start 来执行 bar 脚本,该脚本被导出到 npm install 上的 node_modules/.bin 目录中。

🌐 then you could run npm start to execute the bar script, which is exported into the node_modules/.bin directory on npm install.

package.json 变量

🌐 package.json vars

npm 从 package.json 设置以下环境变量:

🌐 npm sets the following environment variables from the package.json:

  • npm_package_name - 软件包名称
  • npm_package_version - 软件包版本
  • npm_package_bin_* - bin 字段中定义的每个可执行文件
  • npm_package_engines_* - engines 字段中定义的每个引擎
  • npm_package_config_* - config 字段中定义的每个配置值
  • npm_package_json - package.json 文件的完整路径

此外,对于安装脚本(preinstallinstallpostinstall),npm 会设置以下环境变量:

🌐 Additionally, for install scripts (preinstall, install, postinstall), npm sets these environment variables:

  • npm_package_resolved - 软件包的已解析 URL
  • npm_package_integrity - 软件包的完整性哈希值
  • npm_package_optional - 如果软件包是可选的,请设置为 "true"
  • npm_package_dev - 如果该包是开发依赖,则设置为 "true"
  • npm_package_peer - 如果该包是同伴依赖,则设置为 "true"
  • npm_package_dev_optional - 如果该软件包既是开发依赖又是可选依赖,则设置为 "true"

例如,如果你的 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 访问这些变量。

🌐 For example, if you had {"name":"foo", "version":"1.2.5"} in your package.json file, then your package scripts would have the npm_package_name environment variable set to "foo", and the npm_package_version set to "1.2.5". You can access these variables in your code with process.env.npm_package_name and process.env.npm_package_version.

注意: 在 npm 7 及更高版本中,大多数 package.json 字段不再作为环境变量提供。需要访问其他 package.json 字段的脚本应直接读取 package.json 文件。npm_package_json 环境变量提供了该文件的路径以供此用途。

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

🌐 See package.json for more on package configs.

当前生命周期事件

🌐 current lifecycle event

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

🌐 Lastly, the npm_lifecycle_event environment variable is set to whichever stage of the cycle is being executed. So, you could have a single script used for different parts of the process which switches based on what's currently happening.

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

🌐 Objects are flattened following this format, so if you had {"scripts":{"install":"foo.js"}} in your package.json, then you'd see this in the script:

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

示例

🌐 Examples

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

🌐 For example, if your package.json contains this:

{
"scripts": {
"prepare": "scripts/build.js",
"test": "scripts/test.js"
}
}

然后 scripts/build.js 将在生命周期的准备阶段被调用,如果你的脚本需要在不同的环境下表现不同,你可以检查 npm_lifecycle_event 环境变量。

🌐 then scripts/build.js will be called for the prepare stage of the lifecycle, and you can check the npm_lifecycle_event environment variable if your script needs to behave differently in different contexts.

如果你想运行构建命令,你可以这样做。这完全可以正常运行:

🌐 If you want to run build commands, you can do so. This works just fine:

{
"scripts": {
"prepare": "npm run build",
"build": "tsc",
"test": "jest"
}
}

退出

🌐 Exiting

在 POSIX 系统上,可以通过将该行作为脚本参数传递给 /bin/sh 来运行脚本,在 Windows 上则传递给 cmd.exe。你可以通过设置 script-shell 配置选项来控制使用哪个 shell。

🌐 Scripts are run by passing the line as a script argument to /bin/sh on POSIX systems or cmd.exe on Windows. You can control which shell is used by setting the script-shell configuration option.

如果脚本以 0 以外的代码退出,那么这将中止该过程。

🌐 If the script exits with a code other than 0, then this will abort the process.

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

🌐 Note that these script files don't have to be Node.js or even JavaScript programs. They just have to be some kind of executable file.

最佳实践

🌐 Best Practices

  • 除非你确实有这个意思,否则不要以非零错误码退出。如果失败只是轻微的,或者只是会影响一些可选功能,那么最好只打印警告并正常退出。
  • 尽量不要通过脚本去做 npm 可以帮你完成的事情。浏览 package.json 可以查看你通过适当描述你的包就能指定和启用的所有功能。总体而言,这将带来更稳健和一致的状态。
  • 检查环境以确定放置位置。例如,如果 NPM_CONFIG_BINROOT 环境变量被设置为 /home/user/bin,那么就不要尝试将可执行文件安装到 /usr/local/bin。用户很可能出于某种原因这样设置。
  • 不要在你的脚本命令前加上“sudo”。如果出于某些原因需要 root 权限,那么会出现那个错误,而用户会使用 sudo 来运行对应的 npm 命令。
  • 不要使用 install。编译使用 .gyp 文件,其他用途使用 prepare。你几乎不需要显式设置预安装或安装脚本。如果你正在这样做,请考虑是否有其他选项。唯一合理使用 installpreinstall 脚本的情况是必须在目标架构上进行的编译。

也可以看看

🌐 See Also