Skip to content

npm 下载包流程

  1. 检查项目中是否存在package-lock.json 文件,如果有,比较和package.json和lock.json(一般都不一致),如果版本兼容则使用lock.json 定义的依赖,如果不兼容使用package.json 并更新。
  2. 检查缓存,无缓存使用,registry中获取包的tgz压缩包,下载(npm-registry-fetch)到本地缓存目录。
  3. 从缓存目录解压到node_modules目录,tgz是一个package目录,解压时未包含package目录

npm 本地添加离线包

公司处于内网环境无法下载包,需要自行添加包。

  1. 使用npm-pack-all 制作tgz离线包。
  2. 上传离线包到npm仓库
  3. 网络下载或者使用npm i xxx.tgz安装

yarn 常用命令

npm i => yarn npm i xx => yarn add xx -S npm i xx -S => yarn add xx npm i xx -D => yarn add xx -D npm i -g xx => yarn global add xx npm uninstall xx => yarn remove xx

mutilRepo 和 monoRepo

当团队项目变多,而且各个项目存在关联时,就需要采用monoRepo方式管理。传统的muti的lRepo将项目分散到各个仓库。会出现如下痛点

  • 不能批量操作,操作某个项目需要到相应的目录下操作。项目多起来简直就是地狱
  • 比如一个组件库升级了版本,需要在组件库修改版本号,发布npm。然后业务库再更新package.json 重新安装依赖。
  • 组件库如果是break change 的更新,业务库可能会忘记及时更新

monoRepo出现会解决这些痛点,可以在一个地方对所有仓库进行,仓库相互之间能互相感知。

npm 痛点

npm1 npm2项目下根据项目的package.json下载依赖到node_module,如果依赖存在依赖其他的情况会在node_modules/xxx/node_modules, 而且递归下去。 出现问题是大量重复的包被安装,而且文件目录过生对于查找耗时严重。

npm3 后优化了结构扁平化处理,如果一个依赖没在项目node_modules 出现,依赖的依赖就会安装在项目的node_modules 中,如果两个依赖同时依赖一个版本不一致的库,先安装的依赖在项目下,后安装按照npm1 npm2 处理。这样做也有问题 1.扁平的算法耗时严重。2. 项目未在package.json 中声明的依赖也可以直接引用,会出现不确定性(往往是自动补全代码带来的)。 3. 对于相同依赖的不同版本,完全是按照安装先后顺序来的,会有不确定的情况,这种会对缓存有较大影响(后续的package-lock.json专门解决这个)

pnpm 解决的痛点

pnpm通过软链接的方式把扁平的结构放到.pnpm目录中,同时.pnpm 通过npm2 的方式保证了包的正确性,子package中的node_module也是引用的root node_module/.pnpm的包。

pnpm

常用的命令

  • pnpm help xxx 查询各cli使用

  • pnpm config set xx=xxx --location=project | user | global

  • pnpm xxx 自动查询找package中定义的脚本,如果未查询到当做shell命令执行,pnpm pwd => /xx/xx/xx

  • pnpm install,安装整个项目包括子项目的依赖, --frozen-lockfile 保证安装依赖不会更新lockFile。-P 只安装Dependencies依赖, -D 只下载devDependencies依赖。

  • pnpm add xx 安装某个依赖,默认会添加依赖到dependencies, -E 明确版本(必备) -D 保存到devDependencies -P 添加到peerDependencies -O 添加到可选依赖 -W 安装到项目根目录
    当运行安装依赖时默认会在子集中查找是否有满足条件的包,如果有引用子项目的,通过--workspace 指定包只能在子项目中查找。

  • npm update 更新某个包,也用于更新子项目。

  • pnpm env use 16 -g 代替nvm功能安装包,也支持在项目的npmrc 文件中配置use-node-version=16 保证不同的项目使用不同盖尔nodejs

  • pnpm publish -F @xunserver/test 发布某个子项目,发布项目时,会替换package中workspace协议的依赖。--public=access 发布公共项目 --dry-run 仅执行发布流程但是不发布 --no-git-checks 不检查git --public-branch 设置发布的分支 通过packagejson中的publishConfig字段可以定义在发布时替换相应的package字段

  • pnpm pack 将需要publish的包xxx.gz输出到本地目录

npm_config

