node学习 --- 包管理工具

377 阅读9分钟

npm

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

在JavaScript中我们可以通过模块化的方式将代码划分成一个个小的结构

在以后的开发中我们就可以通过模块化的方式来封装自己的代码,并且封装成一个工具

此时我们可能希望分享这个工具或库,但是因为使用了模块化的方式进行开发

所以我们即需要一个网址去发布和下载我们的库,

同时需要一个工具可以自动帮助我们去管理和维护相关的依赖

我们可以通过这个工具来安装,升级,删除我们的工具,库及其相关依赖

这个工具就是npm,而这个网址就是www.npmjs.com/

npm 的 registry

www.npmjs.com/这个网址实际上是npm包的检索和文档查看的网址

真正的npm包是存放在registry.npmjs.org/这个registry地址上的,

我们下载和上传我们的库实际也是上传到这个地址对应的服务器中

项目配置文件

我们每一个项目都会有一个对应的配置文件,无论是前端项目还是后端项目:

  • 这个配置文件会记录着你项目的名称、版本号、项目描述等
  • 也会记录着你项目所依赖的其他库的信息和依赖库的版本号

在Node环境下面(无论是前端还是后端) 这个配置文件就是package.json

所以我们在开发项目前或者至少在安装相应的依赖前,

需要在项目的根目录下生成package.json

# 生成方式1 --- 自己手动输入对应的配置信息
npm init 

# 生成方式2 --- 全部使用默认的配置信息
npm init --yes|-y
常见的配置信息
属性名描述默认值说明
name项目的名称项目文件夹名全部小写,多字母建议用中横线-分隔,不建议使用点.或下划线_分隔(虽然._可以使用)
version版本号1.0.0遵循semver规范 ---> major.minor.patch
description描述信息,很多时候是作为项目的基本描述“”
author作者相关信息""
license开源协议ISC
private当前的项目是否是私有的false当值为true时,npm是不能发布它的
这是防止私有项目或模块发布出去的方式
main程序主入口index.js这个属性只有在当前项目作为一个模块被其它项目引用时候有用
使用该模块的项目可以根据这个入口文件进行代码的构建
在本地开发的时候,项目的实际主入口文件是由构建工具的配置文件决定的,而不是在package.json中确定
script一些脚本命令,以键值对的形式存在test: "xxxx"配置后我们可以通过 npm run 命令的key来执行这个命令
对于常用的 startteststoprestart可以省略掉run直接通过 npm start等方式运行
例如key为dev的时候,运行该命令需要使用npm run dev
但是如果key是start的时候,运行该命令使用npm start即可,run可以省略
dependencies无论开发环境还是生产环境都需要依赖的包
通常是我们项目实际开发用到的一些库模块
安装方式`npm iinstall <包名> [--save--save-prod--save-production省略]<br />我们可以通过npm install --production` 来只安装生产环境下的依赖,而不安装开发环境下的依赖
devDependencies些包在生产环境是不需要的
仅仅只是在开发环境下需要的库
安装方式 `npm iinstall <包名> --save-dev-D`
engines指定Node和NPM的版本号
安装的过程中,会先检查对应的引擎版本,如果不符合就会报错
browserslist项目需要适配的浏览器版本信息
可以在多个需要该信息的工具之间共享信息
可以单独抽离成一个配置文件,且具有默认值,所以一般不在package.json中进行配置
版本管理

npm的包通常需要遵从semver版本规范:

semver版本规范是major.minor.patch

说明描述
major(主版本号)增加了不兼容之前的版本的API
minor(次版本号)增加了新的API,但是兼容之前的版本
patch(修订号)没有新功能,只是进行了问题修正,也就是仅仅只是修复了bug

^和~之间的区别

符号说明例子
^锁主版本号^x.y.z: 表示x是保持不变的,y和z永远安装最新的版本
~锁主版本号和次版本号~x.y.z: 表示x和y保持不变的,z永远安装最新的版本

npm install

