从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器

若川大约 7 分钟

从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器

1. 前言

大家好,我是若川open in new window。最近组织了源码共读活动open in new window,感兴趣的可以加我微信 ruochuan12open in new window 参与,或者关注我的公众号若川视野open in new window,回复“源码”参与。已进行三个月,大家一起交流学习,共同进步,很多人都表示收获颇丰。

想学源码,极力推荐之前我写的《学习源码整体架构系列》open in new window 包含jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4koa-composevue 3.2 发布vue-thiscreate-vue玩具vite等10余篇源码文章。

本文仓库 only-allow-analysis,求个star_open in new window

最近组织了源码共读活动open in new window,每周大家一起学习200行左右的源码。每周一期,已进行到14期。于是搜寻各种值得我们学习,且代码行数不多的源码。

阅读本文,你将学到:

1. 如何学习调试源码
2. 学会 npm 钩子
3. 学会 "preinstall": "npx only-allow pnpm" 一行代码统一规范包管理器
4. 学到 only-allow 原理
5. 等等

2. 场景

我们项目开发时,常需要安装依赖,虽说一般用文档可以说明。但不是比较强制的约束。是人就容易犯错或者疏忽,假如规定是用的npm,而团队里有人某一天不小心使用了其他包管理器安装了的其他依赖,上传了代码,严重时可能导致线上问题。所以我们需要借助工具(代码)来强制约束。

源码共读第12期open in new window中,我们学习了尤雨溪推荐神器 ni ,能替代 npm/yarn/pnpm ?简单好用!源码揭秘!open in new window 根据锁文件自动匹配相应的包管理器,运行相应的命令。

源码共读第3期open in new window中,我们学习了Vue 3.2 发布了,那尤雨溪是怎么发布 Vue.js 的?open in new window

其中 Vue3 源码用了 npm 的 preinstall 钩子open in new window 约束,只能使用 pnpm 安装依赖。我们接着来看其实现。

3. Vue3 源码 && npm 命令钩子

// vue-next/package.json
{
  "private": true,
  "version": "3.2.22",
  "scripts": {
    "preinstall": "node ./scripts/preinstall.js",
  }
}
依次执行
# install 之前执行这个脚本
preinstall
# 执行 install 脚本
install
# install 之后执行这个脚本
postinstall

当然也支持自定义的命令。

更多可以查看官方文档钩子open in new window

接着我们来看 preinstallopen in new window 源码。

// vue-next/scripts/preinstall.js

if (!/pnpm/.test(process.env.npm_execpath || '')) {
  console.warn(
    `\u001b[33mThis repository requires using pnpm as the package manager ` +
      ` for scripts to work properly.\u001b[39m\n`
  )
  process.exit(1)
}

这段代码也相对简单,校验如果不是 pnpm 执行脚本则报错,退出进程。

关于 process 对象可以查看 阮一峰老师 process 对象open in new window

process.argv 属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是 node,第二个成员是脚本文件名,其余成员是脚本文件的参数。

这段代码能解决文章开头场景提出的问题,但是总不能每个项目都复制粘贴这段代码吧。我们是不是可以封装成 npm 包使用。 当时我也没想太多,也没有封装 npm 包。直到我翻看 viteopen in new window 源码发现了 only-allowopen in new window 这个包。一行代码统一规范包管理器

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

当时看到这段代码时,我就在想:他们咋知道这个的。 当时依旧也没想太多。直到有一天,发现 pnpm 文档 Only allow pnpm 文档open in new window 上就有这个。好吧,吃了没看文档的亏。那时我打算分析下这个only-allow 包的源码open in new window,打开一看惊喜万分,才 36 行,写它,于是写了这篇文章。

按照惯例,看源码前先准备环境。

4. 环境准备

先克隆代码。

4.1 克隆代码

# 推荐克隆我的源码库
git clone https://github.com/lxchuan12/only-allow-analysis.git
cd only-allow-analysis/only-allow
# npm i -g pnpm
pnpm i

# 或者克隆官方仓库
git clone https://github.com/pnpm/only-allow.git
cd only-allow
# npm i -g pnpm
pnpm i

开源项目一般先看README.mdopen in new window

Force a specific package manager to be used on a project

强制在项目上使用特定的包管理器

Usage

Add a preinstall script to your project's package.json.

If you want to force yarnopen in new window, add:

{
  "scripts": {
    "preinstall": "npx only-allow yarn"
  }
}

同理可得:强制使用 npmpnpm也是类似设置。

4.2 调试源码

我们通过查看 package.json 文件。

// only-allow/package.json
{
  "bin": "bin.js",
}

确定主入口文件为 only-allow/bin.js

在最新版的 VSCode 中,auto attach 功能,默认支持智能调试,如果发现不支持,可以通过快捷键 ctrl + shift + p 查看是否启用。

