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

176 阅读3分钟

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

  1. 场景: 开发项目时,我们需要安装依赖,包管理器有npmcnpmyarnpnpm,为了项目规范和统一性,减少错误,最好能有一个方法,确定一个包管理器作为唯一的使用包管理器。

  2. vue3中使用了npm的preinstall钩子和preinstall.js脚本来限制使用的包管理器

1653204912(1).png

preinstall.js。检测如果不是用pnpm则退出程序 1653204536(1).png

但是我们总不能每次都复制这个代码,来限制包管理器的使用,这样感觉太麻烦了。下面就引出only-allow的包,来简化操作

  1. 使用only-allow来限制包管理器的使用 先来看它的README:

强制在一个项目中使用同一个包管理器 1653205400(1).png

使用也非常简单,在packagejson中添加下面一行代码即可

//强制使用npm
{
  "scripts": {
    "preinstall": "npx only-allow npm"
  }
}

用其他的包管理器也是类似的

  1. 调试

首先 clone 若川大大提供的 github.com/lxchuan12/o…,使用pnpm安装完依赖后,查看package.json,看到入口文件为bin.js

1653206147(1).png

开启vscode的调试模式,按住Ctrl + shift + p,在输入框输入debug,下面会出现Debug:Toogle Auto Attch,选择Smart模式

1653207952(1).png

切换到bin.js文件,将断点打到const whichPMRuns = require('which-pm-runs')

1653206504(1).png

在终端上输入yarn add release-it -D开始调试

进入which-pm-runs

    'use strict'
    // process.env.npm_config_user_agent为yarn/1.22.10 npm/? node/v16.15.0 win32 x64
    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)
      }
    }

这个函数的主要作用是获取当前使用的包管理器的名字和版本,供bin.js使用

还有一点是值得注意的在which-pm-runs中使用了String.prototype.substr在mdn上有这样一段介绍,我们可以可以使用slice或者subString代替

1653207839(1).png

跳回bin.js,源码和注释如下:

#!/usr/bin/env node
const whichPMRuns = require("which-pm-runs");
const boxen = require("boxen");

// 通过process.argv获取当前进程命令行参数(第一个参数为node,第二个参数为当前运行的脚本名,其余是脚本的参数)
// argv是当前命令的数组
const argv = process.argv.slice(2);
// 当前命令参数为空时,打印报错信息,并退出
// process.exit()传入大于0的参数表示执行失败,传入0表示执行成功
if (argv.length === 0) {
  console.log(
    "Please specify the wanted package manager: only-allow <npm|pnpm|yarn>"
  );
  process.exit(1);
}
// 获取需要的包管理器的name
const wantedPM = argv[0];
// 将取出的name与常见的包管理器进行对比,不是其中的任意一项则打印报错信息,并退出
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);
}
// 通过whichPMRuns获取正在使用的包管理器名及版本号对象,当使用的包管理器名与需要的包管理器名的不一致时,
//  根据需要的包管理器名,打印不同的报错信息,并退出。
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);
}

调试完毕控制台打印如下信息:

1653208713(1).png

  1. 总结:
    1. 在实际的开发中,我们需要去约束包管理器的统一使用,避免出现意想不到的错误。

    2. 怎么去约束包管理器的使用:npm钩子+ only-allow;

    3. 通过process.argv,可以获取到当前命令行的参数

    4. String.prototype.substr已经几乎被废弃了,可以改用slicesubString.

    5. process.env.npm_config_user_agent 可以获取当前使用的包管理器名、版本。以及node的版本信息、平台信息。