包管理器

183 阅读18分钟

概述

模块(module)

模块通常以单个文件的形式存在,一个模块只包含一部分功能,在页面中引入的模块或使用node命令运行的模块称之为入口模块或主模块

库(library)

库是由若干个模块组成的完整功能块,库可以帮助解决开发中遇到的某一方面的问题

如:jquery让操作DOM更加方便,axios使得网络传输更加简单,mockjs用于进行ajax拦截和模拟数据生成

包(package)

库(一个或多个js文件) + 相关描述信息(元数据) = 包

元数据包括:库的名称、库的描述、库的作者信息等

image.png

registry

registry是一个服务器,用于保存编写好的第三方库

第三方库的作者,按照npm的规范编写完库后,就会将其传到npm的registry服务器中

CLI

CLI(command-line interface)命令行接口

命令行接口提供了一系列的命令,调用这些命令可以获取到系统提供的某些服务

如:在命令行中输入命令npm -v,即可查看系统中npm的版本信息

npm

包的安装

npm官方的registry服务器位于国外,因此使用该服务器下载包,速度很慢

可以将npm安装包时使用的registry服务器地址修改为国内的镜像服务器地址

 npm config set registry https://registry.npm.taobao.org

可以通过下面的命令来查看是否设置完成

 npm config get registry

image.png

npm安装包的方式分为两种:

  1. 本地安装
  2. 全局安装

本地安装

使用下面命令进行本地安装

 npm install 包名

install可以简写为i

 npm i 包名

同时下载多个包

 npm i 包名1 包名2 ...

本地安装的包会出现在当前目录下的node_modules目录中

注意:

  1. node_modules目录中的内容不适合传输到生产环境中,因此应该在.gitignore文件中忽略该目录

  2. 本地安装的包只会影响当前目录以及当前目录的子目录,不会对当前目录以外的目录造成影响

  3. npm在安装包(包括本地安装和全局安装)时,若安装的包依赖了其它包,则npm也会自动将这些依赖的包也下载下来,即npm能够自动管理包依赖

  4. 若本地安装的包中带有CLI文件,npm会将该CLI文件放置到node_modules/.bin目录中,使用该CLI中的命令时,需要命令的最前面加上npx

    全局安装的包提供的CLI,其中的命令可以在任何目录中调用

  5. 若npx所执行的本地命令目前还不存在,则npm会临时本地安装该命令所属的包,然后再执行该命令,执行完后立即删除该包

全局安装

使用下面命令进行全局安装

 npm i --global 包名

--global可以简写为-g

 npm i -g 包名

全局安装的包会出现在特定的目录中

可以通过下面命令查看全局安装的包所在的目录

 npm config get prefix

默认情况下,该目录为C:\Users\...\AppData\Roaming\npm

全局安装包的目的通常是为了使用包所提供的CLI命令,如vue-cli,以便为之后的开发做准备,而不是为每一个工程提供可以共同执行的代码

包配置

任何一个包,它的信息都会记录配置文件package.json

我们开发的工程本身也应该是一个包,因此也需要有相应的package.json

可以手动创建该文件,也可以通过在工程的根目录下运行下面命令来初始化package.json

npm init

package.json中记录的信息有:

  1. name

    包的名称,该名称只能包含英文字符、数字和-

  2. version

    包的版本信息,默认为1.0.0

    版本号规范:主版本号.次版本号.补丁版本号

    主版本号:当程序发生了重大变化时,主版本号增加,重大变化包括大量新特性的加入,技术架构的改变等

    此版本号:当程序发生了细微变化时,此版本号增加,细微变化包括加入了一些辅助型的API等

    补丁版本号:当解决了程序中的一些bug,或进行了一些局部优化时,补丁版本号增加

    有些版本号在后面还可能会跟上一些补充信息,如-alpha-beta-rc

    其中-alpha-beta都表示不太成熟的当前版本,-rc表示较为成熟的当前版本,如果什么都不加,表示当前版本已经非常成熟,实际使用时也最应该使用这种版本号的版本

  3. description

    包的描述信息

  4. homepage

    官网地址

  5. repository

    包的仓库地址,通常为包在git或svn中的地址

  6. main

    包的入口文件,默认为index.js

    使用该包的人默认从该入口文件导入包的内容

  7. keywords

    搜索关键字

    当包发布到npm上后,需要通过该关键字来搜索到此包的内容

  8. author

    包的作者信息

