你真的理解 npm install 的构建依赖管理吗?

664 阅读7分钟

npm作为Node.js的默认包管理器,帮助开发者处理项目中的依赖管理问题。这使得我们能够轻松地复用别人的代码,并且保证这些代码能够在我们的项目中正确运行。然而,随着项目规模的增大,依赖管理可能会变得复杂且充满挑战。很多开发对于工程化也不怎么关心,对于 npm 的理解很模糊,毕竟业务天天写,工程不是天天搭,文本通过通过一系列的问题,帮助大家理解一下 npm 的各种行为。

项目中依赖 2个包,他们对于 node 的版本的要求不一样怎么办?

在安装某些npm包时,你可能会收到一个警告信息,说这个包需要一个不同版本的Node.js。这是因为这个包在它的package.json文件中指定了一个engines字段,这个字段定义了这个包兼容的Node.js版本范围。如果你的Node.js版本不在这个范围内,npm会在安装这个包时显示一个警告信息。

这时可以和作者沟通是否有升级低版本 node 的计划,如果他不升的话,你除了换个包替代使用外,就只能修改源码了。所以挑选依赖时候一定选择更新频繁,人多,有保障!

项目中依赖 2个包,他们依赖了同一个包,但是依赖的版本不同怎么办?

这种情况下,npm 会尝试安装满足所有依赖版本要求的最新版本。如果不存在这样的版本,npm 会为每个包安装各自满足版本要求的依赖,这可能会导致一个包的多个版本被安装。

这么做可以确保每个包能够使用正确的依赖版本,但是导致复杂的 nodejs 项目体积变得很大。

为什么 npm install 时候有些包会去 Github 下载而不是 npm 托管?

在package.json文件中,有很多种形式来加载依赖包,比如直接写版本号,或者通过 URL,或者通过别名,标签。在npm构建过程中,npm可能会去GitHub下载依赖包,这主要是因为在package.json文件中的依赖可能直接指定了GitHub地址。例如,你可能会看到这样的依赖声明:

{
  "dependencies": {
    "your-package": "git://github.com/username/repository.git#branch"
  }
}

在这个例子中,your-package的源码存放在GitHub的一个仓库中,而不是在npm的公共仓库。因此,当你运行npm install时,npm会直接从GitHub上下载这个包。不仅是自己的项目,引用的第三方包他们的依赖可能也会指定GitHub地址,在安装这些包时,npm也会去GitHub下载依赖。

为什么 npm install 时候有些包会进行编译?

有些包是纯JS实现的,但是有些包是依赖了C++代码或者是依赖了预编译出的二进制可执行文件,这些代码都与平台有关,因此 npm 会使用 node-pre-gyp 来编译源码,以适合当前操作系统的版本。具体来说,node-pre-gyp 会在安装过程中首先尝试下载预编译的二进制版本。如果预编译的二进制版本可用,并且与当前系统兼容,那么 node-pre-gyp 就会直接下载和安装,这样可以大大加快安装速度,同时避免了在目标系统上进行编译所需的各种依赖问题(这个过程需要在系统上安装编译工具,比如Python和编译器)

所以下次看到在 npm install 时遇到这种错误不要慌

npm ERR! command sh -c node-pre-gyp install --fallback-to-build

出现这个错说明预编译的二进制版本不可用,或者与当前系统不兼容, node-pre-gyp 会退回到从源代码编译。这个过程需要在系统上安装编译工具,比如Python和编译器。这种需要编译的包 在package.json 种都会有一个 binary字段,比如下面这种

"binary": { 
    "module_name": "bcrypt_lib", 
    "module_path": "./lib/binding/napi-v3/", 
    "host": "<https://github.com/kelektiv/node.bcrypt.js>" 
}

以上面这个为例,在安装 bcrypt 模块时,npm会尝试从 github.com/kelektiv/no… 下载名为 bcrypt_lib 的二进制文件,并将其放在 ./lib/binding/napi-v3/ 目录下。如果无法下载预编译的二进制文件,npm会尝试从源代码编译,纯 JS 包是不会有 binary 这个字段的。

为什么 npm 装的依赖会自动更新?

npm使用一种叫做“语义版本控制”(SemVer)的系统来管理依赖的版本。在package.json文件中,每个依赖都有一个版本范围,这个范围定义了可以安装的版本。这种方式允许开发者在不破坏项目的情况下,自动接收依赖的小更新和修复。

举一个常见的例子吧,第三方包会持续暴出漏洞,作者知道后会修复并且打补丁,那我们怎么更新这些安全补丁呢?如果你希望仅更新补丁版本,你可以使用前缀(例如1.2.3),这样下次更新时就会修复这些漏洞。

npm 因网络连通性原因安装失败怎么办?

如果你的网络不能连接 github,相信安装包时最不想看到的就是 ETIMEOUT 这种错误信息。第一种方案最简单,如果你有可以直接设置代理即可 npm config set proxy <http://proxy-server-url>:port。但是如果你没有代理呢?你可以看下你这个包的作者是不是提供了变量来让你指定安装源,比如 node-sass 这个包, 它支持几个变量,你可以通过 SASS_BINARY_SITE 放一个国内的镜像站点,也可以提前下好放本地,然后通过 SASS_BINARY_DIR 指定本地路径来解决在构建时网络不通的问题。

Variable name.npmrc parameterProcess argumentValue
SASS_BINARY_NAMEsass_binary_name--sass-binary-namepath
SASS_BINARY_SITEsass_binary_site--sass-binary-siteURL
SASS_BINARY_PATHsass_binary_path--sass-binary-pathpath
SASS_BINARY_DIRsass_binary_dir--sass-binary-dirpath

但是如果第三方包没有提供渠道给你改依赖源,除了认命了之外,最后的挣扎方式是本地先下好依赖,写个 preinstall 脚本,将下好的文件拷贝到 node_modules 里。 但是这个方式很痛,尤其是你的开发环境和线上环境操作系统不一致,提前下好的这个包如果是线上环境,会影响你本地调试。

我想锁定依赖,确保每次构建的一致性该怎么办?

如果你希望锁定项目的依赖版本,避免自动更新,可以使用npm shrinkwrap命令。这个命令会创建一个npm-shrinkwrap.json文件,列出了项目所有依赖的精确版本。当你运行npm install时,npm会优先考虑npm-shrinkwrap.json文件,而不是package.json文件。

此外,npm也提供了package-lock.json文件,这个文件在每次运行npm install时自动生成或更新。package-lock.json文件和npm-shrinkwrap.json文件类似,都会记录项目的依赖树及其精确版本,但package-lock.json文件不会被发布到npm,这意味着它主要用于控制开发环境的依赖版本。

如果node_modules 里有了包A,那么 npm install时候 包A会被重新安装吗?

npm install命令默认会安装package.json文件中列出的所有依赖包。但是,如果node_modules目录中已经存在某个依赖包,并且它的版本符合package.json文件中的版本范围,那么npm就不会重新安装这个包。

总的来说,理解npm的依赖管理是每一个Node.js开发者的必备技能。希望通过本文的探讨,能够帮助你解决在实际开发中可能遇到的问题,并优化你的开发流程。每一个项目都是独特的,没有一种方法能够适用于所有情况。因此,理解各种工具的工作原理和优缺点,以及如何根据你的具体需求选择和配置这些工具,是至关重要的。