npm的一些知识点

npm 内部机制与核心原理

npm 最重要的任务是安装和维护开源库。它会优先安装依赖包到当前的项目目录,使得不同的项目的依赖各成体系,同时还能减轻包作者的 API 兼容性压力,但是这样做的缺陷也很明显:如果项目 A 和项目 B 都依赖相同的公共库 C,那么公共库 C 将会在项目 A 和项目 B 中各被安装一次。这就说明,同一个依赖包可能在电脑上多次安装。

npm 的安装机制

当我们执行npm install命令以后,首先检查 config,获取 npm 配置。这里的优先级为:项目级的.npmrc 文件 > 用户级的.npmrc 文件 > 全局的.npmrc 文件 > npm 内置的.npmrc 文件。

然后检查项目中有无 package-lock.json 文件(简称 lock 文件)

如果有 package-lock.json 文件,那么就检查 package-lock.json 和 package.json 文件中声明的版本是否一致。

  • 一致,直接使用 package-lock.json 中的信息,从缓存或者网络资源中加载资源。

  • 不一致,那么根据 npm 版本进行处理(5.4.2 以上会按照 package.json 安装,并更新 package-lock.json)

如果没有 package-lock.json 文件,那么根据 package.json 文件递归构建依赖树,然后按照构建好的依赖树下载完整的依赖资源,在下载时会检查是否有相关缓存。

  • 有缓存,将内容解压到 node_modules 中。

  • 没有,先从 npm 远程仓库中获取包资源,检查包的完整性,并将其添加到缓存,同时解压到 node_modules 中。

最后生成 package-lock.json 文件。

npm 缓存机制

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 不完全指南

自定义 npm init

npm 支持自定义 npm init,快速创建一个符合自己需求的自定义项目。npm init 命令本身并不复杂,它的功能其实就是调用 Shell 脚本输出一个初始化的 package.json 文件。相应的我们需要自定义 npm init 命令。就是写一个 node.js 脚本,它的 module.exports 就是 package.json 配置内容。

为了实现更加灵活的自定义功能,我们可以使用 prompt()方法来获取用户输入的内容及动态产出的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const desc = prompt('请输入项目描述', '项目描述...')
const { execSync, exec } = require('child_process')
function run(func) {
console.log(execSync(func).toString())
}
module.exports = {
key: 'value',
name: prompt('name?', process.cwd().split('/').pop()),
version: prompt('version?', '0.0.1'),
author: prompt('author', 'Guo zhaoxi <29693847@qq.com> (guozhaoxi.top)'),
keywords: prompt('keywords', '', function (s) {
if (s) {
return s.split(/\s+/)
}
}),
description: desc,
main: 'index.js',
repository: prompt('请输入远程仓库的地址', '', function (url) {
if (url) {
run('touch README.md')
run('git init')
run('git add .')
run('git commit -m "first commit"')
run(`git remote add origin ${url}`)
run('git push -u origin main')
}
return url
})
}

假设上面脚本的名字为.npm-init.js 执行以下命令来确保 npm init 所对应的脚本指向正确的文件。

1
npm config set init-module ~\.npm-init.js

我们也可以通过配置 npm init 默认字段来自定义 npm init 的内容。如下:

1
2
3
4
npm config set init.author.name "Guozhaoxi"
npm config set init.author.email "29693847@qq.com"
npm config set init.author.url "guozhaoxi.top"
npm config set init.author.license "MIT"

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

npm link的本质就是创建软链接。它主要做了两件事:

  • 为目标 npm 模块创建软链接将其连接到/usr/local/lib/node_modules/全局模块安装路径下
  • 为目标 npm 模块的可执行 bin 文件创建软链接,将其链接到全局 node 命令安装路径/usr/local/bin 下。

npx

npx 为什么操作起来更便捷?

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

npx 另一个更实用的特点是,它在执行模块时会优先安装依赖,但是在安装成功后便删除依赖。避免了全局安装带来的问题。