上面的配置信息,只有在需要将包发布到npm上时才有用,而使用npm init初始化配置文件时,就需要手动输入上面的信息

如果不需要将包发布出去,那么也就不需要填写上面的信息,此时可以使用下面的命令来让npm生成只包含默认配置的package.json

npm init --yes

--yes可以简写为-y

npm init -y
配置依赖关系

package.json中还有两个非常重要的配置:dependencies和devDependencies,这两个配置用于记录当前工程的依赖关系

依赖包括:

  • 生产环境依赖,即dependencies

    生产环境依赖是指在开发过程中,以及开发完成后都需要使用到的依赖

    生产环境依赖也称为普通依赖

  • 开发环境依赖,即devDependencies

    开发环境依赖是指仅在开发过程中需要使用到的依赖

{
    "name": "",
    "version": "",
    "dependencies": {
    	"jquery": "1.1.1",
    	"axios": "1.2.3"
	},
	"devDependencies": {
        "mocha": "2.3.4"
    }
}

当配置好工程的依赖关系后,可以在与package.json的同级目录命令行下使用下面的命令来为工程安装所有的依赖(包括生成环境依赖和开发环境依赖)

npm i

使用下面的命令来安装所有的生产环境依赖

npm i --production

当使用命令npm install 包名安装本地包时,会自动将对该包的依赖记录到package.json的dependencies配置中

在旧版本的npm中,安装生产环境依赖时默认是不会往package.json中自动加入依赖关系,需要加入--save-S参数才能将依赖记录进去

npm i -S 包名

若想把安装的本地包作为开发依赖保存到package.json中,应该使用下面命令

npm i --save-dev 包名

--save-dev可以简写为-D

npm i -D 包名

其实对于不将包发布到npm的开发者来说,开发依赖和普通依赖并没有什么区别,因为无论是开发依赖还是普通依赖,只要是在模块中导入的依赖,模块中的代码经过webpack打包过后都会加入到打包结果中

但对于要发布包给别人使用的作者来说,他们才需要关心一个库应该是普通依赖还是开发依赖

node查找包的顺序

require函数中的路径如果不以./../开头,而直接书写包的名称时,nodejs就会按照下面的顺序查找包:

require("lodash")
  1. 查找是否有内置模块lodash
  2. 如果没有内置模块lodash,则查找模块所在当前目录下的node_modules目录中是否有lodash.js文件
  3. 若没有lodash.js文件,则查找模块所在当前目录下的node_modules目录中是否存在lodash目录,并查找该目录中是否有相应的入口文件
  4. 若没有lodash目录,或有lodash目录但没有相应的入口文件,则返回上一级目录,然后对该目录重复2和3两个步骤,直至查找磁盘的根目录
  5. 若最终都没有找到该模块,nodejs就会抛出Cannot find module 'lodash'错误
入口文件

包的入口文件信息一般都会记录在包的package.json中,其中的main配置就是入口文件的名称

若没有package.json,或package.json中没有记录main配置,或没有与main配置相对应的文件,则默认使用index.js作为包的入口文件

main字段中填写的是入口文件相对于包的根目录的路径

语义版本

版本号规范:主版本号.次版本号.补丁版本号

主版本号:当程序发生了重大变化时,主版本号增加,重大变化包括大量新特性的加入,技术架构的改变等

次版本号:当程序发生了细微变化时,此版本号增加,细微变化包括加入了一些辅助型的API等

补丁版本号:当解决了程序中的一些bug,或进行了一些局部优化时,补丁版本号增加

当一个包的主版本号发生变化时,说明该包一定是发生了重大的变化,这很有可能会造成一些兼容性问题,而次版本号或补丁版本号更新一般不会造成兼容性问题

