JS Advance --- 包管理工具

382 阅读6分钟

代码共享方案

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

早期我们需要共享我们的代码,我们使用的首选是github

上传到GitHub上、其他程序员通过GitHub下载我们的代码手动的引用

  • 使用者需要知道对应的github地址,并且需要手动下载下来以后在手动引入
  • 对应的包的依赖关系需要自己手动进行维护
  • 不需要使用的时候,需要手动来删除相关的依赖
  • 当遇到版本升级或者版本修复了某些bug的时候,需要重复上面的操作

随着项目越来越大,一个项目所需要依赖的库变得越来越多

而且库的依赖和库和库之间的依赖也变得越来越繁琐

这种传统的方式更新和管理库的时候就非常麻烦,并且容易出错

所以我们需要使用一个工具,来替代这种复杂且容易出错的人工管理工作

而这个工具就是包管理工具(npm)

我们通过工具将代码发布到特定的位置, 其他程序员直接通过工具来安装、升级、删除我们的工具代码

npm

Node Package Manager,也就是Node包管理器

但是目前已经不仅仅是Node包管理器了,在前端项目中我们也在使用它来管理依赖的包

检测地址和仓库地址

我们可以在www.npmjs.com/ 这个网址,对我们所需要使用的包(第三方库)进行检索

但是这个库仅仅只是起到查找的功能,我们实际发布和下载对应的包是到npm所对应的registry中去的

配置文件

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

这个配置文件会记录着你项目的名称、版本号、项目描述等

也会记录着你项目所依赖的其他库的信息和依赖库的版本号

这个配置文件就是package.json

# 生成配置文件 package.json
$ npm init

# 快速生成配置文件 --- 全部使用默认值
# 如果需要快速生成对应的配置文件的前提是,包名必须是合法的 --- 全部都是小写的,多个单词之间使用中划线进行连接
$ npm init --yes | -y
常见属性
属性名功能备注
name项目的名称必填
version项目的版本号1. 必填
2. 遵循semver规范
description项目的基本描述默认值是空字符串
author作者相关信息在发布的时候需要用到,私有项目,该属性不起作用
license开源协议1. 默认 --- ISC
2. 项目发布时候需要使用,私有项目,该属性不起作用
keywords项目对应的关键字项目搜索时候对应的查询关键字
private项目是否是开源的1. 值是一个boolean值
2. 如果值是true,那么npm是不能发布它,这是防止私有项目或模块发布出去的方式
main项目的主入口文件1. 默认是index.js
2. 项目发布的时候才会使用,如果项目是本地私有的,该属性不起作用
3. 如果项目是开源的,如开源的第三方包,那么在引入包后,进行依赖关系图构建的时候,默认回去查找项目根目录下的index.js文件,如果项目的主入口文件不是根目录下的index.js的时候,需要在main字段中显示指定
scripts用于配置一些脚本命令,以键值对的形式存在1. 配置后我们可以通过 npm run 命令的key来执行对应的脚本
2. 运行指令为 npm run <脚本key值(脚本名称)>
3. 对于常用的 start、 test、stop、restart可以省略掉run直接通过 npm start等方式运行
dependencies无论开发环境还是生成环境都需要依赖的包1. 直接使用npm install命令的时候,dependencies字段中的库会被下载
2. 如果库是第三方库,使用者在使用这个第三方库的项目中使用npm install命令的时候,这个第三方库中dependencies字段所对应的包也会被一并下载下来
devDependencies开发时候才会需要使用的包
peerDependencies项目中使用这个包的对等依赖包1. 所谓对等依赖包,即安装这个包之前,对应的项目中必须存在peerDependencies字段中所对应的那些包
2. 如果对等依赖包不存在的时候,库是可以被正常下载下来的,但是会出现警告,提示下载库的使用者,需要去下载peerDependencies中对应的包
3. 例如下载element-plus的库的时候,会提示用户该库是依赖于vue3的,如果用户没有下载vue3包而直接下载了element-plus,那么会出现提示警告信息,而vue3就是element-plus的peerDependencies。
enginesengines属性用于指定Node和NPM的版本号1. 在安装的过程中,会先检查对应的引擎版本,如果不符合就会报错
2. 很少用到
homepage项目对应的官网地址1. 发布使用才会使用
2. 如果存在对应的官网就填写对应的官网地址,如果没有一般会填写github地址
repository项目仓库地址1. 值的类型是一个对象
2. 属性type的值用来设置仓库的类型 一般设置为git
3. 属性url的值用来设置仓库的地址
一些其它属性如browserslist,eslintconfig等package.json本质上其实还是一个json文件,所以一些第三方配置工具也会将其对应的配置信息配置到package.json中,他们在解析的时候会去读取package.json

