目录正文使用源码分析总结正文 通常安装依赖都是通过命令式的方式来安装,有没有想过可以通过编程式的方式来安装依赖呢? install-pkg是一个用于安装依赖的工具,它可以在不同的环境
通常安装依赖都是通过命令式的方式来安装,有没有想过可以通过编程式的方式来安装依赖呢?
install-pkg
是一个用于安装依赖的工具,它可以在不同的环境下安装依赖,比如 npm、yarn、pnpm 等。
install-pkg
的使用非常简单,根据README
的说明,就通过下面的代码就可以安装依赖了:
import { install } from 'install-pkg'
await installPackage('vite', { silent: true })
install-pkg
的源码非常简单,只有 100 行左右,我们来看看它的实现原理。
根据README
的说明,我们可以通过installPackage
方法来安装依赖,那么我们先来看看installPackage
方法的实现:
installPackage
方法在src/index.ts
文件中,转成 js 代码如下:
import execa from 'execa'
import { detectPackageManager } from '.'
export async function installPackage(names, options = {}) {
const detectedAgent = options.packageManager || await detectPackageManager(options.cwd) || 'npm'
const [agent] = detectedAgent.split('@')
if (!Array.isArray(names))
names = [names]
const args = options.additionalArgs || []
if (options.preferOffline) {
// yarn berry uses --cached option instead of --prefer-offline
if (detectedAgent === 'yarn@berry')
args.unshift('--cached')
else
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,
},
)
}
可以看到是一个异步方法,它接收两个参数,第一个参数是要安装的依赖名称,第二个参数是配置项。
在方法内部,首先通过传入的配置项options
来获取packageManager
,如果没有传入packageManager
,则通过detectPackageManager
方法来获取packageManager
,如果detectPackageManager
方法也没有获取到packageManager
,则默认使用npm
。
来看看detectPackageManager
方法的实现:
import fs from 'fs'
import path from 'path'
import findUp from 'find-up'
const AGENTS = ['pnpm', 'yarn', 'npm', 'pnpm@6', 'yarn@berry', 'bun']
const LOCKS = {
'bun.lockb': 'bun',
'pnpm-lock.yaml': 'pnpm',
'yarn.lock': 'yarn',
'package-lock.JSON': 'npm',
'npm-shrinkwrap.json': 'npm',
}
export async function detectPackageManager(cwd = process.cwd()) {
let agent = null
const lockPath = await findUp(Object.keys(LOCKS), { cwd })
let packageJsonPath
if (lockPath)
packageJsonPath = path.resolve(lockPath, '../package.json')
else
packageJsonPath = await findUp('package.json', { cwd })
if (packageJsonPath && fs.existsSync(packageJsonPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
if (typeof pkg.packageManager === 'string') {
const [name, version] = pkg.packageManager.split('@')
if (name === 'yarn' && parseInt(version) > 1)
agent = 'yarn@berry'
else if (name === 'pnpm' && parseInt(version) < 7)
agent = 'pnpm@6'
else if (name in AGENTS)
agent = name
else
console.warn('[ni] Unknown packageManager:', pkg.packageManager)
}
}
catch {}
}
// detect based on lock
if (!agent && lockPath)
agent = LOCKS[path.basename(lockPath)]
return agent
}
findUp
是一个用于查找文件的工具,它可以从当前目录向上查找文件,直到找到为止。
我们来逐行分析:
const lockPath = await findUp(Object.keys(LOCKS), {cwd})
let packageJsonPath
if (lockPath)
packageJsonPath = path.resolve(lockPath, '../package.json')
else
packageJsonPath = await findUp('package.json', {cwd})
最开始是获取package-lock.json
、yarn.lock
、pnpm-lock.yaml
等文件的路径;
如果找到就好办了,直接在这个文件目录下找package.json
文件即可;
如果没找到就继续使用findUp
方法来查找package.json
文件。
if (packageJsonPath && fs.existsSync(packageJsonPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
// ...
} catch {
}
}
如果找到了package.json
文件,就读取文件内容,然后解析成 JSON 对象。
if (typeof pkg.packageManager === 'string') {
const [name, version] = pkg.packageManager.split('@')
if (name === 'yarn' && parseInt(version) > 1)
agent = 'yarn@berry'
else if (name === 'pnpm' && parseInt(version) < 7)
agent = 'pnpm@6'
else if (name in AGENTS)
agent = name
else
console.warn('[ni] Unknown packageManager:', pkg.packageManager)
}
这里是用过packageManager
来判断使用哪个包管理器;
packageManager
是yarn
,并且版本号大于1
,则使用yarn@berry
;packageManager
是pnpm
,并且版本号小于7
,则使用pnpm@6
;packageManager
是yarn
、pnpm
、npm
、bun
中的一个,则直接使用;// detect based on lock
if (!agent && lockPath)
agent = LOCKS[path.basename(lockPath)]
如果没有通过package.json
来获取packageManager
,则通过lockPath
来获取packageManager
。
这个方法的核心就是通过两个方式来获取packageManager
:
package.json
中的packageManager
字段;lock
文件来获取。可以说是非常巧妙。
我们继续看installPackage
方法:
const detectedAgent = options.packageManager || await detectPackageManager(options.cwd) || 'npm'
const [agent] = detectedAgent.split('@')
这里是第一行,就是获取packageManager
,和上面讲的方法相辅相成,继续往下看:
if (!Array.isArray(names))
names = [names]
这里是将name
统一变成数组,方便后面处理。
const args = options.additionalArgs || []
if (options.preferOffline) {
// yarn berry uses --cached option instead of --prefer-offline
if (detectedAgent === 'yarn@berry')
args.unshift('--cached')
else
args.unshift('--prefer-offline')
}
这里是处理preferOffline
参数,如果设置了这个参数,就会在args
中添加--prefer-offline
或者--cached
参数,因为yarn@berry
和npm
的参数不一样。
return execa(
agent,
[
agent === 'yarn'
? 'add'
: 'install',
options.dev ? '-D' : '',
...args,
...names,
].filter(Boolean),
{
stdio: options.silent ? 'ignore' : 'inherit',
cwd: options.cwd,
},
)
这里的命令是根据packageManager
来拼接的,yarn
和npm
的命令不一样,所以需要判断一下。
最后就是执行安装命令了,这里使用了execa
来执行命令,这个库的用法和child_process
差不多,但是更加方便,参考:execa。
通过学习这个库,我们可以学到很多东西,比如:
execa
来执行命令。同时这里面还穿插着很多node
的知识和包管理器的知识,比如:
node
的path.basename
方法;lock
文件;以上就是编程式安装依赖install-pkg源码解析的详细内容,更多关于编程式安装依赖install-pkg的资料请关注编程网其它相关文章!
--结束END--
本文标题: 编程式安装依赖install-pkg源码解析
本文链接: https://lsjlt.com/news/175764.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-12
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0