因此当使用一个其它包时,往往都选择主版本号固定的版本,而允许次版本号和补丁版本号进行变化

可以通过配置package.json来达到此目的:

符号描述示例示例描述
固定某个版本1.2.1固定为1.2.1版本
大于某个版本>1.2.1大于1.2.1版本
>=大于等于某个版本>=1.2.1大于等于1.2.1版本
<小于某个版本<1.2.1小于1.2.1版本
<=小于等于某个版本<=1.2.1小于等于1.2.1版本
-介于两个版本之间1.2.1 - 1.4.5介于1.2.1和1.4.5版本之间
x不固定版本号1.3.x保证主版本号为1,次版本号为3
~补丁版本号可增~1.3.4保证主版本号为1,次版本号为3,补丁版本号大于等于4
次版本号和补丁版本号可增^1.3.4保证主版本号为1,次版本号大于等于3,补丁版本号大于等于4
*最新版本*始终安装最新版本,等价于latest

例:

{
    "dependencies": {
        "qrcode": "^1.4.4"
    },
    "devDenpendencies": {
        "webpack": "~5.0.0" 
    }
}

由于包与包之间的依赖关系十分复杂,就使得工程所依赖的某个包只是进行了小小的更新,仍然可能导致整个工程无法使用

因此在使用npm安装包时,它会自动生成一个package-lock.json文件,该文件中会自动记录工程的所有的依赖以及依赖的具体版本号

当工程被移植到了其它地方时,则在还原依赖的过程中如果发现工程中包含package-lock.json,会根据该文件中记录的确切版本的依赖关系进行包的安装,这能最大限度地避免差异

npm的差异版本处理

默认情况下,工程所依赖的包,以及该包所依赖的其它包,都会处于同一层级的目录中,这称之为依赖拍扁(flat dependency)

但如果遇到了两个包,这两个包所依赖的是不同版本的同一个包,则npm会将不同版本的包作为需要依赖的包的子包存在,而不会与其处在同一层级

image.png

├── node_modules
│   ├── a 
│   │   ├── node_modules
│   │   │   ├── c 1.4.0
│   │   │   |   |—— c包的文件
│   │   │── a包的文件     
│   ├── b 
│   │   ├── node_modules
│   │   │   ├── c 1.2.3
│   │   │   |   |—— c包的文件
│   │   │── b包的文件           

npm脚本

在工程的开发过程中通常需要使用到各种各样的命令,而记忆这些命令往往是非常耗时的

npm考虑到了这一点,它允许开发者自己配置命令,并使这些自定义的命令与第三方库提供的命令相关联,今后就可以通过调用自定义配置的命令来间接调用库命令

在package.json中,可以加入scripts配置,在该配置中就可以让自定义命令与库命令相关联

{
    "scripts": {
        "start": "node ./index.js"
        // start为自定义命令的名称,调用该自定义命令就相当于在该package.json所在的目录下调用node ./index.js
    }
}

通过下面的方式调用自定义命令

npm run start
细节
  1. scripts配置中的相对路径,是相对于该package.json的路径

  2. npm对一些常见的自定义命令名称进行了简化,下面的自定义命令名称在调用时可以不加run

    ① start

    ② stop

    ③ test

    npm start
    
  3. 在scripts配置中所关联的本地第三方库命令,可以省略npx

  4. 若没有加入scripts配置,或scripts配置中没有书写start自定义命令,则start默认为node server.js

运行环境配置

我们编写的代码可能会在许多种不同的环境下运行:

  1. 开发环境
  2. 生产环境
  3. 测试环境

而针对不同的环境,可能要进行不同的处理

而在代码中应该如何判断当前所处的环境呢,就需要使用到操作系统的环境变量信息

在nodejs中,可以使用process.env来获取到系统的所有环境变量

通过读取process.env中的某个环境变量,就可以判断当前环境的哪个环境

而运行环境变化时,也只需要将该环境变量进行修改即可

