npm基础回顾

1,388 阅读9分钟

概述

  • npm是什么
  • 配置
  • npm install
  • 版本号规则
  • 安装过程
  • package.lock.json
  • script生命周期
  • 常见问题

npm是什么

是用nodejs写的包管理生态系统

  • 网站 www.npmjs.com/
  • 注册表(npm数据库,60W+package)
  • cli工具(命令行)

npm配置

环境变量

windows 安装完node通常会自动设置环境变量PATH

NODE_PATH = XXX\Node\nodejs
PATH = %NODE_PATH%\;%NODE_PATH%\node_modules;%NODE_PATH%\node_global;

如果不配置呢?

error:node不是内部或外部命令,也不是可运行的程序 或批处理文件。

npm配置项

查看npm配置

npm config ls 或 npm config list

image

上面同样列举了npmrc(npm用户配置文件)的地址

这里面核心的是三项

  • 当前的用户信息(username、email...)
  • registry - 镜像地址,npm包的下载源(通常会修改为淘宝镜像或者私服镜像)
  • prefix - 全局安装目录

修改全局安装目录

npm config set prefix "F:\office_software\nodejs\xxx_global"

修改镜像地址(推荐使用nrm进行多镜像管理)

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

另外一个配置就是npm缓存的目录

$ npm config get cache
C:\Users\Administrator\AppData\Roaming\npm-cache

npm-cache存放下载包的缓存(对应sha1计算后的内容也在里面)

修改npm缓存目录

npm config set cache "F:\office_software\nodejs\xxx_cache"

npm install

安装操作

npm install 或 npm i

这两个命令是一样的,一个全写一个简写

npm install 或 npm i

npm会根据package.json配置文件中的依赖配置下载安装到项目下的node_modules目录

-g 或 -global

npm i -g XXX

全局安装,安装后的包位于系统预设目录下

image

这样就安装在了C:\Users\Administrator\AppData\Roaming\npm\node_modules目录下

-S 或 --save

安装的包将写入package.json里面的dependencies,dependencies:生产环境需要依赖的库

比如:less和scss编译用的包,只有开发时才用得到,实际运行时并不用到,所以就不用放到生产环境依赖包中

-D 或 --save-dev

安装的包将写入packege.json里面的devDependencies,devdependencies:只有开发环境下需要依赖的库

安装本地包

  • 安装本地模块

假如npm_pkg包已经写好(里面只要有个package.json就可以),如

npm install ../npm_pkg

通常用来测试本地npm包

  • 将包作为一个命令行工具(cli)
npm install ../npm_demo_cli -g

npm install还有很多其他使用方式,不常用,就不一一介绍了。 具体请参照《官方文档》

npm版本号规则

版本号格式

主版本号[MAJOR].次版本号[MINOR].修订号[PATCH]

版本号递增规则如下:

  • 主版本号:当做了不兼容的 API 修改,
  • 次版本号:当做了向下兼容的功能性新增,
  • 修订号:当做了向下兼容的问题修正。先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

当主版本号升级后,次版本号和修订号需要重置为0,次版本号进行升级后,修订版本需要重置为0。

关于测试版本后缀命名:

  • alpha:初期内测版,会有很多bug, 如:1.0.0-alpha.1。
  • beta:中期内测,但会持续加入新的功能。
  • rc:Release Candidate 系统平台上就是发行候选版本。RC版不会再加入新的功能了,主要着重于除错,处在beta版之后,正式版之前

semver:semantic version 语义化版本

  • 固定版本:例如 1.0.1、1.2.1-beta.0 这样表示包的特定版本的字符串
  • 范围版本:是对满足特定规则的版本的一种表示,例如 1.0.3-2.3.4、1.x、^0.2、>1.4

语义化说明

用到的语义化字符有: ~、>、<、=、>=、<=、-、||、x、X、*

'^2.1.1' // 2.x最新版本(主版本号锁定)
'~2.1.1' // 2.1.x最新版本好(主和次版本号锁定)
'>2.1'   // 高于2.1版本的最新版本
'1.0.0 - 1.2.0' // 两个版本之间最新的版本(必须要有空格)
'*'      // 最新的版本号
'3.x'    // 对应x部分最新的版本号

