本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
团队协作开发项目,统一包管理器是有必要的,可以防止产生严重的问题,本期源码共读的主题only-allow就是用来强制统一包管理器的。
1.学习准备工作
阅读川哥文章:
从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器
下载川哥代码:
git clone https://github.com/lxchuan12/only-allow-analysis.git
通过学习文章,了解来龙去脉并学习大佬的思考方式和思维模式;下载代码为调试做好准备
2.引子
2.1 vue3使用的包管理工具
在川哥的文章中提到vue3的源码中的package.json中有一个叫preinstall 命令,执行的是preinstall.js文件,但是在我拉取的vue-next源码看到的确是这样的:
看一眼checkYarn.js:
if (!/yarn.js$/.test(process.env.npm_execpath || '')) {
console.warn(
'\u001b[33mThis repository requires Yarn 1.x for scripts to work properly.\u001b[39m\n'
)
process.exit(1)
}
这段代码的含义就是检查一下包管理工具是否是使用的yarn。
之所以和大佬说的不一样,肯定是我本地代码out了,拉取最新代码,果然破案了:
从上图中我们看到最新代码已经删除checkYarn, 并新增了preinstall.js文件。查看新的package.json文件,能够看到和川哥说的一样的内容:
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)
}
可以发现,vue3使用pnpm作为包管理工具啦~
2.2 vite对包管理工具的要求
vite源码中,package.json文件中有如下内容:
我们看到vite使用了only-allow, 这就是我们具体要研究的内容。
3.阅读readme了解only-allow的使用方法
用途:强制在项目上使用特定的包管理器
使用方法:
在package.json中增加一个preinstall命令:
// 想强制使用npm
{
"scripts": {
"preinstall": "npx only-allow npm"
}
}
// 想强制使用pnpm
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}
// 想强制使用yarn
{
"scripts": {
"preinstall": "npx only-allow yarn"
}
}
4.only-allow源码分析
4.1 引入依赖
#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
const boxen = require('boxen')
这里重点关注使用的依赖:which-pm-runs
which-pm-runs: 检测执行进程的包管理器
两个相关的依赖:
preferred-pm :返回项目的首选包管理器
which-pm : 检测用于安装的包管理器
4.2 参数检查
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)
}
如果没有参数,则提示必须声明要使用的包管理器,只允许是 npm pnpm yarn 。
const wantedPM = argv[0]
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)
}
从参数中获取想使用的包管理工具,并检查这个包管理工具是否是npm pnpm yarn三者之一,如果不是,则提示。
4.3 和正在使用的包管理工具比较
获取正在使用的包管理工具并和想使用的包管理工具进行比较:
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)
}
如果正在使用的和想使用的不是一个,则判断想使用的包管理器是哪一个,并给出相应的提示。
5.调试验证
5.1 安装依赖
npm i , 结果报错:
解决报错:
方法1: 去掉preinstall
安装依赖是解决了,可是我们一会就调试不了
方法2:
(1)安装pnpm
C:\Users\newName>npm i -g pnpm
C:\Program Files\nodejs\pnpm -> C:\Program Files\nodejs\node_modules\pnpm\bin\pnpm.cjs
C:\Program Files\nodejs\pnpx -> C:\Program Files\nodejs\node_modules\pnpm\bin\pnpx.cjs
+ pnpm@6.23.6
added 1 package in 3.968s
(2) 使用pnpm安装依赖
5.2 具体调试细节
下面进入到调试详情中
5.2.1 参数检查
上图中argv中的参数是pnpm 对应的 就是preinstall命令中的pnpm process.argv 返回一个数组,数组的第一个元素是 node ; 第二个参数是脚本文件名;其余的是脚本文件的参数,如下图所示:
下图中的pnpm也就是执行脚本文件的参数:
获取想要使用的包管理器:
5.2.2 和正在使用的包管理工具比较
获取正在使用的包管理工具:
\
判断要使用的是哪种包管理工具,并给出相应的提示
6.总结、收获
总结:
(1)only-allow的检查过程通过preinstall触发;
(2)核心思想是判断期望值(包管理工具)和实际值(包管理工具)是否相等;
(3)关键是使用了which-pm-runs检查包管理工具
收获:
(1)了解了vue3使用的包管理工具pnpm
(2)了解only-allow的实现原理和使用方法
(3)另外,想进一步探究which-pm-runs的源码,熟料网络....
但大概应该这样子吧:
if (/pnpm/.test(process.env.npm_execpath || '')) {
return 'pnpm'
}
if (/npm/.test(process.env.npm_execpath || '')) {
return 'npm'
}
if (/yarn/.test(process.env.npm_execpath || '')) {
return 'yarn'
}
实际这样子:
'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)
}
}
根据这个判断 process.env.npm_config_user_agent
学习完本期源码,您可以思考如下问题:
1.常用的npm命令钩子有哪些?
2.npm install之后要执行脚本用哪个钩子?常见场景是什么?
3.npm install之后要执行脚本用哪个钩子?常见场景是什么?
4.如何检查当前运行的是哪一个包管理器?
5.process.env.npm_config_user_agent变量的含义?
6.简述only-allow的作用,核心思想?
7.团队开发为什么要对包管理器强制统一?