scripts

npm 如何处理“scripts”字段

选择命令行版本:

描述

🌐 Description

package.json 文件的 "scripts" 属性支持多种内置脚本及其预设生命周期事件,同时也支持任意脚本。这些都可以通过运行 npm run-script <stage>(或简称 npm run <stage>)来执行。具有相同名称的 prepost 命令也会随之执行(例如 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 上运行,无需任何参数
  • prepublish 之后运行,但在 prepublishOnly 之前
  • 注意:如果通过 git 安装的包包含 prepare 脚本,其 dependenciesdevDependencies 将会被安装,并且在打包和安装该包之前,prepare 脚本将会运行。
  • npm@7 开始,这些脚本在后台运行。要查看输出,请使用:--foreground-scripts 运行。

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.

使用案例

如果你需要在使用你的软件包之前执行某些操作,并且这些操作不依赖于目标系统的操作系统或架构,请使用 prepublish 脚本。这包括以下任务:

🌐 If you need to perform operations on your package before it is used, in a way that is not dependent on the operating system or architecture of the target system, use a prepublish script. This includes tasks such as:

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

prepublish 时间做这些事情的优点是它们可以一次性在一个地方完成,从而减少复杂性和修改性。此外,这意味着:

🌐 The advantage of doing these things at prepublish time is that they can be done once, in a single place, thus reducing complexity and variability. Additionally, this means that:

  • 你可以依赖 coffee-script 作为 devDependency,因此你的用户不需要安装它。
  • 你不需要在你的包中包含缩小器,从而为你的用户减少大小。
  • 你不需要依赖用户在目标机器上拥有 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

prepare--dry-run 期间不会运行

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.

用户

🌐 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

package.json 字段都附加在 npm_package_ 前缀上。例如,如果你的 package.json 文件中有 {"name":"foo", "version":"1.2.5"},那么你的 package 脚本将会设置 npm_package_name 环境变量为 "foo",并且 npm_package_version 设置为 "1.2.5"。你可以在代码中通过 process.env.npm_package_nameprocess.env.npm_package_version 访问这些变量,其他字段也是类似的方式。

🌐 The package.json fields are tacked onto the npm_package_ prefix. So, for instance, 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, and so on for other fields.

有关包配置的更多信息,请参见 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": {
"install": "scripts/install.js",
"postinstall": "scripts/install.js",
"uninstall": "scripts/uninstall.js"
}
}

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

🌐 then scripts/install.js will be called for the install and post-install stages of the lifecycle, and scripts/uninstall.js will be called when the package is uninstalled. Since scripts/install.js is running for two different phases, it would be wise in this case to look at the npm_lifecycle_event environment variable.

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

🌐 If you want to run a make command, you can do so. This works just fine:

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

退出

🌐 Exiting

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

🌐 Scripts are run by passing the line as a script argument to sh.

如果脚本以 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 操作失败,并可能被回滚。如果失败是轻微的,或者只会影响一些可选功能,那么最好只是打印警告并成功退出。
  • 尽量不要通过脚本去做 npm 可以为你完成的事情。浏览 package.json 可以查看你只需通过适当描述你的包就能指定和启用的所有内容。总体而言,这将使状态更加稳健和一致。
  • 检查环境以确定放置位置。例如,如果 npm_config_binroot 环境变量被设置为 /home/user/bin,那么就不要尝试将可执行文件安装到 /usr/local/bin。用户很可能出于某种原因这样设置。
  • 不要在你的脚本命令前加上“sudo”。如果出于某些原因需要 root 权限,那么会出现那个错误,而用户会使用 sudo 来运行对应的 npm 命令。
  • 不要使用 install。编译使用 .gyp 文件,其他用途使用 prepare。你几乎不需要显式设置预安装或安装脚本。如果你正在这样做,请考虑是否有其他选项。唯一合理使用 installpreinstall 脚本的情况是必须在目标架构上进行的编译。
  • 无论调用 npm 时当前工作目录是哪里,脚本都是从包文件夹的根目录运行的。如果你希望你的脚本根据所在的子目录使用不同的行为,可以使用 INIT_CWD 环境变量,它保存了你运行 npm run 时所在的完整路径。

也可以看看

🌐 See Also