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

427 阅读4分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

1. 前言

最近公司遇到一个关于yarnlerna管理安装包的问题,再次暴露出自己的薄弱之处(挺好的😄),并逐步学习当我们执行yarnnpm命令时究竟执行了什么,也刚好看到这一期源码阅读是关于包管理,顺便一起学习了,拓展下自己的知识面。学如逆水行舟,不进则退,加油啊!!!

2. 环境准备

从若川大佬的参考文章中可以读出以下信息

  1. 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官网

  2. vite源码中使用only-allow包完成限制包管理器的功能(源码位置github

     {
       "name": "vite-monorepo",
       "private": true,
       "engines": {
         "node": ">=14.6.0"
       },
       "scripts": {
         "preinstall": "npx only-allow pnpm",
       },
     }
    
  3. 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')判断指定的包管理器名不为npmpnpmyarn打印错误,看起来only-allow只支持这三种包管理,想要用其他的应该就需要自己封装了。

3.3 usedPM

通过whichPMRuns获取当前使用的包管理器

if (usedPM && usedPM.name !== wantedPM)判断当使用的包管理器和指定的不相同时,控制台打印错误,提示用户使用指定的npm installpnpm install或者yarn

boxen用于创建一个终端展示框

3.4 whichPMRuns

whichPMRunswhich-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命令的钩子有了更近一步的了解,虽然暂时还未完全解决在文章最开始提到的公司中遇到的问题,但现在多少有了排查问题思路了,不会像无头苍蝇一样试都不清楚从哪里开始尝试。

最后,希望大家都能在学习中找到快乐。

勿谓今日不学而有来日,勿谓今年不学而有来年。日月逝矣,岁不我延。呜呼老矣,是谁之愆。