常见指令

# 查看版本
$ npm --version | -v

# ps: 以下命令中的i表示的是install的简写,在使用时,既可以使用install也可以使用i

# 下载全部依赖
$ npm i

# 下载第三方包 --- 开发环境和生产环境都需要使用的包(主要是一些项目中会使用到的库)
$ npm i <package> [--save]

# 下载开发环境才需要使用的第三方包
$ npm i <package> -D | --save-dev

# 安装某一个具体版本的包
# 如果需要安装最新版本的包 --- version的值写成latest
$ npm i <package>@<version>

# 全局安装库  --- 作为工具库使用,尤其是那些需要在命令行中使用的时候
# 有的时候,我们对于某一个工具库即需要全局安装又需要局部安装 (如webpack)

# 全局安装是为了在命令行中可以使用这些库
# 局部安装是为了可以在多个项目中使用多个不同版本的工具库,同时可以使整个项目组中使用的工具库的版本是一致的
$ npm i <package> -g

# 卸载对应的包
$ npm uninstall | un <package> [-g]

虽然在实际发布过程中,我们的项目会经过编译工具进行打包后,再进行发布

而编译工具在打包的时候,会通过入口文件文件构建对应的依赖关系图,

如果某一个库并不存在于依赖关系图上的时候,对应的库并不会被打包到最终的项目文件中

所以就算我们将所有的开发时依赖添加到dependencies字段中,在实际打包的过程中,也会将这些依赖从项目中移除

但是为了尽可能的符合对应的规范,我们还是推荐将开发时依赖单独添加到devDependencies字段中

而不是全部都写到dependencies字段中

semver

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

semver (semanic version)也就是语义化版本规范

# 主版本号.次版本号.修定版本号
# major.minor.patch
字段描述
major1. 不兼容之前版本的 API 修改
2. 项目的架构进行了迭代,进行了整体升级
minor向下兼容的功能性新增 --- 新功能增加,但是兼容之前的版本
patch向下兼容的问题修正 --- 没有新功能,修复了之前版本的bug
描述符功能备注
^锁主版本^x.y.z:表示x是保持不变的,y和z永远安装最新的版本
~锁主版本和次版本~x.y.z:表示x和y保持不变的,z永远安装最新的版本

npm install 原理

s

  1. 查看有没有package-lock.json

    1.1 不存在

    • 从入口文件开始依次去构建对应的依赖关系图
    • 去对应的registry仓库中进行查找,并下载对应的压缩包
    • 将对应的压缩包添加到缓存中(缓存其实包括两部分内容 --- 压缩包 + 描述压缩包的配置文件)
    • 将需要使用的包解压,放置到node_modules
    • 生成package-lock.jsonpackage.json中的版本是范围版本,不是具体版本,而package-lock.json中安装的版本是具体的实际使用的版本)

    1.2 存在

    • 检测依赖一致性(即缓存中的版本是否符合package.json中对应的范围版本)
    • 不一致 重新构建依赖关系,一致则去查找对应的缓存
    • 如果没有查找缓存,去对应的registry中下载对应的库,找到就解压压缩包放置到node_modules

package-lock.json