安装npm包分两种情况:

  1. 全局安装 (global install): npm install <包名> -g
  2. 项目(局部)安装 (local install): npm install <包名>

全局安装

局安装是直接将某个包安装到全局

# 安装指令
npm i <包名> -g

一般情况下,我们是将一个工具库安装到全局,例如webapckless,typescript

这些工具会生成对应的可执行指令,并配置好对应的环境变量

这就意味着,我们可以在任意目录下执行这些工具提供的可执行文件

如全局安装完webpack后,我们可以在任意目录层级下使用webpack指令

全局安装的都是一些工具库,一些方法库(如axioskoa等)是没有必要安装到全局的

而且就算安装到了全局,项目也无法找到这些库,

因为在项目中查找包的时候,顶层文件夹是去/node_modules下进行查找

而全局包是安装在/usr/local/lib/node_modules这个文件夹中的

根本不是一个文件夹,所以一些方法库(如axioskoa等)是没有必要安装到全局的

项目安装

项目安装会在当前目录下生产一个 node_modules 文件夹,

其查找路径为module.paths中所对应的查找路径

console.log(module.paths)
/*
  =>
    [
      '/Users/klaus/Desktop/node-demo/node_modules',
      '/Users/klaus/Desktop/node_modules',
      '/Users/klaus/node_modules',
      '/Users/node_modules',
      '/node_modules'
    ]
*/

局部安装分为开发时依赖和生产时依赖

# 安装开发和生产依赖
# npm install <包名>
# 或 npm i <包名>

# 安装开发依赖
# npm install <包名> --save-dev
# 或 npm i <包名> --save-dev
# 或 npm install <包名> -D
# 或 npm i <包名> -D

自动根据package.json中的依赖(包括开发时依赖和生产时依赖),

在项目根目录下生产node_modules文件夹,并自动去安装对应的依赖

对应的依赖所需要的版本会自动根据package.json中对应包所设置的版本约束信息去下载和引入可以使用的最新版本的包

npm install
# 或 npm i
原理

ImdFst.png

npm install会检测是有package-lock.json文件:

  • 没有lock文件

    1. 分析依赖关系,这是因为我们可能包会依赖其他的包,并且多个包之间会产生相同依赖的情况

    2. 从registry仓库中下载压缩包(如果我们设置了镜像,那么会从镜像服务器下载压缩包)

    3. 获取到压缩包后会对压缩包进行缓存(从npm5开始有的)

    4. 将压缩包解压到项目的node_modules文件夹中

    5. 生成对应的package-lock.json文件

  • 有lock文件

    • 检测lock中包的版本是否和package.json中一致(会按照semver版本规范检测)

      • 不一致,那么会重新构建依赖关系,直接会走顶层的流程
    • 一致的情况下,会去优先查找缓存

      • 没有找到,会从registry仓库下载,直接走顶层流程
    • 查找到,会获取缓存中的压缩文件,并且将压缩文件解压到node_modules文件夹中;

package-lock.json