npm install默认安装 ^x.x.x类型的版本,用于兼容大版本下最新的版本

npm安装过程

  • 执行工程自身preinstall

  • 确定首层依赖模块

    • 即:package.json中的 dependencies 和 devDependencies
  • 获取模块

    • 在下载一个模块之前,首先要确定其版本,这是因为 package.json 中往往是 semver版本
    • 此时如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有该模块信息直接拿即可

    如果npm-shrinkwrap.json和package-lock.json同时存在,则以npm-shrinkwrap.json为主,忽略另一个

    • 如果没有则从仓库获取(向registry查询)。如 packaeg.json 中某个包的版本是 ^1.1.0,npm 就会去仓库中获取符合 1.x.x 形式的最新版本。
  • 获取模块内容

    • 上一步会获取到模块的压缩包地址(resolved 字段),npm 会用此地址检查本地缓存,缓存中有就直接拿,如果没有则从仓库下载
  • 查找该模块依赖

    • 如果有依赖则回到第1步,如果没有则停止。
  • 模块扁平化

    • 遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃

    • 这里需要对重复模块进行一个定义,它指的是模块名相同且 semver 兼容。每个 semver 都对应一段版本允许范围,如果两个模块的版本允许范围存在交集,那么就可以得到一个兼容版本,而不必版本号完全一致,这可以使更多冗余模块在 dedupe 过程中被去掉。

      比如

      node-modules 下 foo 模块依赖 lodash@^1.0.0,bar 模块依赖 lodash@^1.1.0,则 ^1.1.0 为兼容版本。

      而当 foo 依赖 lodash@^2.0.0,bar 依赖 lodash@^1.1.0,则依据 semver 的规则,二者不存在兼容版本。会将一个版本放在 node_modules 中,另一个仍保留在依赖树里。

  • 安装模块

    • 这一步将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)。
  • 执行工程自身生命周期

    • 当前 npm 工程如果定义了钩子此时会被执行
  • 最后一步是生成或更新版本描述文件(package.json和package-lock.json),npm install 过程完成。

安装的npm有子依赖包

如果这个npm还有其他依赖包,会一并安装(只会安装dependencies部分)

devDependencies中的依赖并不会安装

安装的npm包中依赖了同名不同==小版本==的包

A@1
  |-B@1.1
  |-C@1
D@1
  |-B@1.2
E@1
  |-B@1.3

这个时候,因为解析的时候,B@1已经在根目录了 那么B@2就会成为D@1中私有模块,即

node_modules
  |-A@1
  |-B@1.3
  |-C@1
  |-D@1
  |-E@1

安装的npm包中依赖了同名不同==大版本==的包

A@1
  |-B@1
  |-C@1
D@1
  |-B@2
E@1
  |-B@2

这个时候,因为解析的时候,B@1已经在根目录了 那么B@2就会成为D@1中私有模块,即

node_modules
  |-A@1
  |-B@1
  |-C@1
  |-D@1
    |-node_modules
       |-B@2
  |-E@1
    |-node_modules
       |-B@2

后面版本的npm包,即使是之前在私有目录中安装过,也同样会重复安装

那同样版本的npm包会多次安装么

package.lock.json

npm 5版本引入了package.lock.json

即在npm install之后会在根目录下生成一个package.lock.json文件

举个例子

{
  ...
  "babel-polyfill": {
    "version": "6.26.0",
    "resolved": "https://rcnpm.zhuanspirit.com/babel-polyfill/download/babel-polyfill-6.26.0.tgz",
    "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
    "requires": {
      "babel-runtime": "6.26.0",
      "core-js": "2.6.5",
      "regenerator-runtime": "0.10.5"
    },
    "dependencies": {
      "regenerator-runtime": {
        "version": "0.10.5",
        "resolved": "https://rcnpm.zhuanspirit.com/regenerator-runtime/download/regenerator-runtime-0.10.5.tgz",
        "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
      }
    }
  }
}

结构很简单:

  • babel-polyfill: 包名字
  • version:安装的版本号
  • resolved:npm压缩包下载地址
  • integrity:sha1值(一种哈希算法)按内容计算后的值
  • requires:所依赖的包信息
  • dependencies:requires的模块中所依赖的包,且和根目录中版本不一致的包信息