比如:设置一个环境变量NODE_ENV来记录当前代码的运行环境,使用development来表示开发环境,使用production来表示生产环境,使用test来表示测试环境

而在代码中,只需要进行下面的判断即可

var env = process.env.NODE_ENV;
if(env === "development"){
    ...
}else if(env === "production"){
    ...
}else if(env === "test"){
    ...
}

不过上面的做法是永久生效的,即环境变量是永久存在于系统中的,并且修改环境变量时还需要进入高级系统设置中进行更改,因此更多时候是选择临时在环境变量中设置一个信息,用完就马上删除即可

使用下面的命令即可在window系统中临时加入或设置一个环境变量(只在window系统中支持)

set 环境变量名=环境变量值

可以使用&&连接多个命令,就可以在命令行中一次性运行多个命令(多种操作系统都支持)

命令1&&命令2&&命令3...

可以在package.json的scripts配置中添加如下几个自定义命令

{
    "scripts": {
        "start": "set NODE_ENV=development&&node index.js",
        "build": "set NODE_ENV=production&&node index.js",
        "test": "set NODE_ENV=test&&node index.js"
    }
}

在上面的命令中,临时设置了环境变量NODE_ENV为某个值,并且立即执行工程的入口文件,然后立即还原该临时环境变量

如果当前环境是生产环境,只需要在命令行中输入npm run build即可启动工程并让其在生产环境下运行,开发环境下输入npm run start,测试环境下输入npm test即可

不过使用set临时设置环境变量只在window系统下有效,在macOS系统中,需要将set更改为export

有一个第三方库专门用于这方面的问题,它就是cross-env

向工程中安装cross-env库,将其作为开发依赖

npm i -D cross-env

然后修改package.json的scripts为如下内容即可

{
    "scripts": {
        "start": "cross-env NODE_ENV=development node index.js",
        "build": "cross-env NODE_ENV=production node index.js",
        "test": "cross-env NODE_ENV=test node index.js"
    }
}

更多npm命令

  1. 精确安装最新版本,且package.json中也将使用精确的版本号进行版本记录

    npm i --save-exact 包名
    

    --save-exact可以简写为-E

    npm i -E 包名
    
  2. 安装指定版本

    npm i 包名@版本号
    
  3. 查询包的安装路径

    npm root [-g]
    

    查看本地包的安装路径

    npm root
    

    查看全局包的安装路径

    npm root -g
    
  4. 查看包的所有信息

    npm view 包名
    

    查看包的名称

    npm view 包名 name
    

    查看包的所有版本

    npm view 包名 versions
    

    查看包的所有依赖

    npm view 包名 dependencies
    
  5. 查询已安装的包(依赖深度默认为0,即不会显示已安装包的依赖包)

    npm list [-g] [--depth=依赖深度]
    

    查询已安装的本地包

    npm list [--depth=依赖深度]
    

    查询已安装的全局包

    npm list -g [--depth=依赖深度]
    
  6. 检查有哪些包可以更新

    npm outdated
    
  7. 根据package.json中记录的版本号更新规则将包更新为“最新版”

    npm update [-g] [包名]
    
  8. 卸载包(被卸载的包所依赖的其他包,如果没有被工程中的其他包依赖,那么也将被卸载)

    npm uninstall [-g] 包名
    

    uninstall可以简写为un

    npm un [-g] 包名
    

npm的模块安装机制

  1. npm首先会检查本地的node_modules目录中是否已经安装过该模块,如果已经安装,则不再重新安装
  2. 若本地没有,则npm会检查缓存中是否有相应模块,如果有,就从缓存中读取并安装
  3. 如果本地和缓存中均不存在,npm就会从registry上下载包,然后将其安装到本地的node_modules目录中,同时缓存起来

npm缓存的相关命令:

  1. 清除缓存

     npm cache clean -f
    
  2. 获取缓存的存放位置

     npm config get cache
    
  3. 设置缓存的存放位置

     npm config set cache "缓存存放路径"
    

yarn