{
  "name": "js-advance", // 项目名
  "version": "1.0.0", // 项目版本
  "lockfileVersion": 2, // package-lock.json是第几代文件
  "requires": true, // 使用requires来跟踪模块的依赖关系 --- 也就是在requires字段中添加对应的依赖包信息 --- 配置信息的扁平化处理,方便维护和查找 同时可以更好的避免同一个版本的同一个库被多次重复下载
  "packages": {
    "": {
      "name": "js-advance",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "axios": "^0.24.0"
      }
    },
    "node_modules/axios": {
      "version": "0.24.0",
      "resolved": "https://registry.npmmirror.com/axios/download/axios-0.24.0.tgz",
      "integrity": "sha1-gE5voeS5xSiFAd2d/1anoJQNINY=",
      "license": "MIT",
      "dependencies": {
        "follow-redirects": "^1.14.4"
      }
    },
    "node_modules/follow-redirects": {
      "version": "1.14.5",
      "resolved": "https://registry.npmmirror.com/follow-redirects/download/follow-redirects-1.14.5.tgz",
      "integrity": "sha1-8JpYSJgdPHcrU5Iwl3hSP42Fw4E=",
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/RubenVerborgh"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=4.0"
      },
      "peerDependenciesMeta": {
        "debug": {
          "optional": true
        }
      }
    }
  },
  "dependencies": {
    "axios": {
      "version": "0.24.0", // 实际安装使用的版本
      "resolved": "https://registry.npmmirror.com/axios/download/axios-0.24.0.tgz", // 包对应的仓库
      "integrity": "sha1-gE5voeS5xSiFAd2d/1anoJQNINY=", // sha1秘钥,在实际操作的时候,会进行解密,最终得到索引值,通过这个索引值可以去缓存中,查找对应的配置文件,通过配置文件,可以在缓存中找到我们所需要使用到的包
      "requires": {
        "follow-redirects": "^1.14.4" // axios库所依赖的第三方库
      }
    },
    "follow-redirects": {
      "version": "1.14.5",
      "resolved": "https://registry.npmmirror.com/follow-redirects/download/follow-redirects-1.14.5.tgz",
      "integrity": "sha1-8JpYSJgdPHcrU5Iwl3hSP42Fw4E="
    }
  }
}
# npm查看缓存所在地址
$ npm config get cache

# 清除缓存
$ npm cache clean [-f]

# 查看npm镜像
npm config get registry

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

yarn

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

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

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

虽然从npm5版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn

I9A98O.png

yarn的版本管理文件是yarn.lock,虽然其中的主要配置内容和package-lock.json基本是一致的

但是yarn.lock它并不是一个配置文件,而是一个特殊格式的文件

所以yarnnpm并不推荐混合使用,容易出现版本管理异常的问题

npx

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

npx的作用非常多,但是比较常见的是使用它来调用项目中的某个模块的指令

这里我们以webpack为例

假设我们在项目中有webpack@3.6.0, 同时全局存在webpack@5.46.0

# 如果我们直接在命令行中查看webpack的命令
# 因为本地的webpack是安装在node_modules/.bin目录下的
# 所以会找到并使用全局的webpack
$ webpack --version # v5.46.0

那么如何使用项目(局部)的webpack,常见的是两种方式:

  1. 明确查找到node_module下面的webpack
$ ./node_modules/.bin/webpack --version # 3.6.0
  1. 在 scripts定义脚本,来执行webpack

scripts中定义的脚本,默认情况下,会先去node_modules/.bin下进行查找,如果找到就直接使用,如果找不到,就去全局查找,如果全局依旧不存在对应的包,那么就会报错

"scripts": {
  "webpack": "webpack --version"
}
  1. 使用npx

npx会去node_modules/.bin中查找对应的包,如果找到了就使用,没有找到就去全局下查找,如果全局下找到就直接使用,如果全局下依旧找不到,那么会将对应的包下载到一个临时的文件夹中,然后使用对应的包,等待对应的包使用完毕后,会自动移除对应的临时文件。

所以如果我们想要在命令行中使用某一个包但又不想在全局安装这个包的时候,我们就可以使用npx,临时使用一个这个包,在使用完毕以后,主动移除这个包

$ npx webpack --version # 3.6.0

发布自己的包

  1. 注册npm账号
  2. 使用npm login命令登录自己的npm账号
  3. 使用npm publish命令发布自己的库(如果已经发布,需要重新发布,那么需要先更新版本号之后再进行发布)
  4. 使用npm unpublish命令删除自己已经发布过的库
  5. 使用npm deprecate命令让自己已经发布的库过期 (在使用npm搜索对应库的时候,会有该库已过期的提示)