npm 的简单介绍

198 阅读8分钟

npm 的简单介绍

Part1 npm 和 package.json 的相关介绍

node.js 与 npm

node.js 是 javascript 的一种运行环境,是对 Google V8 引擎进行的封装,是一个服务器端的 javascript 的解释器。

npm 全称是 Node Package Manager,是 Node.js 默认的、用 JavaScript 编写的软件包管理系统。

  • 默认包管理器 :npm 官网上可以查找包,查看包信息
  • 充当注册表:一个巨大的数据库,存放包的信息
  • 提供命令行工具:开发者运行 npm 命令的工具

建立仓库——开发者命名并上传需要共享的代码——使用者下载(npm 包) 降低了开发者逐个寻找、下载项目中需要的依赖的成本。

查看 npm 信息的一些命令

npm -v 查看 npm 版本 where node 查看 node 的安装目录

使用 nvm 后,npm 安装路径:/Users/zhihu/.nvm/versions/node 未使用 nvm ,npm 安装路径:/usr/local/bin/node

where npm 查看 npm 的安目录

使用 nvm 后,npm 安装路径:/Users/zhihu/.nvm/versions/node/v16.15.0/bin/npm 未使用 nvm ,npm 安装路径:/usr/local/bin/npm

npm root -g 查看全局包的安装目录

/Users/zhihu/.nvm/versions/node/v16.15.0/lib/node_modules

npm 与 package.json

package.json 官网介绍:package.json 介绍和其配置项

package.json 主要作用:

  • 跟踪依赖关系;
  • 包含名称、描述和版本之类的信息;
  • 运行、开发以及有选择地将项目发布到 npm 所需的信息。 常见配置项

package.json

语义化版本 (semver 规范)

版本号遵循「主版本号.次版本号.修订号」的格式规定

"dependencies": {
   "react": "^17.0.2",
   "react": "~17.0.2",
   "react": "17.0.2",
},

固定版本: 17.0.2,安装时只安装这个指定的版本; 波浪号: ~17.0.2,表示安装 17.0.x 的最新版本(不低于 17.0.2)不会改变主版本、次版本号; 插入号:^17.0.2,表示安装 17.x.x 的最新版本(不低于 17.0.2),不会改变主版本号。 latest:安装最新的版本。 更多介绍

dependencies

生产环境下,项目运行所需依赖。

devDependencies

开发环境下,项目所需依赖。

script

执行 npm 脚本命令简写。配置格式:"xxx": "node ./index.js"

bin

bin 字段是命令名到本地文件名的映射。在安装时,npm 会将文件软链接/符号连接,进行全局安装或./node_modules/.bin/本地安装。

发布一个包

  1. 注册一个 npm 账号
  2. 在本地通过运行 npm login 登陆你的 npm 账号
  3. 初始化项目
  4. 编写代码/修改代码
  5. 使用 npm publish 发布当前包到 npm 仓库

把发布之后的包安装到本地:npm install packageName

node_modules 里的内容

node_modules 存储着我们下载的依赖,主要分为两部分:

  1. 命令对应的可执行文件的软链接文件,指向对应第三方依赖的 bin 文件。
  2. 在运行 npm install 时,根据 package.json 配置的第三方依赖信息,下载的对应版本 package 到 node_modules 目录下。 在这里插入图片描述

Part2 npm 常用命令介绍

npm run

运行 pacgake.json 中指定的脚本。

{
  "name": "ztest",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "create": "create-react-app --version"  
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "create-react-app": "^5.0.1"
  }
}

当我们运行 npm run create 会查找 package.json 中 scripts 中 key 对应的值来当作命令执行,也就是相当于执行了 create-react-app --version,查看 create-react-app 版本。

问题 1:为什么我们执行 npm run create ,不直接执行 create-react-app --version

在这里插入图片描述 npm run 执行脚本时会先去 node_modules/.bin 中查找是否存在要运行的命令;如果不存在则查找 ../node_modules/.bin,如果全都找不到才会去按系统的环境变量查找。

使用 npm root -g,查看一下全局安装目录: /Users/zhihu/.nvm/versions/node/v16.14.0/lib/node_modules 进入到目录: cd /Users/zhihu/.nvm/versions/node/v16.14.0/lib/node_modules 查看全局安装的依赖: ls 我并没有全局安装 create-react-app ,所以提示命令并没有找到。

问题 2:说一说 node_modules/.bin 中的文件?

package.json 中的 bin 指定的命令和文件链接到 node_modules/.bin,即 node_modules/.bin/create-react-app 其实是 node_modules/create-react-app/bin/index.js 的快捷方式(软链接) create-react-app 在 package.json 是这样配置 bin 属性的,bin 属性中定义了要软链接的文件源。

