如何一行代码统一 NPM 包管理器规范, 来看看大牛如何实现的!

188 阅读2分钟

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

这篇文章将会学到如何限定使用的包管理器, 以及这个限定包是如何实现的.

由于现在的包管理器为了限定包的版本, 设计使用了lock文件来锁定版本. 不同的包管理器会可能会和导致集成工具不一致. 从而导致版本问题. 所以要限定包管理器.

这个功能用到了npm钩子中的preinstall.

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

刚看到代码时我想为什么加上npx, 不用加也可以啊, 但测试发现, 如果第一次安装依赖时是没有only-allow的,会导致出错.

image.png

加上 npx 就不会报错了, 会提示先安装这个依赖, 然后这个限制包的功能就起效了.

从only-allow的源码来看, 它的script里是这样配置的:

// only-allow/package.json
{
  "scripts": {
    "preinstall": "node bin.js pnpm"
  },
}

意思很明确, 安装前执行bin.js的代码.下面对bin.js代码段注释解析:

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)
}

获取第二个之后的参数赋给argv数组.如果argv长度为零.说明使用有误.提示并退出.

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)
}

通过whichPMRuns获取到正在使用的包管理器.不合配置就提示退出.下面看下whichPMRuns的实现

//node_modules\which-pm-runs\index.js
'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. 这个变量反映出当前包管理器. 各个用户代理器都具有包含版本信息的用户代理字符串。 npm模块会将这个字符串解析为一个对象,该对象可以可以使用process.env.npm_config_user_agent取得。

这个变量值大致如下:

npm: npm/9.8.0 node/v16.17.1 win32 x64 workspaces/false
yarn: yarn/1.22.19 npm/? node/v16.17.1 win32 x64
pnpm: pnpm/8.6.0 npm/? node/v16.17.1 win32 x64

另外, 还看到了一个不错的命令行工具boxen. 这个工具就是将要强调的文字用各种效果的框框包裹起来.

不推荐使用 substr。推荐使用 slice . substrsubstring有可能会废弃!