本篇文章将讲述 npm包的发展进化史,npm与yarn及pnpm的区别,详解package.json内容, npm命令, npm实用技巧等内容。
npm使用的SemVer版本规范
在使用第三方依赖时,我们通常会在package.json中指定依赖的版本范围,语义化版本范围规定:
~:只升级修订号^:升级次版本号和修订号*:升级到最新版本1.0.0-alpha.1,1.0.0-beta.4,2.0.0-rc.1等。常用的先行版本一般为alpha,beta,rc,stable,csp等。Alpha: 是内部测试版,一般不向外部发布,会有很多Bug.Beta: 该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一缺陷,需要经过多次测试来进一步消除。这个阶段的版本会一直加入新的功能。RC:(Release Candidate) Candidate是候选人的意思,用在软件上就是候选版本。GA:首次发行稳定的版本,也就是官方开始推荐广泛使用了Release: 该版本意味“最终版本”关于前端包管理器推荐阅读 《前端包管理器探究》。这篇文章写的很好比较幽默也很全面,这里我做一些个人总结。
这种方式非常简单,但最大的问题是依赖地狱
Monorepo项目的同学会深有体会), 在遇到版本冲突时才会在模块下的 node_modules 目录下存放该模块子依赖,虽然解决了依赖地狱问题,但也引入了新的问题
package.json中定义, 但可以引用。 幽灵依赖的问题是 依赖不兼容 和 依赖缺失在npm v5中新增了
package-lock.json。当项目有package.json文件并首次执行npm install安装后,会自动生成一个package-lock.json文件,该文件里面记录了package.json依赖的模块,以及模块的子依赖。并且给每个依赖标明了版本、获取地址和验证模块完整性哈希值。通过package-lock.json,保障了依赖包安装的确定性与兼容性,使得每次安装都会出现相同的结果。
解决了依赖缺失和不确定性问题
Yarn 是在2016年开源的,yarn 的出现是为了解决 npm v3 中的存在的一些问题,那时 npm v5 还没发布。Yarn 被定义为快速、安全、可靠的依赖管理。
与npm一样,yarn也适用本地缓存,区别是yarn还提供了离线模式
Yarn v1 lockfile
yarn.lock 与 package-lock.json 的大体思路一致,具体区别如下
yarn使用自定义格式 npm使用json格式package-lock.json 文件里记录的依赖的版本都是确定的, 不会出现范围符号。 而 yarn.lock 文件里仍然会出现语义化版本范围符号package-lock.json 文件内容更丰富,实现了更密集的锁文件,包括子依赖的提升信息
npm v5 只需要 package.lock 文件就可以确定 node_modules 目录结构yarn.lock 无法确定顶层依赖,需要 package.json 和 yarn.lock 两个文件才能确定 node_modules 目录结构。node_modules 目录中 package 的位置是在 yarn 的内部计算出来的,在使用不同版本的 yarn 时可能会引起不确定性。Yarn v2 NPN模式
pnp模式优缺点也非常明显:
pnpm1.0诞生于2017年,它说它也是为了解决npm和yarn存在的问题😂 ,并声称具有安装速度快、节约磁盘空间、安全性好等优点
因为依赖结构本质上是有向无环图(DAG),但是npm和yarn通过文件目录和node resolve算法模拟的是有向无环图的一个超级(多了许多错误的祖先节点和兄弟节点之间的连接。pnpm也是通过硬链接和软链接结合的方式,更加精准的模拟DAG来解决yarn和npm的问题。
硬链接与软链接
ls source_file link_name 创建硬链接ln -s source_file link_name 创建软连接来看一下官方的一张图

这里就很好的解决了幽灵依赖问题
局限性
pnpm-lock.yamlcnpm是由阿里维护并开源的npm国内镜像源,支持官方npm registry 的镜像同步。tnpm是在cnpm基础之上,专为阿里巴巴经济体的同学服务,提供了私有的 npm 仓库,并沉淀了很多 Node.js 工程实践方案。
| npm | yarn | pnpm | |
|---|---|---|---|
| 安装所有依赖 | npm install | yarn | pnpm install |
| 安装依赖 | npm install <pkg> | yarn add <pkg> | pnpm install <pkg>pnpm add <pkg> |
| 移除依赖 | npm uninstall <pkg> | yarn remove <pkg> | pnpm uninstall <pkg>pnpm remove <pkg> |
| 更新依赖 | npm update <pkg> | yarn update <pkg> | pnpm update <pkg> |
| 运行脚本 | npm run <script> | yarn run <script> | pnpm run <script> |
注: pnpm 没有全局依赖的概念(它已经做了全局缓存了)
执行npm install安装常用的有两个参数
npm i vue | "vue": "^3.3.4" |
npm i vue@latest | "vue": "^3.3.4" |
npm i vue@2 | "vue": "^2.7.14" |
npm i vue@2.1 | "vue": "^2.1.10" |
npm i vue@2.1.2 | "vue": "^2.1.2" |
除了 ^、*、~ 符号,
还支持 > >= < <= 1.2.x
npm的镜像源管理工具
npm install -g nrmnrm ls # 查看可选的源nrm use [源名称] # 切换源nrm add registry [你公司的npm私服地址] # 增加源nrm del [源名称] #删除对应的源nrm test [源名称] #测试相应源的响应时间npm的版本管理
安装 以mac为例
vi .bash_profile (在Users/youname目录下) 增加插入这端的内容bashexport NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm list #查看node版本列表nvm install [版本号] #安装某个nodejs版本nvm use [版本号] && npm alias default [版本号] # 切换到某个版本nvm use system && nvm alias default system开源私服 verdaccio
本地开发模块调试 假设你在开发B项目,有个组件你想封装成npm包C
npm link npm link C 软链接 npm unlink Bnpm run env # 查看环境变量npm publish 命令 (可能需要npm login)files 字段决定上传哪些内容bin 创建全局命令通常在开发脚手架需要用到这个type 指定.js文件是CJS还是ESM规范,
commonjs、module 前者是默认值.mjs是 ESM规范,.cjs是CJS规范,优先于type字段main 指定包入口文件exports
main字段module 字段 指定ESM模块的入口,如果你想开发一个包即支持EMS又支持CommonJS,就需要配置 module 和main 两个字段description respository autor license keywords homepage bugs contributors
devDependencies 开发依赖 npm i XXX -Ddependencies 运行依赖 npm i XXX -S npm i XXXpeerDependencies 如果你安装我,那么你最好也安装X,Y和Z. 参考optionalDependencies 可选的包bundledDependencies 你的包依赖一些第三方包,你担心某个或某几个三方包发生变更导致你的包也不能用了,那么就配置这个字段包,将这些第三方包和你的包绑定起来。overrides
package A,package A依赖B@0.1.0版本,你知道依赖B存在问题,应该使用使用B@0.1.1 版本,则配置overrides:{"A":{"B":"0.1.1"}}B@0.1.1 你期望任何地方都使用B@0.1.1, 则配置overrides:{"B":"0.1.1"}yarn中 使用 resolutions字段 具体用法点这里resolutions,再有的 overrides (npm8)engines 配置项目启动所需的node版本npm版本条件script 构建脚本 npm run XXX 执行脚本
precommit 通常配合lint-staged使用,git commit前进行检查prepush git push前进行检查,如执行单元测试lint-staged 字段version 包版本号 alpha beta rc statename 包名称config 通过 process.env.XXX读取bin 非常重要,通常做一些脚手架包会用到如 vue-cli egg-cli,会在脚本头部插入#!/usr/bin/env node表示执行node脚本,作为前端有很多人只会一些shell命令,对shell编程不太熟,可以用node进行编程操纵shell脚本require('child_process').execman 说明帮助, 值为字符串 或字符串数组private 不希望上传到npm上,用它publishConfig 发布相关的配置,非常重要,比如publishConfig.registry字段就非常有用,能让你避免把包发不到其他私服(如cnpm私服,无需登录就能发包,千万要注意这个坑!如果公司发现了解释不清)typing/types TS解析文件入口,为用户提供更好的IDE支持lint-staged 通常配置eslint校验modulesideEffects 声明该模块是否含有副作用,为webpack TreeShake提供更大的压缩空间babeleslintstyle、lessflat yarn install --flat 将所有依赖项安装在项目的顶层 node_modules 目录下,而不是嵌套在子目录中。bowerserslist 目标环境(浏览器及node环境) 参考browser 指定浏览器环境的入口,以便在打包时可以根据目标环境选择不同的文件。参考资料
package-lock.json就是锁定安装时的包的版本号,以保证其他人在npm install时大家的依赖能保持一致。需要将该文件提交至git 官方是这样说的package-lock.json。(一开始我网上查到一般不需要将改文件提交,但有些例外情况比如使用了第三方包即将废弃的api。后来朋友提醒应该文件需要提交)
npm list/lsnpm la/ll // 相当于npm ls --longnpm list/ls --depth=0 // 只列出父包依赖的模块npm list/ls <packageName>npm view/info <packageName>npm outdatednpm prune # 整理项目中无关的模块npm [home | resp | bugs | docs] <packageName>npm run dev --prefix /path/to/your/folder #在不同的目录下运行脚本npm audit [--json] # 安全漏洞检查,加上--json,以 JSON 格式生成漏洞报告npm audit fix #自动修复安全漏洞npm config set save-prefix="~"
npm默认安装模块时,会通过脱字符^来限定所安装模块的主版本号。
可以配置npm通过波浪符~来限定安装模块版本号:
当然还可以配置npm仅安装精确版本号的模块:npm config set save-exact true
项目版本号管理: 尽量使用 npm version 指令来自动更新version
npm version xxx -m "upgrade to %s for reasons" # %s 会自动替换为新版本号
运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。
由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。
./node_modules/.bin/babel src --out-dir lib
等价于 npx babel src --out-dir lib
npx可以调用 ./node_modules/.bin/下面的命令及系统命令
由于众所周知的问题,国内安装npm包可能失败,这里简单写一点笔记。
node-sass是个坑,如遇到这方面的报错,请切换node版本或切换node-sass版本 它与npm版本有个对应关系cnpm、yarn or pnpm试试patch-package这个包给node_modules下面的文件打补丁 具体用法点这里bash# server.js
# console.log(process.args)
node server --port=3100
# 输出
#[
# '/Users/XXX/node',
# '/Users/XXX/node/server',
# '--port=3100'
#]
# 或者配置pkg.script字段 `"server": "node server"`
npm run server -- --port=3100
# 可以得到同样的结果
参考资料: npm script中命令行传参数
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!