2023-08-14
前端工程化
00
请注意,本文编写于 192 天前,最后修改于 23 天前,其中某些信息可能已经过时。

目录

语义化版本
版本范围
先行版本号
npm包发展
1. npm v1/v2 依赖嵌套
2. npm v3 扁平化
3. npm v5 扁平化+lock
4. yarn
5. pnpm
cnpm
命令行
nrm
nvm
npm私服&发包
本地调试开发中的npm包
发包
package.json详解
导出
辅助信息字段
开发相关字段
功能字段
非官方字段
package-lock.json
npm实用技巧
模块管理
查看模块文档
依赖锁定
npx 是什么?
npm安装或启动失败问题
如果依赖包的依赖存在问题怎么办?
npm命令行传参数

本篇文章将讲述 npm包的发展进化史,npmyarnpnpm的区别,详解package.json内容, npm命令, npm实用技巧等内容。

语义化版本

npm使用的SemVer版本规范

  • 主版本号:不兼容的 API 修改
  • 次版本号:向下兼容的功能性新增
  • 修订号:向下兼容的问题修正

版本范围

在使用第三方依赖时,我们通常会在package.json中指定依赖的版本范围,语义化版本范围规定:

  • ~:只升级修订号
  • ^:升级次版本号和修订号
  • *:升级到最新版本

先行版本号

  • 例如1.0.0-alpha.11.0.0-beta.42.0.0-rc.1等。常用的先行版本一般为alphabetarcstablecsp等。
  • Alpha: 是内部测试版,一般不向外部发布,会有很多Bug.
  • Beta: 该版本相对于α版已有了很大的改进,消除了严重的错误,但还是存在着一缺陷,需要经过多次测试来进一步消除。这个阶段的版本会一直加入新的功能。
  • RC:(Release Candidate) Candidate是候选人的意思,用在软件上就是候选版本。
  • GA:首次发行稳定的版本,也就是官方开始推荐广泛使用了
  • Release: 该版本意味“最终版本”

npm包发展

关于前端包管理器推荐阅读 《前端包管理器探究》。这篇文章写的很好比较幽默也很全面,这里我做一些个人总结。

1. npm v1/v2 依赖嵌套

这种方式非常简单,但最大的问题是依赖地狱

2. npm v3 扁平化

  • 将子依赖项安装到主项目所在的目录中(hoising提升)
  • npm的依赖查找算法也要适配--递归向上查找(有开发过Monorepo项目的同学会深有体会), 在遇到版本冲突时才会在模块下的 node_modules 目录下存放该模块子依赖,

虽然解决了依赖地狱问题,但也引入了新的问题

  • 幽灵依赖 未在package.json中定义, 但可以引用。 幽灵依赖的问题是 依赖不兼容 和 依赖缺失
  • 多重依赖 破坏单例模式 types冲突
  • 不确定性问题 部署时发现与本地node_modules不一致

3. npm v5 扁平化+lock

在npm v5中新增了package-lock.json。当项目有package.json文件并首次执行npm install安装后,会自动生成一个package-lock.json文件,该文件里面记录了package.json依赖的模块,以及模块的子依赖。并且给每个依赖标明了版本、获取地址和验证模块完整性哈希值。通过package-lock.json,保障了依赖包安装的确定性与兼容性,使得每次安装都会出现相同的结果。

解决了依赖缺失和不确定性问题

4. yarn

Yarn 是在2016年开源的,yarn 的出现是为了解决 npm v3 中的存在的一些问题,那时 npm v5 还没发布。Yarn 被定义为快速、安全、可靠的依赖管理。

与npm一样,yarn也适用本地缓存,区别是yarn还提供了离线模式

Yarn v1 lockfile yarn.lockpackage-lock.json 的大体思路一致,具体区别如下

  • 文件格式 yarn使用自定义格式 npm使用json格式
  • package-lock.json 文件里记录的依赖的版本都是确定的, 不会出现范围符号。 而 yarn.lock 文件里仍然会出现语义化版本范围符号
  • package-lock.json 文件内容更丰富,实现了更密集的锁文件,包括子依赖的提升信息
    • npm v5 只需要 package.lock 文件就可以确定 node_modules 目录结构
    • yarn.lock 无法确定顶层依赖,需要 package.jsonyarn.lock 两个文件才能确定 node_modules 目录结构。node_modules 目录中 package 的位置是在 yarn 的内部计算出来的,在使用不同版本的 yarn 时可能会引起不确定性。