{
  "name": "create-react-app",
  "version": "5.0.1",
  "keywords": [
    "react"
  ],
  "description": "Create React apps with no build configuration.",
  "repository": {
    "type": "git",
    "url": "https://github.com/facebook/create-react-app.git",
    "directory": "packages/create-react-app"
  },
  "license": "MIT",
  "engines": {
    "node": ">=14"
  },
  "bugs": {
    "url": "https://github.com/facebook/create-react-app/issues"
  },
  // 描述了将软件包作为依赖项安装时要包括的条目
  "files": [
    "index.js",
    "createReactApp.js"
  ],
  // 暴露给安装后 node_modules 文件夹下 .bin 的软链接
  "bin": {
    "create-react-app": "./index.js"
  },
  "scripts": {
    "test": "cross-env FORCE_COLOR=true jest"
  },
  "dependencies": {
    "chalk": "^4.1.2",
    "commander": "^4.1.1",
    "cross-spawn": "^7.0.3",
    "envinfo": "^7.8.1",
    "fs-extra": "^10.0.0",
    "hyperquest": "^2.1.3",
    "prompts": "^2.4.2",
    "semver": "^7.3.5",
    "tar-pack": "^3.4.1",
    "tmp": "^0.2.1",
    "validate-npm-package-name": "^3.0.0"
  },
  "devDependencies": {
    "cross-env": "^7.0.3",
    "jest": "^27.4.3"
  },
  "gitHead": "19fa58d527ae74f2b6baa0867463eea1d290f9a5"
}

问题 3:node_modules/.bin 中的脚本文件怎么执行的?

刚才说到,node_modules/.bin 中的文件是对应的软链接。 有一点值得注意,create-react-app 文件的第一行是 #!/usr/bin/env node ,因为 Shebang ,输入命令后,会有在一个新建的 shell 中执行指定的脚本,指定这个脚本的解释程序是 node。

npm install

  1. 执行工程自身 preinstall 钩子
  2. 确定首层依赖模块 确定工程中的首层依赖,即 dependencies 中直接指定的模块。
  3. 获取模块 获取模块是一个递归的过程,分为以下几步: 获取模块信息。在下载一个模块之前,package.json 中确定其版本。此时如果版本描述文件中有该模块信息直接拿即可,如果没有则从仓库获取。 获取模块实体。上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载。 查找该模块依赖,如果有依赖则回到第 1 步,如果没有则停止。 在这里插入图片描述
  4. 模块扁平化 上一步获取到的是一棵完整的依赖树,其中可能包含大量重复模块。从 npm3 开始默认加入了一个 dedupe 的过程。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。 在这里插入图片描述
  5. 安装模块 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
  6. 执行工程自身生命周期 当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare 的顺序)。
  7. 生成或更新版本描述文件,npm install 过程完成。 在这里插入图片描述

Part3 一些其他问题

package.json 与 package-lock.json

npm5 前,没有 package-lock.json 文件。package.json 文件会记录你项目中所需要的所有模块。当你执行 npm install 的时候,根据 dependencies 中的信息与 node_modules 中的模块进行对比,没有的直接下载,已有的检查更新。

npm5 后,npm install 时候,会自动生成一个 package-lock.json 文件。package-lock.json 文件锁定所有模块的版本号,包括主模块和所有依赖子模块。后续执行 npm install ,从 package.json 文件读取模块名称,从 package-lock.json 文件中获取版本号,进行下载和更新。

package-lock.json 确定最终版本号的历史

npm 5.0.x 版本:不管 package.json 中依赖是否有更新,npm i 都会根据 package-lock.json 下载。针对这种安装策略,有人提出了这个issue - #16866 ,然后就演变成了 5.1.0 版本后规则。

5.1.0 版本后:当 package.json 中的依赖项有新版本时,npm install 会无视 package-lock.json 去下载新版本的依赖项并且更新 package-lock.json。针对这种安装策略,又有人提出了一个issue - #17979,得出 5.4.2 版本后的规则。

5.4.2 版本后: 如果 package.json 文件,运行 npm i 会根据它生成一个 package-lock.json 文件,不仅记录了 package.json 指明的直接依赖的版本,也记录了间接依赖的版本。

package-lock.json 是怎么确定最终版本号的?
  1. 如果 package.json 和 package-lock.json 中版本兼容(package-lock.json 版本在 package.json 指定的版本范围内),即使此时 package.json 中有新的版本,执行 npm i 也还是会根据 package-lock.json 下载。
// package.json
"dependencies": {
	"vue": "^2.0.0"
}

// package-lock.json
"dependencies": {
	"vue": {
		"version": "2.1.0",
		"resolved": "https://registry.npm.taobao.org/vue/download/vue-2.1.0.tgz",
		"integrity": "sha1-KTuj76rKhGqmvL+sRc+FJMxZfj0="
	}
}
package-lock.json 指定的 2.1.0 在^2.0.0 指定的范围内,
npm install 会安装 vue2.1.0 版本。
  1. 如果手动修改了 package.json 的 version ranges,且和 package-lock.json 中版本不兼容,那么执行 npm i 时 package-lock.json 将会更新到兼容 package.json 的版本。
// package.json
"dependencies": {
	"vue": "^2.2.0"
}

// package-lock.json
"dependencies": {
	"vue": {
		"version": "2.1.0",
		"resolved": "https://registry.npm.taobao.org/vue/download/vue-2.1.0.tgz",
		"integrity": "sha1-KTuj76rKhGqmvL+sRc+FJMxZfj0="
	}
}
package-lock.json 指定的 2.1.0 不在^2.2.0 指定的范围内,
npm install 会按照^2.2.0 的规则去安装最新的 2.6.10 版本,
并且将 package-lock.json 的版本更新为 2.6.10

npm 的问题

  1. 幽灵依赖
  2. 没有完全解决重复依赖问题

pnpm 简要介绍

官网 在这里插入图片描述 更多介绍

参考 & 扩展