【若川视野 x 源码共读】16期 - preinstall钩子和only-allow

lxf2023-05-05 23:14:01

本文源码,欢迎勘误,交流。

1 一句代码统一项目包管理器

这一句代码在Vite和Vue3源码的根目录package.json里,一定要到根目录去,不要学我,跑到packages的项目里面去找了半天。

Vite中的代码是这样的:

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

Vue3 中的代码是这样的:

scripts :{
	"preinstall": "node ./scripts/preinstall.js",
}

看着不一样,事实上这里两句代码的目的都是一样的,就是限制开发的时候,必须使用pnpm这个包管理。vue3vite都是采用monorepo风格管理代码的项目,使用pnpm更适合它们项目的情况。

2 是否有必要管?

包管理器其实有很多,npmyarn, cnpm ,还有刚才提到的 pnpm。大家都是将包下载到node_modules,但是却会产生不一样的文件和文件结构,比如 npm 会产生 package-lock.json 而 yarn 则会产生 yarn.lock,如果团队中的同事因为开发习惯的不同,由于执行 install的时间不同,可能会导致不同人使用的依赖版本完全不一样。

不同的包管理器对依赖的处理算法也不一样,比如npmpnpm,它们在处理依赖时会得出完全不一样的node_moudles结构。

这显然会让团队的代码管理变得困难,同时如果像**lock这样的文件也纳入代码管理的话,也有可能让代码中多出很多由其它包管器产生的对部分人有用文件。

3 preinstall

我觉得首先要搞清楚这个东西是什么,它是npm提供的一系列生命周期钩子,以使得用户可以在一定时期,执行一些其它指令。我找到了npm, yarn,以及pnpm介绍这些钩子的文档

  • npm
  • yarn
  • pnpm和npm稍有不同,它有两处地方介绍这个特性:一 二

通过npm的文档我了解到,在scripts中,npm提供了一系列的生命周期钩子(Life Cycle Scripts),它们只在特定情形下触发,而且,更进一步的是,也允许用户创建自定义的钩子,并通过 pre-post-前缀,可以在执行特定scirpts时,附加执行一些前置或者后置的程序。

pnpm的处理和npm和yarn都不太一样,它认为支持用户自定义的scripts使用pre和post前缀容易引起误解,而且可能会导致部分人在毫无知觉的情形下执行了他们并不想执行的程序,因此pnpm默认不支持用户自定义scirpts的pre和post前缀

可选内容 你可能会觉得,preinstall一定是会在install以前执行,我在检索的时候发现一个npm的issue,它不同版本的执行顺序有所不同:点我去看,使用时还是要多加注意。

4 Vue的做法:preinstall.js

这个文件只有7行代码,一眼就能看完了:链接

if (!/pnpm/.test(process.env.npm_execpath || '')) {
	// 不是pnpm包管理器时的处理
}

可以读到,它是通过读取运行环境的变量npm_execpath来判断当前使用的包管理器是不是pnpm。读到这里我有一个疑问,vite和vue的两种做法有什么不一样呢?vue的做法这么精简,vite为什么不用这种做法? , 我决定又先看看vite的做法。

5 Vite的做法:only-allow和which-pm-runs

  • only-allow
  • which-pm-runs

说来比较有意思,其实only-allow是那个最与众不同的pnpm开发的一个包。

看过only-allow的核心代码bin.js,可能会发现only-allow依赖了which-pm-runsboxen两个包,其实boxen也是个有趣的依赖,但是它的主要功能是为输出的文字添加一个"框",所以,并不是重点。

only-allow的代码也是非常短,只有39行,最关键的莫过于这个部分了:

const whichPMRuns = require('which-pm-runs')
// ...
const usedPM = whichPMRuns()
if (usedPM && usedPM.name !== wantedPM) {
 // 不是想要的包管理器时的处理
}

