Node.js<五>——包管理工具深入解析

1,241 阅读12分钟

共享你的代码

我们已经学习了在JS中如何通过模块化的方式将代码划分成一个个小的结构

  • 在以后的开发中我们就可以通过模块化的方式来封装我们自己的代码,并且封装成一个工具
  • 这个工具我们可以让同事通过导入的方式来使用,甚至你可以分享给世界各地的程序员来使用

如果我们想分享给世界上所有的程序员使用,有哪些方式呢?

  1. 上传到Github上、其他程序员通过Github下载我们的代码并手动的引用
  • 缺点是大家必须知道你的代码Github的地址,并且从Github上手动下载
  • 需要在自己的项目中手动的引用,并且管理相关的依赖
  • 不需要使用的时候,需要手动来删除相关的依赖
  • 当遇到版本升级或者切换时,需要重复上面的操作
  • 显然,上面的方式是有效的,但是这种传统的方式非常麻烦,并且容易出错
  1. 使用一个专业的工具来管理我们的代码
  • 我们通过工具将代码发布到特定的位置
  • 其他程序员直接通过工具来安装、升级、删除我们的工具代码

包管理工具npm

简介

  • npm(Node Package Manager),也就是Node包管理器
  • 但是目前已经不仅仅是Node包管理器了,在前端项目中我们也在使用它来管理依赖的包
  • 比如expresskoareactreact-domaxiosbabelwebpack
  • npm管理的包实际上是发布在registry上面的,所以我们安装包时其实是从registry仓库中下载的

项目配置文件

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

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

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

  • npm init可以在我们的项目下面生成一个package.json文件,需要一个个填写默认信息
  • npm init -y 表明创建package.json文件时所有的信息都是默认的,项目名称默认是当前所在目录的文件夹名称

常见的属性

  1. 必须填写的属性:
  • name是项目的名称
  • version是当前项目的版本号
  • description是描述信息,很多时候是作为项目的基本描述
  • author是作者相关的信息(发布时用到)
  • license是开源协议(发布时用到)
  1. private属性:
  • private属性记录当前的项目是否为私有的
  • 当值为true时,是不能在npm上面发布的,这是为了防止私有项目不小心被公开
  1. main属性:
  • 设置程序的入口

  • 很多人会有疑惑,webpack不是会自动找到程序的入口吗?

    • webpack是默认指定了入口文件,但这个入口和webpack打包的入口并不冲突
    • 它是在你发布一个模块到npm或者其它平台的时候用到的
    • 比如我们引入axios模块 const axios = require('axios')
    • 实际上是去该模块的packa.json配置文件中找对应的main属性来查找入口文件的
  1. scripts属性:
  • scripts属性用于配置一些脚本命令,以键值对的形式存在
  • 配置后我们可以通过 npm run 命令的key来执行这个命令,比如说我在scripts中有配置start属性,它对应的值是node index.js,那么当我输入 npm run start 命令的时候就相当于执行了 node index.js这个指令

  • npm startnpm run start的区别是什么?

    • 他们是等价的
    • 对于常用的startteststoprestart可以省略掉run直接通过npm + 键 的方式运行;比如执行npm run start和执行npm start是一样的效果,但其它指令则不可以省略
  1. dependencies属性:
  • dependencies属性是指定无论开发环境还是生成环境都需要依赖的包
  • 通常是我们项目实际开发中用到的一些库模块
  • 与之对应的是devDependencies
  1. devDependencies属性:
  • 一些包在生产环境中是不需要的,比如webpackbabel
  • 这个时候我们会通过npm install webpack -- save-dev指令将它的版本号记录到devDependencies属性中

那么在生产环境如何保证不安装devDependencies属性下的包呢?

普通的npm install指令确实会安装devDependencies属性下对应的依赖,但通过npm install --production指令就可以不安装devDependencies下的依赖

  1. engines属性
  • engines用于指定NodeNPM的版本号
  • 在安装的过程中,会先检查对应的引擎版本,如果不符合就报错
  • 事实上也可以指定所在操作系统"os": ["darwin, "linux""],只是很少用到
  1. browserslist属性(可以单独放到一个文件里面的)
  • 用于配置打包后的JS浏览器的兼容情况
  • 否则我们需要手动的添加polyfills来让支持某些语法
  • 也就是说他是为webpack等打包工具服务的一个属性

