bro 你真的了解npm吗?

365 阅读7分钟

💁 这个我门清,npm run xxx, npm install ,我已经学废了

image.png

直到有一天,我遇到了一些问题,才让我重新开始学习,并了解了npm

🦄 场景1(pack)

Q:你帮我项目打个包,我在本地运行一下

A:好的,那我连node_modules 一起给你,这样你都不用安装依赖了(自己想偷懒,如果删除node_modules,给他发完包,我还得重新安装依赖)

过了一会,不行啊,我们电脑系统不一样,你的node_modules 我用不了

此刻内心OS

image.png

骂骂咧咧的删除自己的node_modules,给他发完包,又自己安装了一遍依赖

⌨️ 场景1 resolve

其实npm 提供了一个命令,可以直接将项目除node_modules 外做一个复制

npm pack <package-spec>

📄 npm pack 描述

对于任何可安装的文件(即包文件夹、tarball、tarball url、git url、name@tag、name@version、name 或作用域名称),此命令将其获取到缓存中,将 tarball 文件以 <name>-<version>.tgz 的形式复制到当前工作目录,然后将文件名写入标准输出。

如果多次指定相同的包,则该文件将在第二次被覆盖。

如果没有提供参数,npm 就会打包当前的包文件夹。

www.npmrc.cn/commands/np…

image.png image.png

这样就不用每次删除node_modules 再安装node_modules 来给同事看代码咯~

🦄 场景2 (link)

我们在本地开发了一个npm库,但是需要用项目测试,每做一点,就打一个包更新一下,在项目里测一下,一天什么都没有干,光干打包测试了

20210127165426_83208.gif

⌨️ 场景2 resolve

在本地开发npm 模块的时候,我们可以使用npm link 命令,将npm模块链接到对应的项目中去,方便对模块进行调试和测试

 npm link: 创建软连接
 npm unlink: 去掉软连接
 npm link 包名: 使用上一步创建的软连接
 npm unlink 包名: 删除引用的软连接

在npm 包 模块创建软链接到全局

image.png

在项目中连接全局的npm包

image.png

此时就可以在项目中使用npm包暴露出的属性/方法。

image.png

后续调试完,记得执行npm unlink 做销毁

📄 何为软链,在何处寻找

mac: shift + command + G

image.png 找到下面路径即可(比如这是我新增加的npm-module 软链)

image.png

🦄 场景三(view)

当我们要实现一个npm 包,首先要检测包名是否重复(查看包是否被占用)

⌨️ 场景三 resolve

name 是一个包的唯一标识,不得和其他包名重复,我们可以执行 npm view packageName 查看包是否被占用,并可以查看它的一些基本信息:

image.png

image.png 若包名称从未被使用过,则会抛出 404 错误。

🦄 场景四 限定系统/cpu

当我们要实现一个npm 包,但是要限定只能在linux下,你需要保证 windows 用户不会安装到你的模块,从而避免发生不必要的错误。

  1. 使用 os 属性可以帮助你完成以上的需求,你可以指定你的模块只能被安装在某些系统下,或者指定一个不能安装的系统黑名单:
"os" : [ "darwin", "linux" ]
"os" : [ "!win32" ]

在node环境下可以使用 process.platform 来判断操作系统。

image.png

  1. 和上面的 os 类似,我们可以用 cpu 属性更精准的限制用户安装环境:
"cpu" : [ "x64", "ia32" ]
"cpu" : [ "!arm", "!mips" ]

在node环境下可以使用 process.arch 来判断 cpu 架构。

image.png

🦄 查看npm 包版本

 npm view xxx version

image.png

🤌 查看某个 package 在npm服务器上所有发布过的版本

 npm view xxx versions

image.png

🦄 npm 快快速打开相关文档

npm docs xxx

image.png

🦄 npm 查看当前包版本状态

npm outdated

image.png

当然也可以通过查看的形式查看

image.png

前提是可以正常访问github

image.png

🤔 npm run xxx 是如何执行的

  1. 通过 package.json 找到 scripts 对应的脚本配置
"scripts": {
    "dev": "vite",
 }

首先,当执行 npm run dev 的时候,node 会去项目的 package.json 文件找到 scripts 对应的 dev 脚本配置。

  1. 通过 node_modules/.bin 找到对应的脚本文件 找到 dev 脚本配置后将执行对应的命令,也就是 vite

