阅读 install-pkg 源码

阅读 install-pkg 源码

本文是在看到 《Vue 团队核心成员开发的 39 行小工具 install-pkg 安装包,值得一学!》 文章后,对 @antfu/install-pkg 进行的一次源码阅读。

install-pkg 介绍

Install package programmatically. Detect package managers automatically (npm, yarn and pnpm).

自动检测包管理器(npm、yarn 和 pnpm)来编程式安装包依赖。

1
npm i @antfu/install-pkg
1
2
3
import { installPackage } from '@antfu/install-pkg';

await installPackage('vite', { silent: true });

源码

入口文件 src/index.ts

导出所有

1
2
export * from './detect';
export * from './install';

install.ts installPackage 安装包

支持指定包管理器,支持安装多个依赖,支持额外的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 = {}) {
// 包管理器
const agent = options.packageManager || (await detectPackageManager(options.cwd)) || 'npm';
if (!Array.isArray(names)) names = [names];

// 额外的依赖
const args = options.additionalArgs || [];

if (options.preferOffline) args.unshift('--prefer-offline');

return execa(
agent,
[agent === 'yarn' ? 'add' : 'install', options.dev ? '-D' : '', ...args, ...names].filter(Boolean),
{
stdio: options.silent ? 'ignore' : 'inherit',
cwd: options.cwd,
}
);
}

上述代码最终执行类似如下脚本:

1
yarn add -D react react-dom

detect.ts detectPackageManager 包探测器

根据当前的锁文件(yarn.lock or package-lock.json or pnpm-lock.yaml )判断是哪个包管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import path from 'path';
import findUp from 'find-up';

export type PackageManager = 'pnpm' | 'yarn' | 'npm';

const LOCKS: Record<string, PackageManager> = {
'pnpm-lock.yaml': 'pnpm',
'yarn.lock': 'yarn',
'package-lock.json': 'npm',
};

// process.cwd()是 返回当前工作目录。如:调用node命令执行脚本时的目录。
export async function detectPackageManager(cwd = process.cwd()) {
// 通过findUp 获取当前目录lock文件
const result = await findUp(Object.keys(LOCKS), { cwd });
const agent = result ? LOCKS[path.basename(result)] : null;
return agent;
}

其中需要注意的一些 node 相关的知识:

  • process.cwd(): 返回当前工作目录。如:调用 node 命令执行脚本时的目录。

  • __dirname: 返回源代码所在的目录。

  • path.basename(): 返回 path 的最后一部分

    1
    2
    3
    4
    5
    path.basename('/foo/bar/baz/asdf/quux.html');
    // 返回: 'quux.html'

    path.basename('/foo/bar/baz/asdf/quux.html', '.html');
    // 返回: 'quux'

其中findUp为查找文件路径:

1
2
3
4
5
6
7
8
/
└── Users
└── sindresorhus
├── unicorn.png
└── foo
└── bar
├── baz
└── example.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import path from 'node:path';
import { findUp, pathExists } from 'find-up';

console.log(await findUp('unicorn.png'));
//=> '/Users/sindresorhus/unicorn.png'

console.log(await findUp(['rainbow.png', 'unicorn.png']));
//=> '/Users/sindresorhus/unicorn.png'

console.log(
await findUp(
async (directory) => {
const hasUnicorns = await pathExists(path.join(directory, 'unicorn.png'));
return hasUnicorns && directory;
},
{ type: 'directory' }
)
);
//=> '/Users/sindresorhus'

所以在有yarn.lock文件的项目中,detectPackageManager 函数最终返回 agent 的是yarn

至此,install-pkg的源码已经看完,可以总结原理为:

通过 lock 文件自动检测,确定使用哪一个包管理器(npm、yarn、pnpm),最终用 execa 执行类似如下的命令。

1
yarn add -D react react-dom

看完源码,我们也可以看下 package.json 中的 scripts 命令,学习一下。

package.json script 解析

1
2
3
4
5
6
7
8
9
"scripts": {
"prepublishOnly": "nr build",
"dev": "nr build --watch",
"start": "esno src/index.ts",
"build": "tsup src/index.ts --format cjs,esm --dts --no-splitting",
"release": "bumpp --commit --push --tag && pnpm publish",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint:fix": "nr lint -- --fix"
}

nr? —-> ni 神器

github ni

推荐看尤雨溪推荐神器 ni ,能替代 npm/yarn/pnpm ?简单好用!源码揭秘!

可以自动根据锁文件 yarn.lock / pnpm-lock.yaml / package-lock.json 检测使用 yarn / pnpm / npm 的包管理器。

例子:

nr 交互式选择脚本

1
2
3
4
5
6
7
8
9
10
nr
# 交互式选择脚本
# interactively select the script to run
# supports https://www.npmjs.com/package/npm-scripts-info convention

nr dev --port=3000

# npm run dev -- --port=3000
# yarn run dev --port=3000
# pnpm run dev -- --port=3000

nci - clean install

1
2
3
4
5
nci
# npm ci
# 简单说就是不更新锁文件
# yarn install --frozen-lockfile
# pnpm install --frozen-lockfile

esno

github esno

image-20220108162301182

源码不多,如下所示:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env node

const spawn = require('cross-spawn');
const spawnSync = spawn.sync;

const register = require.resolve('esbuild-register');

const argv = process.argv.slice(2);

process.exit(spawnSync('node', ['-r', register, ...argv], { stdio: 'inherit' }).status);

esbuild-register 简单说:使用 esbuild 即时传输 JSX、TypeScript 和 esnext 功能

cross-spawn:编写跨平台 spawn 语句,可参考《用 Node 编写跨平台 spawn 语句》

tsup

打包 TypeScript 库的最简单、最快的方法。

tsup

bumpp 交互式提升版本号

bumpp

version-bump-prompt

交互式 CLI 可增加您的版本号等

Screenshot

总结

最后,在 install-pkg 上可查看本篇源码文章~

参考链接

  • [Vue 团队核心成员开发的 39 行小工具 install-pkg 安装包,值得一学!](
作者

Sea

发布于

2022-01-08

更新于

2023-01-10

许可协议

评论