install-pkg从案例到源码

415 阅读3分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

前言

今天翻到antfu大佬的 install-pkg

image.png

在项目简介中是这样描述的Install package programmatically. Detect package managers automatically (npmyarn and pnpm).
翻译成中文就是自动检测包管理工具并以编程方式安装package。
这个项目我感觉核心代码不多仅仅100多行代码。印证那句话了麻雀虽小五脏俱全,从中学到很多东西特此分析一下。

简单使用

安装 install-pkg

初始化 package.json

npm init -y

初始化 git

git init 

以npm方式安装 install-pkg

npm i @antfu/install-pkg

当然也可以根据 yarn 或 pnpm 进行安装

pnpm i @antfu/install-pkg
yarn add @antfu/install-pkg

简单使用一下

建一个 index.js 文件,并写下一下代码

const pkg = require("@antfu/install-pkg");
const installPackage = pkg.installPackage;
async function fun() {
  await installPackage("vite", { silent: true });
}
fun();

通过node执行我们写好的index.js文件

node index.js

等待一会后在node_modules文件夹里就能发现我们要安装的vite,此时就说明已经安装成功了

image.png

实现install-pkg-starter

使用commander实现 add 安装依赖命令

npm i commander

通过以下代码就可以实现简单的 add 安装依赖命令

program
  .command("add <package-names>")
  .description("add a package")
  .action(async (packageName) => {
    try {
      await installPackage(packageName, { silent: true });
    } catch (error) {
      console.log(error);
    }
  });

program.parse(process.argv);

使用 ora 实现安装中的loading状态

program
  .command("add <package-names>")
  .description("add a package")
  .action(async (packageName) => {
    const spinner = ora("waiting download package");
    spinner.start();
    try {
      await installPackage(packageName, { silent: true });
      spinner.succeed();
    } catch (error) {
      console.log(error);
      spinner.fail("Request failed, refetch ...");
    }
  });

program.parse(process.argv);

这样就是实现了一个简易版的install-pkg-starter

深入源码

接下来进入阅读代码环节,首先fork到我们自己的仓库里,通过git clone将代码下载到本地

git clone git@github.com:wzhaofei/install-pkg.git

了解项目

我们通过看package.json文件,来了解项目,通过观察此项目的运行依赖就俩个execa以及find-up

"dependencies": {
    "execa": "^5.1.1",
    "find-up": "^5.0.0"
  }
  • execa 的作用是建立子进程执行命令
  • find-up 的作用是遍历父目录查找文件或目录

开发依赖则比较多了

  • typescript ts开发必备
  • tsup 进行ts打包
  • esno 实时运行ts

其他的就行看不了,下面直接看源码

阅读install.ts源码

import execa from "execa";
import { detectPackageManager } from ".";

export interface InstallPackageOptions {
  cwd?: string; //子进程的当前工作目录
  dev?: boolean; // 是否是开发依赖
  silent?: boolean; //标准输出
  packageManager?: string; // 包管理器
  preferOffline?: boolean; // 优先使用缓存数据
  additionalArgs?: string[]; // 附加参数
}

export async function installPackage(
  names: string | string[],
  options: InstallPackageOptions = {}
) {
  // 获取本地包管理工具默认是npm
  const agent =
    options.packageManager ||
    (await detectPackageManager(options.cwd)) ||
    "npm";

  if (!Array.isArray(names)) names = [names]; // 如果names不是数组则转成数组

  const args = options.additionalArgs || []; // 附件参数

  if (options.preferOffline) args.unshift("--prefer-offline"); // 是否使用缓存

  // pnpm install -D --prefer-offline vite

  return execa(
    agent,
    [
      agent === "yarn" ? "add" : "install",
      options.dev ? "-D" : "",
      ...args, // 附件参数
      ...names, // 需要安装的包名
    ].filter(Boolean),
    {
      stdio: options.silent ? "ignore" : "inherit", // 输出格式
      cwd: options.cwd, //当前工作目录
    }
  );
}

阅读detectPackageManager.ts源码

import path from "path";
import findUp from "find-up"; // 通过遍历父目录查找文件或目录

export type PackageManager = "pnpm" | "yarn" | "npm"; // 定义包管理工具类型

// lock文件与锁文件的对应关系
const LOCKS: Record<string, PackageManager> = {
  "pnpm-lock.yaml": "pnpm",
  "yarn.lock": "yarn",
  "package-lock.json": "npm",
};

// 根据lock文件寻找包管理工具
export async function detectPackageManager(cwd = process.cwd()) {
  const result = await findUp(Object.keys(LOCKS), { cwd }); // 在工作目录中寻找 lock 文件
  // path.basename 返回路径中的最后一部分  pnpm-lock.yaml
  const agent = result ? LOCKS[path.basename(result)] : null; // 有则返回对应的包管理工具没有则返回空
  return agent;
}