「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
前言
今天翻到antfu大佬的 install-pkg 。
在项目简介中是这样描述的Install package programmatically. Detect package managers automatically (npm, yarn 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,此时就说明已经安装成功了
实现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;
}