版本管理的问题

npm的包通常需要遵循的是semver版本规范,该版本规范是X.Y.Z

  • X主版本号:当你做了不兼容的API修改时(可能不兼容以前的版本)
  • Y次版本号:当你做了向下兼容的功能性新增(新功能增加,但是兼容以前的版本)
  • Z修订号:当你做了向下兼容的问题修正(没有新功能,修复了之前版本的bug

我们会发现安装的依赖版本中出现:^2.0.3~2.0.3,这是什么意思呢?

  • ^x.y.z:表示x是保持不变的,yz永远安装最新的版本
  • ~x.y.z:表示xy是保持不变的,z永远安装最新的版本

举个例子,在dependencies下有个版本号是^2.0.3,但是npm库中的版本已经达到了2.2.5,那么安装的是最新的2.2.5版本下的依赖,而不是dependencies属性中写的版本;但如果最新版本变成了3.0.3,则这个版本是无法安装的,因为主版本号发生了变化

npm install 命令

  1. 安装npm包分两种情况
  • 全局安装(global install),直接将某个包安装到全局,例如:npm install yarn -g
  • 项目(局部)安装(lcoal install),将某个包安装到项目当中, 例如:npm install axios
  1. 但是很多人对全局安装有一些误会
  • 通常使用npm全局安装的包都是一些工具包:yarnwebpack
  • 并不是类似于axiosexpresskoa等库文件,根据require的查找规则,在模块中使用require('axios')是查找不到全局下安装的axios包的,因为其并不在我们当前项目下的node_modules文件夹中
  • 所以全局安装了之后并不能让我们在所有的项目中使用axios等库
  1. 全局安装的意义
  • 当你全局安装了某个包的时候,他是会生成对应的可执行文件并放入全局,而且还会自动配置对应的环境变量
  • 一旦配置了环境变量就意味着:你在敲关键字例如:nodewebpack的时候会去寻找对应的环境变量,然后找到相应的可执行文件,去帮助我们执行对应的命令和相关的代码

项目安装

  1. 项目安装会在当前目录下生产一个node_modules文件夹,我们之前讲解require查找顺序时也有讲解过这个包在什么情况下被查找
  2. 局部安装分为开发时依赖和生产时依赖
  • 安装开发和生产依赖

    • npm install axios
    • npm i axios(是上面一种方式的简写)
  • 开发依赖

    • npm install webpack --save-dev
    • npm install webpack --D
    • npm i webpack --D
  • 根据package.json中的依赖安装包

    • npm install

npm install原理

很多人都会使用npm下载依赖,但是它的原理可能并不是都清楚

  • 执行npm install他背后帮助我们完成了什么操作?
  • 我们会发现还有一个称为package-lock.json的文件,它的作用又是什么?
  • npm5开始,npm支持缓存策略(来自yarn的压力),缓存有什么作用呢?

执行npm install命令时会先检测有没有package-lock.json文件:

  • 没有lock文件
    • 分析依赖关系,这是因为我们的包可能会依赖其他的包,并且多个包之间会产生相同依赖的情况
    • registry仓库中下载压缩包(如果我们设置了镜像,那么会从镜像服务器下载压缩包)
    • 获取到压缩包后会对压缩包进行缓存(从npm5开始有的)
    • 将压缩包解压到项目的node_modules文件夹中(前面我们讲过,根据require的查找顺序,其会在该包下面查找)
  • 有lock文件

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

      • 不一致,那么会重新构建依赖关系,直接走顶层的流程

      • 一致的情况下,会优先去查找缓存

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

package-lock.json文件解析

  • name:项目的名称

  • version:项目的版本

  • lockfileVersionpackage-lock.json文件的版本

  • requires:是否使用requires属性来表示模块的依赖关系

  • dependencies:项目的依赖

    • 当前项目依赖axios,但axios又依赖follow-redireacts

      • 这就是为什么在package.json文件中只看到了axios一个依赖,但在package-lock.json文件中却看到了axios和follow-redireacts两个依赖,就是因为一个库中还有可能会依赖于其它的库
    • axios中的属性如下:

      • version表示实际安装的axios的版本,这个版本号可能会和package.json中的不完全一样,但一定是遵守了semver规范的版本号
      • resolved用来记录要下载依赖的地址,也就是依赖所对应的压缩包在rigistry仓库中的位置
      • requires记录当前模块所需要的依赖
      • intergrity用来结合算法从缓存中获取索引,再通过索引去获取对应依赖的压缩包文件

补充:执行npm config get cache命令可以看到自己电脑上的压缩包缓存在哪一个文件夹中

  • index-v5里面存储的是对应的索引
  • content-v2里面存储的是对应的压缩包
  • 在缓存中获取压缩包文件时,会先通过intergrity结合某种算法在index-v5文件夹里面拿到包所对应的索引,然后再到content-v2根据索引寻找具体的压缩包
  • 找到压缩包之后会通过对应的工具进行解压,然后放到项目的node_modules文件夹中去

package-lock.json文件里面记录的是真实安装的依赖版本,所以我们在上传代码的时候尽量把这个文件都上传上去,避免别人拷贝项目的时候出现因版本引起的问题

npm其他命令

  • 一次性安装多个包

    • npm install 项目1 项目2 项目3 ......
  • 卸载某个依赖包

    • npm uninstall package
    • npm uninstall package --save-dev
    • npm uninstall package -D
  • 强制重新安装项目依赖

    • 删除原先安装的依赖,然后再重新安装
    • npm rebuild
  • 清除缓存

    • 比如某个在缓存中的包已经被破坏了,那如果还会去缓存中拿,项目运行就会出错。所以就需要我们手动清除掉缓存才行
    • npm cache clean

Yarn工具

  • yarn是有FackbookGoogleExponentTilde联合退出了一个新的JS包管理工具

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

  • 早起的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题

    • 因为一开始npm没有缓存,所以其每次都要去仓库下载依赖,导致安装速度很慢
    • 版本依赖非常混乱是因为早期其采用的树形的结构来表示依赖关系,这样会导致层级非常深且可能出现重复依赖的情况
  • 虽然从npm5版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn

下面是npm与yarn中一些等价的命令:

cnpm工具

由于一些特殊的原因(比如rigistry仓库的服务器在国外),某些情况下我们没办法很好的从 https://registry.npmjs.org下载下来一些需要的包

  • 查看npm镜像:

    • npm config get registry
  • 我们可以直接设置npm的镜像

    • npm config set registry
  • 但是对于大多数人来说,并不希望修改npm的镜像

    • 第一,不希望随意修改npm原本从官方下来包的渠道
    • 第二,担心某天淘宝的镜像挂了或者不维护了,又要对之前设置的镜像做一个改动
  • 这个时候,我们可以使用cnpm,并且将cnpm的镜像设置为淘宝的镜像

    • cnpm config get registry # https://r.npm.taobao.org/

npx工具

npxnpm5.2之后自带的一个命令

  • npx的作用非常多,但是比较常见的是使用它来调用项目中的某个模块的指令
  • 当你在终端使用npx webpack --version的时候,它会先去当前项目中寻找对应指令,而不是全局下面

我们以webpack为例

  • 全局安装的是webpack5.1.3
  • 项目安装的是webpack3.6.0,放在了node_modules/.bin文件夹中

如果我在终端执行 webpack --version使用的是哪一个命令呢?

  • 显示结果会是webpack 5.1.3,也就是说用的是全局的,因为它是不知道要去node_modules文件中去寻找已经安装在项目中的webpack的

但如果我们确实想使用的是项目中的webpack指令,而不是全局的,那么就可以在package.jsonscripts属性中配置相关的键值对,通过这种方式执行的命令会优先到我们项目中node_modules的.bin文件夹中寻找

最常用的方法:直接用npx webpack --version的话,访问到模块会先在项目中寻找,打印出的版本就是项目中所安装的3.6.0版本