属性名描述
name项目的名称
version项目的版本
lockfileVersionlock文件的版本
requires (例如: requires: true模块所依赖的对应模块是存储在requires这个配置对象中的
dependencies项目的依赖
{
  "name": "demo",
  "version": "1.0.0",
  "lockfileVersion": 2,
  "requires": true, // 之后版本依赖关系是存储在requires属性中的
  // 之前的npm包管理是树结构,这样就会导致npm树中有多个节点其实是重复的,但是被多次安装了
  // 所以npm V5.0 开始,将npm中包依赖关系扁平化了,这样就不会导致节点重叠的问题
  "dependencies": {
    "axios": {
      "version": "0.21.4",
      "resolved": "https://registry.nlark.com/axios/download/axios-0.21.4.tgz?cache=0&sync_timestamp=1630942582656&other_urls=https%3A%2F%2Fregistry.nlark.com%2Faxios%2Fdownload%2Faxios-0.21.4.tgz",
      "integrity": "sha1-xnuQ3AVo5cHPKwuFjEO6KOLtpXU=",
      "requires": {
        "follow-redirects": "^1.14.0"
      }
    },
    "follow-redirects": {
      "version": "1.14.4",
      "resolved": "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.4.tgz?cache=0&sync_timestamp=1631622129411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ffollow-redirects%2Fdownload%2Ffollow-redirects-1.14.4.tgz",
      "integrity": "sha1-g4/fSKi73XnlLuUfsclOPtmLk3k="
    }
  }
}

某一个特定包所对应的package-lock.json中的信息

  "axios": {
      "version": "0.21.4", // 实际安装的版本号
      "resolved": "https://registry.nlark.com/axios/download/axios-0.21.4.tgz?cache=0&sync_timestamp=1630942582656&other_urls=https%3A%2F%2Fregistry.nlark.com%2Faxios%2Fdownload%2Faxios-0.21.4.tgz", // 对应的库的压缩包所在的地址
      "integrity": "sha1-xnuQ3AVo5cHPKwuFjEO6KOLtpXU=", // 一个秘钥,可以用来在缓存文件夹中寻找到对应的压缩库
      "requires": {
        "follow-redirects": "^1.14.0" // 所需要的依赖库
      }
    }

其它命令

其它的一些npm命令可以点击这里进行查看

# 卸载某个依赖包
npm uninstall <包名>
npm uninstall <包名> --save-dev
npm uninstall <包名> -D
# 强制构建 --- 无视package.lock.json和缓存
npm rebuild
# 清除缓存
npm cache clean

# 查看npm缓存所在的文件夹
npm config get cache

yarn

另一个node包管理工具yarn:

yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具

yarn 最早是为了弥补 npm 的一些缺陷而出现的

ImeQEC.png

cnpm

由于一些特殊的原因,某些情况下我们没办法很好的从 registry.npmjs.org下载下来一些需要的包

解决方法1:设置npm的registry地址

# 获取镜像地址
npm config get registry

# 设置镜像地址
pm config set registry https://registry.npm.taobao.org

但有的时候,我们不太希望随意修改npm原本从官方下来包的渠道, 这个时候,我们可以使用cnpm

# 下载cnpm 并直接修改registry
npm install -g cnpm --registry=https://registry.npm.taobao.org

# 获取cnpm registry地址
cnpm config get registry

# cnpm 设置registry地址
cnpm config ser registry

npx

npx是npm V5.2之后自带的一个命令

npx 想要解决的主要问题,就是调用项目内部安装的模块

一般来说,我们在项目级别安装了某个可执行的npm包 (也就意味着这个包在node_modules/.bin文件夹下会有对应脚本的软连接 ), 例如webpackhttp=server, mocha

如果我们直接在命令行中对这些脚本进行运行,默认情况下是去全局环境下寻找对应的指令

# 此时运行的webpack是全局的webpack,并不是局部的webpack
$ webpack

但是又的时候我妈需要调用项目内部安装的模块

在没有npx之前,我们的做法有两种

  • 将指令配置到package.jsonscripts字段中
"scripts": {
  "start": "webpack serve" // => 此时运行的webpack就会优先查找项目级别的webpack来进行执行
}
  • 直接执行对应的脚本文件
# 找到本地的webpack对应的运行脚本进行执行
$ node-modules/.bin/webpack --version

npx 就是想解决这个问题,让项目内部安装的模块用起来更方便, 同时可以避免全局安装的模块

# 当我们使用npx去查找某一个包的时候
# 1. 现在项目的node_modules中进行查找,如果查找到就使用,没有找到执行第二步
# 2. 去环境变量$path中进行查找,如果查找到就使用,没有找到,没有找到执行第三步
# 3. 没有安装,将对应的包安装到一个临时目录中后进行相应的使用,在对应的包使用完毕以后,会自动移除对于的临时目录

$ npx webpack --version # 此时运行的webpack会优先运行项目级别的webpack