💁 这个我门清,npm run xxx, npm install ,我已经学废了
直到有一天,我遇到了一些问题,才让我重新开始学习,并了解了npm
🦄 场景1(pack)
Q:你帮我项目打个包,我在本地运行一下
A:好的,那我连node_modules 一起给你,这样你都不用安装依赖了(自己想偷懒,如果删除node_modules,给他发完包,我还得重新安装依赖)
过了一会,不行啊,我们电脑系统不一样,你的node_modules 我用不了
此刻内心OS
骂骂咧咧的删除自己的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 就会打包当前的包文件夹。
这样就不用每次删除node_modules 再安装node_modules
来给同事看代码咯~
🦄 场景2 (link)
我们在本地开发了一个npm库,但是需要用项目测试,每做一点,就打一个包更新一下,在项目里测一下
,一天什么都没有干,光干打包测试了
⌨️ 场景2 resolve
在本地开发npm 模块的时候,我们可以使用npm link 命令,将npm模块链接到对应的项目中去,方便对模块进行调试和测试
npm link: 创建软连接
npm unlink: 去掉软连接
npm link 包名: 使用上一步创建的软连接
npm unlink 包名: 删除引用的软连接
在npm 包 模块创建软链接到全局
在项目中连接全局的npm包
此时就可以在项目中使用npm包暴露出的属性/方法。
后续调试完,记得执行npm unlink
做销毁
📄 何为软链,在何处寻找
mac: shift + command + G
找到下面路径即可(比如这是我新增加的npm-module 软链)
🦄 场景三(view)
当我们要实现一个npm 包,首先要检测包名是否重复(查看包是否被占用)
⌨️ 场景三 resolve
name
是一个包的唯一标识,不得和其他包名重复,我们可以执行 npm view packageName
查看包是否被占用,并可以查看它的一些基本信息:
若包名称从未被使用过,则会抛出 404 错误。
🦄 场景四 限定系统/cpu
当我们要实现一个npm 包,但是要限定只能在linux下,你需要保证 windows 用户不会安装到你的模块,从而避免发生不必要的错误。
- 使用 os 属性可以帮助你完成以上的需求,你可以指定你的模块只能被安装在某些系统下,或者指定一个不能安装的系统黑名单:
"os" : [ "darwin", "linux" ]
"os" : [ "!win32" ]
在node环境下可以使用 process.platform
来判断操作系统。
- 和上面的 os 类似,我们可以用 cpu 属性更精准的限制用户安装环境:
"cpu" : [ "x64", "ia32" ]
"cpu" : [ "!arm", "!mips" ]
在node环境下可以使用 process.arch 来判断 cpu 架构。
🦄 查看npm 包版本
npm view xxx version
🤌 查看某个 package 在npm服务器上所有发布过的版本
npm view xxx versions
🦄 npm 快快速打开相关文档
npm docs xxx
🦄 npm 查看当前包版本状态
npm outdated
当然也可以通过查看的形式查看
前提是可以正常访问github
🤔 npm run xxx 是如何执行的
- 通过 package.json 找到 scripts 对应的脚本配置
"scripts": {
"dev": "vite",
}
首先,当执行 npm run dev
的时候,node
会去项目的 package.json
文件找到 scripts
对应的 dev 脚本配置。
- 通过 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 的原理
执行
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.jso
n 文件,那么你每次执行 npm install
后生成的 node_modules
目录结构一定是完全相同的。
🦛 文件完成性
上面我们多次提到了文件完整性,那么什么是文件完整性校验呢?
在下载依赖包之前,我们一般就能拿到 npm 对该依赖包计算的 hash 值,例如我们执行 npm info
命令,紧跟 tarball(下载链接) 的就是 shasum(hash) 。
用户下载依赖包到本地后,需要确定在下载过程中没有出现错误,所以在下载完成之后需要在本地在计算一次文件的 hash 值,如果两个 hash 值是相同的,则确保下载的依赖是完整的,如果不同,则进行重新下载。
🦄 缓存
在执行 npm install
或 npm update
命令下载依赖后,除了将依赖包安装在node_modules
目录下外,还会在本地的缓存目录缓存一份。
通过 npm config get cache
命令可以查询到:在 Linux 或 Mac 默认是用户主目录下的.npm/_cacache
目录。
在这个目录下又存在两个目录:content-v2
、index-v5
,content-v2
目录用于存储 tar包的缓存,而index-v5
目录用于存储tar包的 hash。
npm 在执行安装时,可以根据 package-lock.json
中存储的 integrity、version、name 生成一个唯一的 key 对应到 index-v5
目录下的缓存记录,从而找到 tar包的 hash,然后根据 hash 再去找缓存的 tar包直接使用。
🗑️ 清除缓存
有时候安装依赖没有更新,可能是直接走缓存了,那这个时候我们就要清除npm 缓存
npm cache clean --force