本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是源码共读的第16期 | # 一行代码统一规范 包管理器
前言
本篇笔记将解析Vue3 、 Vite中用到过的统一包管理器的工具 only-allow, 我们将:
- 学习 npm 钩子使用
- 学习 only-allow 源码
原理
本质上就是通过 npm的钩子 先执行特定文件,通过读取 命令参数来判断是否执行了正确包管理工具的命令
"preinstall": "npx only-allow pnpm"
任何scripts 脚本都可以通过 pre/post 声明钩子, 一个是之前,一个是之后触发,除此还有特殊的如prepublishOnly npm publish前触发,
准备
官方仓库:only-allow
川哥整理好的仓库:only-allow-analysis
使用
只需要在项目总的package.json 加入钩子 声明指定的包
{
"scripts": {
"preinstall": "npx only-allow pnpm" //npm yarn
}
}
npx 将会从指定的包中运行命令, 如果这个包不存在本地中则会安装在临时目录中执行
源码
核心只有 bin.js 一个文件 比较简单, 在这里还要了解下使用到的两个库boxen/which-pm-runs
boxen 用于在控制台输出带盒子形状的输出内容
which-pm-runs 用于检测当前命令是被哪个包管理器执行了
which-pm-runs
在看only-allow 源码实现前,先来看下 which-pm-runs这个包的实现
'use strict'
module.exports = function () {
// npm_config_user_agent 当前包管理工具的信息头
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通过不同的包管理工具命令执行,会得到对应这样的内容:
//npm/8.19.3 node/v14.19.0 win32 x64 workspaces/false
//pnpm/7.29.2 npm/? node/v14.19.0 win32 x64
//yarn/1.22.4 npm/? node/v14.19.0 win32 x64
const pmSpec = userAgent.split(' ')[0] // npm/8.19.3
const separatorPos = pmSpec.lastIndexOf('/')
return {
name: pmSpec.substr(0, separatorPos), //npm
version: pmSpec.substr(separatorPos + 1) //8.19.3
}
主源码实现
// 裁剪默认的node 文件名参数 得到后面的参数
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)
}
整体代码只有这么多就结束了, 比较简单, 源码主体就是得到命令行执行的环境变量 与 指定的包名称进行对比,如果不匹配则结束提示, 最后通过npm钩子使用这个库达到统一包管理工具的目的。
轮子
简单改造下,将代码抽离,得到一份完整实现的代码
#!/usr/bin/env node
const ColorInfo = (color:string) => (text:string) => {
const whichColor = {
'green':92,
'red':91,
'yellow':93,
'blue':94,
'purple':95,
}
console.log(`\x1b[${whichColor[color as keyof typeof whichColor]}m${text}\x1b[0m`)
}
const Red = ColorInfo('red')
const Green = ColorInfo('green')
const Blue = ColorInfo('blue')
const Purple = ColorInfo('purple')
const Yellow = ColorInfo('yellow')
const getPackageAgent = () =>{
if (!process.env.npm_config_user_agent) {
return undefined
}
return getAgentInfo(process.env.npm_config_user_agent)
}
const getAgentInfo = (userAgent:string) => {
const Spec = userAgent.split(' ')[0]
const separatorPos = Spec.lastIndexOf('/')
return {
name: Spec.slice(0, separatorPos),
version: Spec.slice(separatorPos + 1)
}
}
const usedPM = getPackageAgent()
const argv = process.argv.slice(2)
if (argv.length === 0) {
Blue('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') {
Yellow(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, pnpm, or yarn.`)
process.exit(1)
}
if (usedPM && usedPM.name !== wantedPM) {
switch (wantedPM) {
case 'npm':
Red('Use "npm install" for installation in this project')
break
case 'pnpm':
Red(`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/`)
break
case 'yarn':
Red(`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/`)
break
}
process.exit(1)
}
Green(`Only Allow Done.`)
总结
本期源码比较简单,推荐阅读起来,自己动手实现一个 only allow, 最核心的就是通过npm钩子来实现 任何 install 前的触发, 除此之外 还有 post 钩子, 以后自己写包或者公司项目统一规范时就可以用上,很nice