【源码共读】编程式安装依赖 install-pkg

简介: 【源码共读】编程式安装依赖 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.jsonyarn.lockpnpm-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来判断使用哪个包管理器;


  1. 如果packageManageryarn,并且版本号大于1,则使用yarn@berry
  2. 如果packageManagerpnpm,并且版本号小于7,则使用pnpm@6
  3. 如果packageManageryarnpnpmnpmbun中的一个,则直接使用;
  4. 否则就打印一个警告。
// detect based on lock
if (!agent && lockPath)
    agent = LOCKS[path.basename(lockPath)]

如果没有通过package.json来获取packageManager,则通过lockPath来获取packageManager


这个方法的核心就是通过两个方式来获取packageManager


  1. 通过package.json中的packageManager字段;
  2. 通过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@berrynpm的参数不一样。

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

这里的命令是根据packageManager来拼接的,yarnnpm的命令不一样,所以需要判断一下。


最后就是执行安装命令了,这里使用了execa来执行命令,这个库的用法和child_process差不多,但是更加方便,

总结


通过学习这个库,我们可以学到很多东西,比如:


  1. 如何判断用户使用的包管理器;
  2. 如何查找文件;
  3. 如何使用execa来执行命令。


同时这里面还穿插着很多node的知识和包管理器的知识,比如:


  1. nodepath.basename方法;
  2. 包管理器的lock文件;
  3. 包管理器的参数和命令。


目录
相关文章
|
1月前
|
人工智能 架构师 开发者
破局2025:定义AI数字艺术新生态的十大关键人物
当AI艺术迈入生态竞争时代,十位创作者正重塑未来。他们超越技术炫技,以生态构建、商业创新与跨界影响力开辟新疆域。从乾元AIGC的多维赋能,到林响的数字乡建,再到织梦人的互动叙事,他们定义规则,连接技术与人文,成为AI艺术时代的建筑师与引路人。
|
SQL Oracle 关系型数据库
南大通用GBase 8s 数据库封锁与并发事务调度介绍
南大通用GBase 8s 数据库封锁与并发事务调度介绍
|
JavaScript 数据库
使用 Webpack 打包 node 程序,node_modules 真的被干掉啦
使用 Webpack 打包 node 程序,node_modules 真的被干掉啦
975 0
|
Web App开发 开发者
|
SQL 存储 关系型数据库
SQL默认索引是什么
在SQL数据库中,索引是一种用于提高查询性能的数据结构
|
物联网 应用服务中间件 Linux
CentOS7.9 Nginx+EMQX集群组建MQTTS平台
通过以上步骤,您已成功搭建了一个基于CentOS 7.9、Nginx和EMQX的MQTTS平台。这个平台既能保证数据传输的安全性,又能利用Nginx的负载均衡能力和EMQX的高性能、高并发处理能力,实现稳定高效的消息服务。在部署和配置过程中,务必注意证书、域名以及EMQX配置的正确性,确保系统安全和稳定运行。此外,定期更新软件和系统,以及监控系统性能,也是保证MQTTS平台长期稳定运行的重要环节。
476 3
|
分布式计算 资源调度 Hadoop
Hadoop软件与配置问题
【7月更文挑战第14天】
190 3
|
应用服务中间件 API nginx
|
NoSQL 关系型数据库 MySQL
Redis实现分布式锁
Redis实现分布式锁
176 0
|
算法 测试技术 开发工具
【软件设计师备考 专题 】系统设计基础:从总体到详细
【软件设计师备考 专题 】系统设计基础:从总体到详细
519 0