【若川视野 x 源码共读】第16期 | 一行代码统一规范 包管理器

913 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
团队协作开发项目,统一包管理器是有必要的,可以防止产生严重的问题,本期源码共读的主题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的使用方法

地址:github.com/pnpm/only-a…

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

使用方法:

在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.团队开发为什么要对包管理器强制统一?