Skip to content

前端如何统一开发环境

2024年1月18日  6分钟

统一不同的同事之间,本地和 CI 之间的开发环境有利于保证运行效果一致和 bug 可复现,本文聚焦于前端最基本的开发环境配置:nodejs 和 包管理器。

nodejs

首先推荐使用 fnm 管理多版本 nodejs。

对比 nvm

使用 fnm 一定要记得开启根据当前 .nvmrc 自动切换对应的 nodejs 版本,也就是在在 .zshrc 中加入:

eval "$(fnm env --use-on-cd)"

包管理器

尽管 npm 一直在进步,甚至 7.x 已经原生支持了 workspace。但是我钟爱 pnpm,理由:

推荐使用 Corepack 管理用户的包管理器,其实我一开始知道有 corepack 这个 nodejs 官方的东西的时候,我就在想:为啥不叫 npmm(node package manager manager) 呢?

corepack 目前官方觉得功能没稳定,所以默认没开启,需要用户通过 corepack enable 手动开启,相关的讨论:enable corepack by default

有了 corepack 我们就可以轻松的在 npm/yarn/pnpm 中切换,安装和更新不同的版本。还有一个非常方便的特性就是通过在 package.json 中声明 packageManager 字段例如 "[email protected]",当我们开启了 corepack,cd 到该 package.json 所在的 package 的时候,运行 pnpmcorepack 会使用 8.14.1 版本的 pnpm

corepack 是怎样做到的呢?nodejs 安装文件夹有个的 bin 目录,这个目录会被添加到 path 环境变量,其中包含了 corepack 以及 corepack 支持的包管理器的可执行文件:

 tree ../../Library/Caches/fnm_multishells/17992_1705553706619/bin
../../Library/Caches/fnm_multishells/17992_1705553706619/bin
├── corepack -> ../lib/node_modules/corepack/dist/corepack.js
├── node
├── npm -> ../lib/node_modules/npm/bin/npm-cli.js
├── npx -> ../lib/node_modules/npm/bin/npx-cli.js
├── pnpm -> ../lib/node_modules/corepack/dist/pnpm.js
├── pnpx -> ../lib/node_modules/corepack/dist/pnpx.js
├── yarn -> ../lib/node_modules/corepack/dist/yarn.js
└── yarnpkg -> ../lib/node_modules/corepack/dist/yarnpkg.js

可以看到 pnpm 被链接到了 corepack 的一个 js 文件,查看 corepack/dist/pnpm.js 内容:

#!/usr/bin/env node
require('./lib/corepack.cjs').runMain(['pnpm', ...process.argv.slice(2)]);

可以看到其实 corepack 相当于劫持了 pnpmyarn 命令,然后根据 packageManager 字段配置自动切换到对应的包管理器,如果已经安装过就使用缓存,没有就下载。

怎样统一 nodejs 和 包管理器

问题

虽然我在项目中配置了 .nvmrc 文件,在 package.json 中声明了 packageManager 字段,但是用户可能没有安装 fnm 以及配置根据 .nvmrc 自动切换对应的 nodejs,还有可能没有开启 corepack,所以同事的环境还是有可能和要求的不一致。我一向认为,不应该依靠人的自觉去遵守规范,通过工具强制去约束才能提前发现问题和避免争论。

解决办法

最开始是看到项目中使用了 only-allow 用于限制同事开发时只能用 pnpm,由此我引发了我一个灵感,为什么我不干脆把事情做绝一点,把 nodejs 的版本也给统一了

于是我写了一个脚本用于检查用户本地的 nodejs 的版本,包管理器的版本必须和要求一致。最近封装成了一个 cli:check-fe-env。使用方式很简单,增加一个 preinstall script:

{
  "scripts": {
    "preinstall": "npx check-fe-env"
  }
}

工作原理

获取当前环境的 nodejs 版本很简单,可以用 process.version。想要获取执行脚本时的包管理器可以通过环境变量:process.env.npm_config_user_agent,如果一个 npm script 是通过 pnpm 运行的,那么这个环境变量会被设置为例如 pnpm/8.14.1 npm/? node/v20.11.0 darwin arm64,由此我们可以获取当前使用的包管理器和版本。

为了加快安装速度,我特意把源码和相关依赖给一起打包了,整个 bundle 大小 8k 左右。

局限性

最新的 npmpnpm 目前貌似都有一个 bug,都是安装完依赖才执行 preinstall hooks,具体看这: Preinstall script runs after installing dependencies

这个方案对于 monorepo 项目或者说不需要发包的项目是没啥问题的,但是不适用于一个要发包的项目。原因是 preinstall script 除了会在本地 pnpm install 时执行,别人安装这个包,也会执行这个 preinstall script,就和 vue-demi 用的 postinstall script 一样。主要是确实没找到一个:只会在本地运行 pnpm install 后且在安装依赖前执行的 hook。