为了找到这个命令对应的脚本文件,Node 首先会去项目中的 node_modules/.bin 目录寻找名为 vite 的脚本文件,如果存在则执行该脚本。

如果不存在,再去全局的 /usr/local/bin 目录寻找对应文件。

如果全局目录还是没找到,那么就从 path 环境变量中查找有没有其他同名的可执行程序。

如果还没找到则抛出异常。

其实就是这段代码的执行逻辑 node_modules/.bin/vite

#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -z "$NODE_PATH" ]; then
  export NODE_PATH="/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules/vite/bin/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules/vite/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/node_modules"
else
  export NODE_PATH="/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules/vite/bin/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules/vite/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/vite@5.2.11_@types+node@20.12.10_sass@1.77.0_terser@5.31.0/node_modules:/Users/mac/Desktop/vue-project/node_modules/.pnpm/node_modules:$NODE_PATH"
fi
if [ -x "$basedir/node" ]; then
  exec "$basedir/node"  "$basedir/../vite/bin/vite.js" "$@"
else
  exec node  "$basedir/../vite/bin/vite.js" "$@"
fi

🤔 npm install 的原理

image.png 执行 npm install 后,依赖包被安装到了 node_modules ,下面我们来具体了解下,npm 将依赖包安装到 node_modules 的具体机制是什么。

🦛 嵌套结构,递归所有包

在 npm 的早期版本, npm 处理依赖的方式简单粗暴,以递归的形式,严格按照 package.json 结构以及子依赖包的 package.json 结构将依赖安装到他们各自的 node_modules 中。直到有子依赖包不再依赖其他模块

这样做会存在两个弊端

  • 在不同层级的依赖中,可能引用了同一个模块,导致大量冗余;
  • 在 Windows 系统中,文件路径最大长度为260个字符,嵌套层级过深可能导致不可预知的问题;

🦛 扁平结构

为了解决以上问题,NPM 在 3.x 版本做了一次较大更新。其将早期的嵌套结构改为扁平结构

  • 安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在 node_modules 根目录
  • 当安装到相同模块时,判断已安装的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下安装该模块。

为了让开发者在安全的前提下使用最新的依赖包,我们在 package.json 通常只会锁定大版本,这意味着在某些依赖包小版本更新后,同样可能造成依赖结构的改动,依赖结构的不确定性可能会给程序带来不可预知的问题。

🦛 lock 锁

为了解决 npm install 的不确定性问题,在 npm 5.x 版本新增了 package-lock.json 文件,而安装方式还沿用了 npm 3.x 的扁平化的方式。

package-lock.json 的作用是锁定依赖结构,即只要你目录下有 package-lock.json 文件,那么你每次执行 npm install 后生成的 node_modules 目录结构一定是完全相同的。

🦛 文件完成性

上面我们多次提到了文件完整性,那么什么是文件完整性校验呢?

在下载依赖包之前,我们一般就能拿到 npm 对该依赖包计算的 hash 值,例如我们执行 npm info 命令,紧跟 tarball(下载链接) 的就是 shasum(hash) 。

用户下载依赖包到本地后,需要确定在下载过程中没有出现错误,所以在下载完成之后需要在本地在计算一次文件的 hash 值,如果两个 hash 值是相同的,则确保下载的依赖是完整的,如果不同,则进行重新下载。

image.png

🦄 缓存

在执行 npm installnpm update命令下载依赖后,除了将依赖包安装在node_modules 目录下外,还会在本地的缓存目录缓存一份。

通过 npm config get cache 命令可以查询到:在 Linux 或 Mac 默认是用户主目录下的.npm/_cacache 目录。

在这个目录下又存在两个目录:content-v2index-v5content-v2 目录用于存储 tar包的缓存,而index-v5目录用于存储tar包的 hash。

npm 在执行安装时,可以根据 package-lock.json 中存储的 integrity、version、name 生成一个唯一的 key 对应到 index-v5 目录下的缓存记录,从而找到 tar包的 hash,然后根据 hash 再去找缓存的 tar包直接使用。

image.png

image.png

image.png

🗑️ 清除缓存

有时候安装依赖没有更新,可能是直接走缓存了,那这个时候我们就要清除npm 缓存

npm cache clean --force