那它是做什么用的?

锁定安装时包的版本号,并且可以上传到git

保证每个人安装的依赖是一样的

为什么要生成这个文件?

因为npm install -D xxx 之后,默认版本是向后兼容的

比如:

"dependencies": {
    "core-js": "^3.2.0",
}

^3.2.0 即表示3.x版本中最新的版本

也就是说package.json中锁定的其实是大版本号,而小版本每次安装都是取最新的。

假如说后面又发布了3.x更高版本,那么后面执行npm install同学安装的就是更高的版本了,比如3.5.0

当然,上面说的版本号规则都是所谓的“规则”,并没有什么强制校验的措施,遵不遵守全凭自觉。所以很有可能出现:

即使是小版本也会出现和之前版本不兼容的情况。

所以,锁定当前的npm包的版本号就很有必要,package.lock.json 也就应运而生了。

那么package.lock.json作用就是:锁定npm包小版本号

问题:那npm install时,如果package.json和package-lock.json都存在,以哪个为主?

其实只有一条规则:==如果package-lock中的版本,在package.json范围内,就以package-lock为主,否则以package.json为主==

比如:以core-js为例子

1)场景一

package.json 中是 "^3.2.0", package-lock.json 中是 "3.1.0"

兼容范围是3.2.0+版本,而3.1.0是在这范围之外(比这小),所以安装时以package.json的^3.2.0为主,就会安装3.x最新的版本(目前是3.5.0)

2)场景二

package.json 中是 "^3.1.0", package-lock.json 中是 "3.2.0"

兼容范围是3.1.0+版本,而3.2.0正符合这个范围,所以安装就按照package-lock.json中指定的3.2.0版本

npm script

生命周期

node为script指令提供了生命周期钩子

  • preXXX - XXX命令之前之前调用
  • postXXx - XXX命令之后调用

举个例子,比如package.json中:

"scripts": {
  "aaa": "echo \"Log: aaa specified\""
}

当执行下面脚本时

npm run aaa

实际上等价于际执行

if (preaaa) npm run preaaa
npm run aaa
if (postaaa) npm run postaaa

所以,进一步实验

"scripts": {
  "aaa": "echo \"Log: aaa specified\"",
  "preaaa": "echo \"Log: preaaa specified\"",
  "postaaa": "echo \"Log: postaaa specified\" & exit 1"
}

结果为:

E:\work\npm_test>npm run aaa

> npm_test@1.0.0 preaaa E:\work\npm_test
> echo "Log: preaaa specified"

"Log: preaaa specified"

> npm_test@1.0.0 aaa E:\work\npm_test
> echo "Log: aaa specified"

"Log: aaa specified"

> npm_test@1.0.0 postaaa E:\work\npm_test
> echo "Log: postaaa specified"

"Log: postaaa specified"

./node_modules/.bin/

npm run执行时,会把./node_modules/.bin/ 目录添加到执行环境的 PATH 变量中

这样,即便只在当前项目里安装了某个包,npm run一样可以调用

传参

执行 script 脚本如果需要传入参数,需要在命令后加 -- 标明, 如:

npm run dev -- -r main

将自定义参数r=main传入dev命令中

process.env

在执行的script 脚本中,可以直接使用process.env对象,它会返回所有和执行环境相关的信息

  • npm_lifecycle_event - 正在运行的脚本名称
  • npm_package_ - 当前项目 package.json 中指定字段的配置值:如 npm_package_name 获取包名
  • npm_package__ - package.json 中嵌套属性:如:npm_package_dependencies_core_js

注:获取属性时,如果是带有中划线的属性,需要改成下划线,如core-js需要改成core_js

npm常见疑问

npm cache clean --force

删除npm_cache下所有缓存文件

删除后,package-lock.json文件中就找不到对应的sha1内容了,就会强制重新从服务器拉取npm包

npm cache verify

重新计算npm_cache下所有缓存文件是否与sha1值匹配,如果不匹配可能删除

npm install发生错误(大招)

删除package.lock.json文件

npm cache clean --force强制删除缓存中的npm包