pnpm 复用.npmrc 配置文件,大部分配置都能在cli指定通过选项指定,部分不能指定的配置在.npmrc中key=value,如果需要在cli动态指定参数有两种方案添加npm_config_key=value 的环境变量,二是添加选项--config.key=value 常用的npm_config 有如下

  • registry=http://xxxx/ 制定依赖安装的仓库,支持@bable:registry=xxx,指定某个包(或者组织)只按照某个仓库安装
  • \<URL\>:_authToken url 是仓库的地址,_authToken 支持从环境变量获得${xxx}
  • \<URL\>:tokenHelper tokenHelper指向一个可以执行的文件,通过文件返回的结果作为token。
  • use-node-version=16.1.1 指定项目运行需要的nodejs版本
  • node-mirror:<releaseDir> 默认值 https://nodejs.org/download/\<releaseDir>/ 指定pnpm env use 的下载的nodejs目录

packageJSON需要关注的字段

  • engines 指定项目的nodejs版本和pnpm版本
json
{
  "engines": {
    "node": ">=10",
    "pnpm": ">=3"
  }
}
  • publishConfig 发布时替换packageJSON中字段

pnpm 解决patch问题

有时候用的第三方依赖有bug,需要我们自行调整,使用package alias简单实现,下载第三方源码(比如xxx1),修改编译发布到自己的仓库(取了了新名字xxx2)。pnpm add xxx1@npm:xxx2 即可把对xxx1的引用全部换成xxx2。

pnpm 命令补全

pnpm install-completion

用好pnpm的关键 --filter -F

pnpm生产实战

假设项目中存在packageA和packageB,其中packageA依赖于packageB。面临着如下的问题

  1. packageA在构建生产环境时,使用本地构建的packageB还是发布到环境的packageB
  2. 如果是CI在构建,是否需要在CI环境上构建packageB
  3. 本地构建开发环境时,如何引入packageB的包,引入的是构建前的包还是构建后的包
  4. 本地开发环境如何对packageB的代码进行调试
  5. packageA需要依赖packageB的老版本,但是当前项目中packageB是新版本,如何解决

packageJson中依赖通过workspace: x.x.x 指定,如果当前项目需要workspace中的版本,则通过这种方式指定,如果当前项目依赖的是老版本,则通过删除workspace的指定

pnpm -F 支持依赖的层级的选择,pnpm -F @xunserver/test... run build。会依次的查找@xunserver/test 依赖的同时存在项目中的包执行build,比如@packageA定义了依赖packageB, npm -F @packageA... run build 会先执行packageB的build,在执行packageA的build

问题一:解决如下,如果依赖的packageB已经发布,同时项目中的packageB比较新,packageA依赖的旧版本的packageB,则应该修改packageA的package删除workspace协议。如果需要使用的packageB是本地最新的,则按照workspace的方式使用,如果使用的是打包后的B,直接在本地执行B的打包

问题二:解决方案同问题一,主要看是否需要使用workspace中的代码,如果需要使用workspace的代码,使用依赖层层构建的方式

问题三 :这个情况有两种场景,一般来说引入packageB不会引入B的源码,都是引入的构建后的代码,因为packageB和packageA不一定使用的相同的构建工具和流程,但是也有packageB的是packageA的子项目,这个时候也就不存在packageB源码的生产代码有区别,直接引用即可, 如果packageB引入的是构建后代码,那么调试的时候就没法找到是哪个文件出错,需要引入sourcemap的调试方法,同时在vscode中源码映射的方式来实现调试。还有个问题需要解决的是,packageB每次调试改动后如何通知packageA重新构建,将watch选项添加到生产环境的构建流程中,新增production:watch的构建选项

问题四:本地开发时如何对packageA的依赖项packageB进行调试,packageB在构建时始终构建sourcemap,特别是在本地开发环境中。针对线上环境,通过调整packageA构建的参数来屏蔽packageB的sourcemap是否引入。packageB构建参数sourcemap后,参考前面的devtools通关秘籍进行调试

问题五:前面已经论述

pnpm 发布流程控制

只允许pnpm

json
{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

n 包管理器是啥

ni xx -S => npm i xxx -S nr => npm run nrm => npm uninstall nx => npm execute

n 会自动检查项目下存在package-lock.json、yarn.lock或者pnpm-lock.yaml 自动选择包管理器安装。

.npmrc、.yarnrc和.pnpmrc

npmrc格式是key=value的形式 value和key有空格需要用""包起来 yarnrc格式是key value 的形式,有空格需要用""包裹 pnpmrc使用的npmrc配置

packageJson中需要关注的字段

dependencies: 生产依赖,总是会被安装 dev dependencies: 如果这个包出现在首应用中,总是会被安装,如果是作为另一个包的依赖,则不会安装,比如A依赖了B,B中定义dev dependencies的不会被安装 peer dependencies:声明这个包不是我需要直接安装的,我只是需要这个包,要求依赖方自行安装依赖。npm7后也会自动安装。安装过程如果碰到重复依赖不能处理的情况会报错,添加--legacy-peer-dep 会忽略冲突的依赖,以先安装的为准