1. npm install 安装机制

image.png

2. 公共依赖的问题

构建依赖树时,当前依赖项目不管其是直接依赖还是子依赖的依赖,都应该按照扁平化原则,优先将其放置在 node_modules 根目录(最新版本 npm 规范)。在这个过程中,遇到相同模块就判断已放置在依赖树中的模块版本是否符合新模块的版本范围,如果符合则跳过;不符合则在当前模块的 node_modules 下放置该模块(最新版本 npm 规范)。

3. npm 缓存

前端工程中,依赖嵌套依赖,一个中型项目中 node_moduels 安装包可能就已经是海量的了。如果安装包每次都通过网络下载获取,无疑会增加安装时间成本。对于这个问题,缓存始终是一个好的解决思路,我们接下来看看 npm 自己的缓存机制。

对于一个依赖包的同一版本进行本地化缓存,是当代依赖包管理工具的一个常见设计。使用时要先执行以下命令:

  1. npm config get cache

得到配置缓存的根目录在 /Users/wangyang/.npm( Mac OS 中,npm 默认的缓存位置) 当中。我们 cd 进入 /Users/wangyang/.npm 中可以发现_cacache文件。事实上,在 npm v5 版本之后,缓存数据均放在根目录中的_cacache文件夹中。
image.png

我们可以使用以下命令清除 /Users/wangyang/.npm/_cacache 中的文件:

  1. npm cache clean --force

接下来打开_cacache文件,看看 npm 缓存了哪些东西,一共有 3 个目录:

  • content-v2

  • index-v5

  • tmp

其中 content-v2 里面基本都是一些二进制文件。为了使这些二进制文件可读,我们把二进制文件的扩展名改为 .tgz,然后进行解压,得到的结果其实就是我们的 npm 包资源。
image.png

而 index-v5 文件中,我们采用跟刚刚一样的操作就可以获得一些描述性的文件,事实上这些内容就是 content-v2 里文件的索引。

这些缓存如何被储存并被利用的呢?

这就和 npm install 机制联系在了一起。当 npm install 执行时,通过pacote把相应的包解压在对应的 node_modules 下面。npm 在下载依赖时,先下载到缓存当中,再解压到项目 node_modules 下。pacote 依赖npm-registry-fetch来下载包,npm-registry-fetch 可以通过设置 cache 属性,在给定的路径下根据IETF RFC 7234生成缓存数据。

接着,在每次安装资源时,根据 package-lock.json 中存储的 integrity、version、name 信息生成一个唯一的 key,这个 key 能够对应到 index-v5 目录下的缓存记录。如果发现有缓存资源,就会找到 tar 包的 hash,根据 hash 再去找缓存的 tar 包,并再次通过pacote把对应的二进制文件解压到相应的项目 node_modules 下面,省去了网络下载资源的开销。

注意,这里提到的缓存策略是从 npm v5 版本开始的。在 npm v5 版本之前,每个缓存的模块在 ~/.npm 文件夹中以模块名的形式直接存储,储存结构是:{cache}/{name}/{version}。
**

4. 利用 npm link,高效率在本地调试以验证包的可用性

当我们开发一个公共包时,总会有这样的困扰:假如我开发一个组件库,某个组件开发完成之后,如何验证该组件能在我的业务项目中正常运行呢?

除了写一个完备的测试以外,常见的思路就是在组件库开发中,设计 examples 目录或者一个 playground,启动一个开发服务,以验证组件的运行情况。

然而真实应用场景是多种多样的,如果能在某个项目中率先尝试就太好了。但我们又不能发布一个不安全的包版本供业务项目使用。另一个“笨”方法是,手动复制粘贴组件并打包产出到业务项目的 node_modules 中进行验证,但是这种做法既不安全也会使得项目混乱,变得难以维护,同时过于依赖手工执行,这种操作非常原始。

那么如何高效率在本地调试以验证包的可用性呢?这个时候,我们就可以使用 npm link。简单来说,它可以将模块链接到对应的业务项目中运行。

我们来看一个具体场景,假设你正在开发项目 project 1,其中有个包 package 1,对应 npm 模块包名称是 npm-package-1,我们在 package 1 项目中加入了新功能 feature A,现在要验证在 project 1 项目中能否正常使用 package 1 的 feature A,你应该怎么做?

我们先在 package 1 目录中,执行 npm link,这样 npm link 通过链接目录和可执行文件,实现 npm 包命令的全局可执行。

然后在 project 1 中创建链接,执行 npm link npm-package-1 命令时,它就会去 /usr/local/lib/node_modules/ 这个路径下寻找是否有这个包,如果有就建立软链接。

这样一来,我们就可以在 project 1 的 node_module 中会看到链接过来的模块包 npm-package-1,此时的 npm-package-1 就带有最新开发的 feature A,这样一来就可以在 project 1 中正常开发调试 npm-package-1。当然别忘了,调试结束后可以执行 npm unlink 以取消关联。

从工作原理上总结,npm link 的本质就是软链接,它主要做了两件事:

  • 为目标 npm 模块(npm-package-1)创建软链接,将其链接到全局 node 模块安装路径 /usr/local/lib/node_modules/ 中;

  • 为目标 npm 模块(npm-package-1)的可执行 bin 文件创建软链接,将其链接到全局 node 命令安装路径 /usr/local/bin/ 中。

通过刚才的场景,你可以看到:npm link 能够在工程上解决依赖包在任何一个真实项目中进行调试的问题,并且操作起来更加方便快捷

5. npx 的作用

npx 由 npm v5.2 版本引入,解决了 npm 的一些使用快速开发、调试,以及项目内使用全局模块的痛点。

在传统 npm 模式下,如果我们需要使用代码检测工具 ESLint,就要先通过 npm install 安装:

  1. npm install eslint --save-dev

然后在项目根目录下执行:

  1. e
  2. ./node_modules/.bin/eslint yourfile.js

或者通过项目脚本和 package.json 的 npm scripts 字段调用 ESLint。

而使用 npx 就简单多了,你只需要下面 2 个操作步骤:

  1. npx eslint --init
  2. npx eslint yourfile.js

为什么 npx 操作起来如此便捷呢?

这是因为它可以直接执行 node_modules/.bin 文件夹下的文件。在运行命令时,npx 可以自动去 node_modules/.bin 路径和环境变量 $PATH 里面检查命令是否存在,而不需要再在 package.json 中定义相关的 script。

npx 另一个更实用的好处是:npx 执行模块时会优先安装依赖,但是在安装执行后便删除此依赖,这就避免了全局安装模块带来的问题。

运行如下命令后,npx 会将 create-react-app 下载到一个临时目录,使用以后再删除:

  1. npx create-react-app cra-project

6. npm 配置作用优先级

npm 可以通过默认配置帮助我们预设好 npm 对项目的影响动作,但是 npm 的配置优先级需要开发者确认了解。

如下图所示,优先级从左到右依次降低。我们在使用 npm 时需要了解 npm 的设置作用域,排除干扰范围,以免一顿骚操作之后,并没有找到相应的起作用配置。image.png