Yarn v2 NPN模式

  • Node依赖node_modules查找依赖,有两个问题
    • 1 安装慢, 包含一些系列重IO操作(下载、解压、复制)
    • 2 依赖查找耗时, 递归向上查找
  • 所以Yarn反其道而行,
    • 通过全局缓存解决安装慢的问题,
    • 通过映射表确定依赖位置

pnp模式优缺点也非常明显:

  • 优:摆脱node_modules,安装、模块速度加载快;所有 npm 模块都会存放在全局的缓存目录下,避免多重依赖;严格模式下子依赖不会提升,也避免了幽灵依赖
  • 缺:自建resolver 处理Node require方法,执行Node文件需要通过yarn node解释器执行,脱离Node现存生态,兼容性不太好

5. pnpm

pnpm1.0诞生于2017年,它说它也是为了解决npmyarn存在的问题😂 ,并声称具有安装速度快、节约磁盘空间、安全性好等优点

因为依赖结构本质上是有向无环图(DAG),但是npm和yarn通过文件目录和node resolve算法模拟的是有向无环图的一个超级(多了许多错误的祖先节点和兄弟节点之间的连接。pnpm也是通过硬链接和软链接结合的方式,更加精准的模拟DAG来解决yarn和npm的问题。

硬链接与软链接

  • 硬链接 有A、B两个文件,假设B是A的硬链接,它们都指向同一个文件允许同一个文件有多个路径,无论是往A还是B文件中写内容,两者都会同步变化, 一些重要的文件通过该机制防止误删
  • 软链接 也叫符号链接 类似windows下面的快捷方式,删除原文件,快捷链接也访问不了了
  • ls source_file link_name 创建硬链接
  • ln -s source_file link_name 创建软连接

来看一下官方的一张图

image.png

  • 依赖与依赖之间是软链接关系
  • 依赖与全局缓存是硬链接关系

这里就很好的解决了幽灵依赖问题

局限性

  1. 需要使用自身的锁文件 pnpm-lock.yaml
  2. 符号链接兼容性 。存在符号链接不能适用的一些场景,比如 Electron 应用、部署在 lambda 上的应用无法使用 pnpm。
  3. 不同应用的依赖是硬链接到同一份文件,如果在调试时修改了文件,有可能会无意中影响到其他项目。
  4. 子依赖提升到同级的目录结构,虽然由于 Node.js 的父目录上溯寻址逻辑,可以实现兼容。但对于类似 Egg、Webpack 的插件加载逻辑,在用到相对路径的地方,需要去适配。

cnpm

  • cnpm是由阿里维护并开源的npm国内镜像源,支持官方npm registry 的镜像同步。
  • tnpm是在cnpm基础之上,专为阿里巴巴经济体的同学服务,提供了私有的 npm 仓库,并沉淀了很多 Node.js 工程实践方案。

image.png

  • cnpm/tnpm的依赖管理是借鉴了pnpm ,通过符号链接方式创建非扁平化的node_modules结构,最大限度提高了安装速度。安装的依赖包都是在 node_modules 文件夹以包名命名,然后再做符号链接到 版本号 @包名的目录下。与pnpm不同的是,cnpm没有使用硬链接,也未把子依赖符号链接到单独目录进行隔离

命令行

npmyarnpnpm
安装所有依赖npm installyarnpnpm 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安装常用的有两个参数

  • --save   简写  -S   写入dependencies项目运行所依赖的包
  • --save-dev  简写  -D   写入devDependencies开发过程中依赖的包
  • 没有参数 = -S
  • 记录该包基本信息(包名版本号其他描述信息)及所依赖的包的信息(最低版本号)
  • 依赖包版本号 
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

nrm

npm的镜像源管理工具

  • npm install -g nrm
  • nrm ls # 查看可选的源
  • nrm use [源名称] # 切换源
  • nrm add registry [你公司的npm私服地址] # 增加源
  • nrm del [源名称] #删除对应的源
  • nrm test [源名称] #测试相应源的响应时间

nvm

npm的版本管理

安装 以mac为例

  • 安装 nvm 的脚本 可以不用卸载系统node (注意要翻墙)
  • vi .bash_profile (在Users/youname目录下) 增加插入这端的内容
bash
export 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 [版本号] # 切换到某个版本
  • 切回 到系统node nvm use system && nvm alias default system

npm私服&发包

开源私服 verdaccio

本地调试开发中的npm包

本地开发模块调试  假设你在开发B项目,有个组件你想封装成npm包C

  • 在npm包C执行 npm link 
  • 在B项目根目录下执行 npm link C 软链接
  • 开发完成在B项目中移除软链接  npm unlink B
  • npm run env # 查看环境变量

发包

  • 进入npm包目录,执行 npm publish 命令 (可能需要npm login)
  • package.json
    • files 字段决定上传哪些内容
    • bin 创建全局命令通常在开发脚手架需要用到这个

package.json详解

导出

  • type 指定.js文件是CJS还是ESM规范,
    • 值有两种commonjsmodule 前者是默认值
    • .mjs是 ESM规范,.cjs是CJS规范,优先于type字段
  • main 指定包入口文件
  • exports
    • 优先于main字段
    • 有多种用法
      • 子目录别名,main的别名,条件加载
  • module 字段 指定ESM模块的入口,如果你想开发一个包即支持EMS又支持CommonJS,就需要配置 modulemain 两个字段

辅助信息字段

description respository autor license keywords homepage bugs contributors

开发相关字段

  • devDependencies 开发依赖 npm i XXX -D
  • dependencies 运行依赖 npm i XXX -S npm i XXX
  • peerDependencies 如果你安装我,那么你最好也安装X,Y和Z. 参考
  • optionalDependencies 可选的包
  • bundledDependencies 你的包依赖一些第三方包,你担心某个或某几个三方包发生变更导致你的包也不能用了,那么就配置这个字段包,将这些第三方包和你的包绑定起来。
  • overrides
    • 假如你的项目依赖package Apackage A依赖B@0.1.0版本,你知道依赖B存在问题,应该使用使用B@0.1.1 版本,则配置overrides:{"A":{"B":"0.1.1"}}
    • 再或者package C依赖 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 state
  • name 包名称
  • config 通过 process.env.XXX读取

功能字段

  • bin 非常重要,通常做一些脚手架包会用到如 vue-cli egg-cli,会在脚本头部插入#!/usr/bin/env node表示执行node脚本,作为前端有很多人只会一些shell命令,对shell编程不太熟,可以用node进行编程操纵shell脚本require('child_process').exec
  • man 说明帮助, 值为字符串 或字符串数组
  • private 不希望上传到npm上,用它
  • publishConfig 发布相关的配置,非常重要,比如publishConfig.registry字段就非常有用,能让你避免把包发不到其他私服(如cnpm私服,无需登录就能发包,比较恶心,不知道是不是故意这样设计的,利用别人的失误,窃取别人公司的代码)

非官方字段

  • typing/types TS解析文件入口,为用户提供更好的IDE支持
  • lint-staged 通常配置eslint校验
  • module
  • sideEffects 声明该模块是否含有副作用,为webpack TreeShake提供更大的压缩空间
  • babel
  • eslint
  • styleless
  • flat yarn install --flat 将所有依赖项安装在项目的顶层 node_modules 目录下,而不是嵌套在子目录中。
  • bowerserslist 目标环境(浏览器及node环境) 参考
  • browser 指定浏览器环境的入口,以便在打包时可以根据目标环境选择不同的文件。

参考资料

package-lock.json

package-lock.json就是锁定安装时的包的版本号,以保证其他人在npm install时大家的依赖能保持一致。需要将该文件提交至git 官方是这样说的package-lock.json。(一开始我网上查到一般不需要将改文件提交,但有些例外情况比如使用了第三方包即将废弃的api。后来朋友提醒应该文件需要提交)

npm实用技巧

模块管理

  • npm list/ls
  • npm la/ll // 相当于npm ls --long
  • npm list/ls --depth=0 // 只列出父包依赖的模块
  • npm list/ls <packageName>
  • npm view/info <packageName>
  • npm outdated
  • npm 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 会自动替换为新版本号

npx 是什么?

运行的时候,会到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知识储备都在这了

npm安装或启动失败问题

由于众所周知的问题,国内安装npm包可能失败,这里简单写一点笔记。

  • 首先最好自己有梯子
  • 努力看报错信息、
  • node-sass是个坑,如遇到这方面的报错,请切换node版本或切换node-sass版本 它与npm版本有个对应关系
  • 尝试使用cnpmyarn or pnpm试试
  • 尝试切换源、切换node版本、切换某个包或某块功能的包的版本试试

如果依赖包的依赖存在问题怎么办?

  • 参考pkg.resolutions字段 或者 pkg.overrides字段
  • 另一个方案是 使用patch-package这个包给node_modules下面的文件打补丁 具体用法点这里

npm命令行传参数

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 许可协议。转载请注明出处!