前言
最近参加源码共读的小组,每周一份源码学习笔记。
场景
现在前端包管理工具有npm、yarn、pnpm,在多人协作的项目中,大家一般会约定用其中一种工具,如果团队加入了新同学,使用其他包管理工具进行安装和提交。就有可能改变项目依赖版本,造成一些不可预见的问题。
所以我们需要一个工具,在代码层面强制规定使用哪一个包管理工具。
它就是
only-allow
我们直接来看官方仓库
使用很简单,他直接通过 npm 的preinstall钩子,在项目安装依赖之前,检查并只允许约定好的包管理工具进行安装
前置知识
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安装
执行pnpm i,同样报错了
实现思路
only-allow 源码只有30多行,逻辑很简单
- 取到当前执行脚本文件的第一个参数为预设值
- 如果预设值不是 npm、pnpm 或 yarn,则报错、退出进程
- 如果当前使用的包管理器不等于预设值,则报错、退出进程
源码解读
源码用到了两个第三方库
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)
}