统一前端包管理器小工具:only-allow + 源码解读

712 阅读3分钟

前言

最近参加源码共读的小组,每周一份源码学习笔记。

场景

现在前端包管理工具有npm、yarn、pnpm,在多人协作的项目中,大家一般会约定用其中一种工具,如果团队加入了新同学,使用其他包管理工具进行安装和提交。就有可能改变项目依赖版本,造成一些不可预见的问题。

所以我们需要一个工具,在代码层面强制规定使用哪一个包管理工具。

它就是 only-allow

我们直接来看官方仓库

使用很简单,他直接通过 npm 的preinstall钩子,在项目安装依赖之前,检查并只允许约定好的包管理工具进行安装

image.png

前置知识

npm 脚本钩子

官方解释

只需创建另一个具有匹配名称的脚本,并在它们的开头添加“pre”或“post”,就可以在package.json 的脚本中定义前置或后置的脚本内容。

{
  "scripts": {
    "precompress": "{{ executes BEFORE the `compress` script }}",
    "compress": "{{ run command to compress files }}",
    "postcompress": "{{ executes AFTER `compress` script }}"
  }
}

简单来说就是在执行自定义或者npm自带脚本时,在开始执行脚本前和结束后,提供入口,便于我们添加额外的操作。

脚本名开头加pre是前置钩子,开头加post是后置钩子。

only-allow 是在安装命令install之前执行一个js脚本,来控制期望使用的包管理工具。

node process对象

process 对象是 Node 的一个全局对象,提供当前 Node 进程的信息。它可以在脚本的任意位置使用,不必通过require命令加载。该对象部署了EventEmitter接口。

process.exit()

退出当前进程,接受一个number类型的参数,如果大于0则退出。

nodejs中要退出当前进程,一般会这么写

process.exit(1)

process.argv

它是一个数组,由用命令行执行脚本时的参数组成。数组第一项是node路径,第二项是执行的脚本的路径,其余是脚本文件的参数。

他大概长这样

console.log(process.argv)
​
​
[
  'C:\Program Files\nodejs\node.exe',
  'D:\my_test\ReadingSourceCode\only-allow\test.js',
  'a',
  'b',
  'c',
  'd'
]

process.env

返回一个对象,包含了当前Shell的所有环境变量

全面了解该对象,请前往阮一峰老师的博客。process 对象

only-allow 源码

试一试

我们将项目包管理器预设为yarn

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

执行 npm install,程序会报错,提示应该用yarn安装

image.png

执行pnpm i,同样报错了

image.png

实现思路

only-allow 源码只有30多行,逻辑很简单

  1. 取到当前执行脚本文件的第一个参数为预设值
  2. 如果预设值不是 npm、pnpm 或 yarn,则报错、退出进程
  3. 如果当前使用的包管理器不等于预设值,则报错、退出进程

源码解读

源码用到了两个第三方库

  • which-pm-runs 检测你是用哪个包管理器安装的
  • boxen 用于在终端打印box样式

完整代码:

#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
const boxen = require('boxen')
​
const argv = process.argv.slice(2) // node执行脚本文件的参数
if (argv.length === 0) {
  // 如果没有参数会退出进程
  console.log('Please specify the wanted package manager: only-allow <npm|pnpm|yarn>')
  process.exit(1)
}
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()
// 如果不是预设值,则报错、退出进程
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)
}

参考文章

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