上面这个代码,会从which-pm-runs获取现在使用的包管理器是什么,如果不是目标的包管理器,就会做出相应的报错处理。那么which-pm-runs的做法是不是和vue的脚本一样呢?那就继续下钻吧,又去翻一下which-pm-runs的源码咯。

which-pm-runs只有17行代码,关键部分是,我添加了一些我自己的注释:

// whichPMRuns()实际执行的函数
module.exports = function () {
	// ...
	return pmFromUserAgent(process.env.npm_config_user_agent)
}
/**
获取实际的客户端信息
@params userAgent {string} 用户当前使用的客户端信息
*/
function pmFromUserAgent (userAgent) {

	// 对信息进行截取
	const pmSpec = userAgent.split(' ')[0]  
	const separatorPos = pmSpec.lastIndexOf('/')
	
	return {
		name: pmSpec.substr(0, separatorPos),     // 前面的部分
		version: pmSpec.substr(separatorPos + 1)  // 后面的部分
	}
}

嗯?有意思,which-pm-runsvue使用的都是env中的变量,但是which-pm-runs用的是npm_config_user_agent, 而vue用的是npm_execpath,这两个变量有什么不同,没有人跟我一样好奇吗?

6 npm_execpath和npm_config_user_agent

我好像没有找到关于这两个变量的文档说明,不知道这两个仓库的作者大佬是怎么找到的这两个变量,太流弊了。

为了看到这两个变量分别是什么,我搭建了一个最简单的nodejs项目:

mkdir nodejs-env
cd nodejs-env
npm init
# 一路回车到最后

打开生成的package.json,做这样的变更:

{
  "name": "nodejs-env",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "preinstall": "node preinstall.js"
  },
  "author": "",
  "license": "ISC"
}

创建一个新的preinstall.js文件,写入代码:

#!/usr/bin/env node
console.log('npm_execpath', process.env.npm_execpath);
console.log('npm_config_user_agent', process.env.npm_config_user_agent);

分别用npmyarn, pnpm(真巧,三个包管理器我都安装了,欸嘿~!) 执行安装指令:

npm的结果:

❯ npm i

> nodejs-env@1.0.0 preinstall
> node preinstall.js

npm_execpath /usr/lib/node_modules/npm/bin/npm-cli.js
npm_config_user_agent npm/8.1.2 node/v16.13.2 linux x64 workspaces/false

up to date, audited 1 package in 190ms

found 0 vulnerabilities

yarn的结果:

❯ yarn
yarn install v1.22.17
info No lockfile found.
$ node preinstall.js
npm_execpath /usr/share/yarn/bin/yarn.js
npm_config_user_agent yarn/1.22.17 npm/? node/v16.13.2 linux x64
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...

success Saved lockfile.
Done in 0.09s.

pnpm的结果:

❯ pnpm i
Already up-to-date

> nodejs-env@1.0.0 preinstall /home/bond/code-self/nodejs-env
> node preinstall.js

npm_execpath /usr/lib/node_modules/pnpm/bin/pnpm.cjs
npm_config_user_agent pnpm/6.32.4 npm/? node/v16.13.2 linux x64

那,如果用node直接运行,又会怎样呢?

❯ node preinstall.js
npm_execpath undefined
npm_config_user_agent undefined

6.1 先说结论

  • 这两个变量都是包管理器添加的
  • npm_execpath反馈的是执行安装指令的包管理器程序的具体路径
  • npm_config_user_agent反馈的是执行安装指令的包管理器程序的具体版本和环境

6.2 再说异同

我感觉 npm_execpath提供的信息并不如npm_config_user_agent详细,虽然only-allowvue都没有使用到版本信息。

或许,我说的是或许,在不久的将来,还考虑到对包管理器的版本进行统一管理,比如这样:

npx only-allow pnpm/6.32.4

确实是已经有类似的PR了哦,可以点这里看。

活动相关

链接

欢迎加入,共同进步哟!