核心命令

  1. 初始化

    yarn init [--yes/-y]
    

    使用yarn生成的package.jsonnpm完全相同

  2. 添加包

    yarn [global] add 包名 [--dev/-D] [--exact/-E]
    
  3. 根据package.json安装依赖

    yarn install [--production/--prod]
    
  4. 运行自定义命令或本地CLI

    yarn run 自定义命令名称/本地CLI命令名称
    

    start、stop、test可以省略run

  5. 查询bin目录(CLI所在目录)

    yarn [global] bin
    
  6. 查询包信息

    yarn info 包名 [子字段]
    

    子字段就是name、dependencies、versions等

  7. 列举已安装的依赖

    yarn [global] list [--depth=依赖深度]
    
  8. 列举所有可以更新的包

    yarn outdated
    
  9. 更新包

    yarn [global] upgrade [包名]
    
  10. 卸载包

    yarn [global] remove 包名
    

其它命令

  1. 检查package.json文件中的依赖版本记录与lock文件中的版本记录是否一致

    yarn check
    
  2. 检查本地安装的包有哪些已知漏洞

    yarn audit
    

    漏洞的级别有以下几种:

    ① INFO:信息级别

    ② LOW:低级别

    ③ MODERATE:中级别

    ④ HIGH:高几倍

    ⑤ CRITICAL:关键级别

  3. 搭建工程时一般需要使用到一些现成的脚手架工具,而大部分脚手架都是以create-xxx命名的,比如react官方的脚手架名称为create-react-app

    #全局安装脚手架
    yarn global add create-react-app
    
    #使用脚手架搭建工程
    create-react-app my-app
    

    若脚手架名称以create-开头,则可以使用下面的命令来代替,下面的命令相当于上面的两条命令

    yarn create react-app my-app
    

    若已经在全局安装了脚手架,则只相当于第二条命令

nvm

nvm并非包管理器,它是用于管理多个node版本的工具

nvm会提供一些CLI命令,用于管理node版本

最新版下载地址:github.com/coreybutler…

下载nvm-setup.zip后,直接安装

常用命令

  1. 安装指定版本的node,以及对应的npm

    nvm install 版本号
    
  2. 列出已安装的node版本

    nvm list
    
  3. 切换到已安装的指定node版本,npm和全局包也会被切换

    nvm use 版本号
    
  4. 卸载已安装的指定版本的node,以及对应的npm

    nvm uninstall 版本号
    
  5. 默认nvm是使用国外的服务器来安装node和npm,因此速度很慢,可以配置它们的下载源为国内的镜像源

    nvm node_mirror https://npm.taobao.org/mirrors/node/
    nvm npm_mirror https://npm.taobao.org/mirrors/npm/
    
  6. 列出node已发布的所有版本

    nvm list available
    

    LTS列:最近发布的稳定的node版本

    CURRENT列:最近发布的node版本(不一定是稳定的)

npx

运行本地命令

npx是伴随着node一起安装的,无需另外单独安装

使用npx 命令时,它会首先从本地工程的node_modules/.bin目录中寻找是否有对应的命令

例如:

npx webpack

上面这条命令寻找本地工程的node_modules/.bin/webpack

如果将命令配置到package.json的scripts中,则可以省略npx前缀

临时下载执行

当使用npx执行某个不存在的命令(即该命令既不是本地工程的命令,也不是全局命令)时,则npx会把命令对应的包下载到一个临时目录,下载完成后立即执行,而临时目录中的命令会在适当的时候删除

例如:

npx prettyjson 1.json

npx会下载prettyjson包到临时目录,然后运行该命令

若包提供的命令名称和包名不对应时,需要手动指定包名

例如:

@vue/cli是包名,该包提供的命令名称为vue,两者不一致,因此需要使用下面的命令

npx -p @vue/cli vue create vue-app

-p后面的@vue/cli是包的名称

npm init

npm init通常用于初始化工程的package.json文件,有时也可以充当npx的作用

npm init 包名 # 等效于 npx create-包名
npm init @命名空间 # 等效于 npx @命名空间/create
npm init @命名空间/包名 # 等效于 npx @命名空间/create-包名