本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1. 前言
最近公司遇到一个关于yarn和lerna管理安装包的问题,再次暴露出自己的薄弱之处(挺好的😄),并逐步学习当我们执行yarn和npm命令时究竟执行了什么,也刚好看到这一期源码阅读是关于包管理,顺便一起学习了,拓展下自己的知识面。学如逆水行舟,不进则退,加油啊!!!
2. 环境准备
从若川大佬的参考文章中可以读出以下信息
-
Vue3中限制使用包管理器的实现位置是在package.json文件中,查看代码(注:本文编写时参考vue3最新版本v3.2.33,源码位置github){ "private": true, "version": "3.2.33", "scripts": { "preinstall": "node ./scripts/preinstall.js", }, }脚本中的使用
preinstall约束,即表示当在执行npm install安装依赖前,会先执行改脚本,查看./scripts/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) }判断没有使用
pnpm的话控制台报错,同时退出process对象是Node中的一个全局对象,提供当前Node进程的信息;process.env属性包含当前shell的所有环境变量。关于process对象的更多信息可参见《JavaScript标准参考教程》by阮一峰或Node官网 -
vite源码中使用only-allow包完成限制包管理器的功能(源码位置github){ "name": "vite-monorepo", "private": true, "engines": { "node": ">=14.6.0" }, "scripts": { "preinstall": "npx only-allow pnpm", }, } -
only-allow包中,封装了对于包管理器限制的代码
3. 源码解析
找到源码,这里我用的是若川大佬文章中提到的官方代码库,版本为v1.0.0,源码位置github;主要实现代码在bin.js文件中
#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
const boxen = require('boxen')
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)
}
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)
}
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)
}
3.1 argv处理
通过const argv = process.argv.slice(2)获取命令指定的参数
process.argv属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
slice(2)从第三个参数开始获取,因为在运行脚本时实际上传递的参数为node bin.js <npm/yarn/pnpm>
if (argv.length === 0)判断如果没有第三个参数,说明没有指定包管理器,打印错误,process.exit(1)退出进程
process.exit方法用来退出当前进程。它可以接受一个数值参数,如果参数大于0,表示执行失败;如果等于0表示执行成功。
3.2 wantedPM
通过const wantedPM = argv[0]获取希望的管理器
if (wantedPM !== 'npm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn')判断指定的包管理器名不为npm、pnpm或yarn打印错误,看起来only-allow只支持这三种包管理,想要用其他的应该就需要自己封装了。
3.3 usedPM
通过whichPMRuns获取当前使用的包管理器
if (usedPM && usedPM.name !== wantedPM)判断当使用的包管理器和指定的不相同时,控制台打印错误,提示用户使用指定的npm install、pnpm install或者yarn
boxen用于创建一个终端展示框
3.4 whichPMRuns
whichPMRuns从which-pm-runs导出;
which-pm-runs也是这个作者封装的一个小工具,代码不长,这里就顺便看了下,源码地址github
'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)
}
}
'use strict'使用严格模式module.exports = function ()模块导出,导出一个函数if (!process.env.npm_config_user_agent)获取npm_config_user_agent全局属性- 不存在返回
undefined - 存在的话为
'yarn/1.22.18 npm/? node/v18.0.0 win32 x64'或'npm/8.6.0 node/v18.0.0 win32 x64 workspaces/false'格式字符串,并最终返回通过pmFromUserAgent函数处理得到的结果{name: 'yarn', version: '1.22.18'}或{name: 'npm', version: '8.6.0'}对象 pmFromUserAgent函数的功能比较简单,这里就不展开说明了,注意split()、lastIndexOf()和substr()三个函数的用法就行了,不清楚的可以查询MDN😉
4.总结
本期文章源码虽然比较少,但是对于npm命令的钩子有了更近一步的了解,虽然暂时还未完全解决在文章最开始提到的公司中遇到的问题,但现在多少有了排查问题思路了,不会像无头苍蝇一样试都不清楚从哪里开始尝试。
最后,希望大家都能在学习中找到快乐。
勿谓今日不学而有来日,勿谓今年不学而有来年。日月逝矣,岁不我延。呜呼老矣,是谁之愆。