于是我们在 only-allow/package.json 文件中,添加如下命令。

// only-allow/package.json
{
  "scripts": {
    "preinstall": "node bin.js pnpm"
  },
}

可以提前在 only-allow/bin.js 文件打上断点 const usedPM = whichPMRuns()

按快捷键 ctrl + ` 快捷键打开终端。输入如下 yarn add release-it -D 命令,即可调试 only-allow/bin.js

调试截图
调试截图

最终调试完会在终端报错提示使用 pnpm install

如下图所示:

终端报错截图
终端报错截图

更多调试细节可以看我的这篇文章:新手向:前端程序员必学基本技能——调试JS代码open in new window

接着我们按调试来看源码主流程。

5. only-allow 源码

// only-allow/bin.js
#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
// 输出边框盒子
const boxen = require('boxen')

const argv = process.argv.slice(2)
if (argv.length === 0) {
  console.log('Please specify the wanted package manager: only-allow <npm|pnpm|yarn>')
  process.exit(1)
}
// 第一个参数则是 用户传入的希望使用的包管理器
// 比如 npx only-allow pnpm 
// 这里调试是 node bin.js pnpm
const wantedPM = argv[0]
// npm pnpm yarn 都不是,则报错
if (wantedPM !== 'npm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn') {
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, pnpm, or yarn.`)
  process.exit(1)
}
// 使用的包管理器
const usedPM = whichPMRuns()
// 希望使用的包管理器 不相等,则报错。
// - npm  提示使用 npm install
// - pnpm 提示使用 pnpm install
// - yarn 提示使用 yarn install
// 最后退出进程
if (usedPM && usedPM.name !== wantedPM) {
  const boxenOpts = { borderColor: 'red', borderStyle: 'double', padding: 1 }
  switch (wantedPM) {
    case 'npm':
      console.log(boxen('Use "npm install" for installation in this project', boxenOpts))
      break
    case 'pnpm':
      console.log(boxen(`Use "pnpm install" for installation in this project.

If you don't have pnpm, install it via "npm i -g pnpm".
For more details, go to https://pnpm.js.org/`, boxenOpts))
      break
    case 'yarn':
      console.log(boxen(`Use "yarn" for installation in this project.

If you don't have Yarn, install it via "npm i -g yarn".
For more details, go to https://yarnpkg.com/`, boxenOpts))
      break
  }
  process.exit(1)
}

跟着断点,我们可以查看到 which-pm-runsopen in new window

6. which-pm-runs 当前运行的是哪一个包管理器

最终返回包管理器和版本号。

根据调试可知,process.env.npm_config_user_agent 是类似这样的字符串。

"yarn/1.22.10 npm/? node/v14.16.0 linux x64"

'use strict'

module.exports = function () {
  if (!process.env.npm_config_user_agent) {
    return undefined
  }
  return pmFromUserAgent(process.env.npm_config_user_agent)
}

function pmFromUserAgent (userAgent) {
  const pmSpec = userAgent.split(' ')[0]
  const separatorPos = pmSpec.lastIndexOf('/')
  return {
    name: pmSpec.substr(0, separatorPos),
    version: pmSpec.substr(separatorPos + 1)
  }
}

6.1 String.prototype.substr 截取字符串

顺带提下。我之前在 vue-next 源码看到的 pull request => chore: remove deprecated String.prototype.substropen in new window

String.prototype.substr is deprecated.

也就是说不推荐使用 substr。推荐使用 slice

ecma 规范open in new window

7. 总结

我们通过从团队需要规范统一包管理器的实际场景出发,讲了 vue3 源码中 preinstall 钩子 约束只能使用 pnpm 。同时通过查看 viteopen in new window 源码和 pnpmopen in new window 文档,了解到 only-allowopen in new window 这个包。可以做到一行代码统一规范包管理器"preinstall": "npx only-allow pnpm"

也学习了其原理。only-allow 期待的包管理器和运行的包管理器对比。匹配失败,则报错。而which-pm-runs 通过获取 process.env.npm_config_user_agent 变量获取到当前运行脚本的包管理器和版本号。

我们通过文档和沟通约束,不如用工具(代码)约束。

文章写到这里,让我想起我2018年写的文章参加有赞前端技术开放日所感所想open in new window

当时演讲的大佬说过一句话。无比赞同。

技术(开源)项目本质上是:理念、套路、规范的工具化。

同时给我们的启发也是要多看官方文档和规范。

建议读者克隆我的仓库open in new window动手实践调试源码学习。

最后可以持续关注我@若川。欢迎加我微信 ruochuan12open in new window 交流,参与 源码共读open in new window 活动,每周大家一起学习200行左右的源码,共同进步。

欢迎扫码加我微信
拉你进源码共读群
一起学习源码