学了很多年前端,居然才知道 npm-which

69 阅读2分钟

npm-which 学习

简介

npm-which 是一个用于在 Node.js 项目中定位可执行文件的工具,特别适用于需要执行项目依赖中命令行工具的场景。

安装

npm install npm-which --save

基础用法

const which = require('npm-which')(__dirname)

// 同步方式
try {
  const eslintPath = which.sync('eslint')
  console.log('ESLint 路径:', eslintPath)
} catch (err) {
  console.error('未找到 ESLint')
}

// 异步方式
which('prettier')
  .then(path => console.log('Prettier 路径:', path))
  .catch(err => console.error('未找到 Prettier'))

API 说明

初始化

const which = require('npm-which')(startPath)
  • startPath: 开始查找的目录路径,通常传入 __dirname

方法

  1. which.sync(command)
  • 同步查找可执行文件
  • 参数:
    • command: 要查找的命令名称
  • 返回:命令的完整路径
  • 抛出:如果未找到则抛出错误
  1. which(command)
  • 异步查找可执行文件
  • 参数:
    • command: 要查找的命令名称
  • 返回:Promise

使用场景

  1. CLI 工具开发
const which = require('npm-which')(__dirname)
const execa = require('execa')

async function runESLint() {
  const eslintBin = which.sync('eslint')
  await execa(eslintBin, ['src/**/*.js'])
}
  1. 项目构建工具
const which = require('npm-which')(__dirname)
const {spawn} = require('child_process')

function build() {
  const webpackBin = which.sync('webpack')
  spawn(webpackBin, ['--config', 'webpack.config.js'])
}
  1. 开发环境工具链
const which = require('npm-which')(__dirname)

function getToolPaths() {
  return {
    prettier: which.sync('prettier'),
    eslint: which.sync('eslint'),
    typescript: which.sync('tsc')
  }
}

优点

  1. 依赖版本控制
  • 优先使用项目依赖中的版本
  • 避免全局工具版本冲突
  • 确保团队使用相同版本
  1. 路径解析可靠
  • 遵循 npm 的模块解析规则
  • 支持 node_modules 层级查找
  • 处理符号链接
  1. 跨平台兼容
  • 支持 Windows/Unix 路径
  • 自动处理文件扩展名(.exe 等)
  1. 使用简单
  • API 简洁明了
  • 支持同步/异步操作
  • 错误处理友好

缺点

  1. 性能考虑
  • 同步操作可能阻塞事件循环
  • 多次查找可能影响性能
  1. 功能局限
  • 仅支持 npm 包管理器
  • 不支持自定义查找规则
  • 缺少高级配置选项

最佳实践

  1. 缓存查找结果
const which = require('npm-which')(__dirname)
const binCache = new Map()

function getBinPath(command) {
  if (!binCache.has(command)) {
    binCache.set(command, which.sync(command))
  }
  return binCache.get(command)
}
  1. 错误处理
function safeGetBin(command) {
  try {
    return which.sync(command)
  } catch (err) {
    console.error(`命令 ${command} 未找到,请确保已安装相关依赖`)
    process.exit(1)
  }
}
  1. 与其他工具集成
const which = require('npm-which')(__dirname)
const execa = require('execa')

async function execBin(command, args = []) {
  const binPath = which.sync(command)
  return execa(binPath, args, {
    stdio: 'inherit',
    preferLocal: true
  })
}

注意事项

  1. 路径问题
  • 始终使用 __dirname 作为起始路径
  • 注意处理相对路径
  • 考虑 monorepo 项目结构
  1. 错误处理
  • 总是包含错误处理逻辑
  • 提供有意义的错误信息
  • 考虑降级方案
  1. 性能优化
  • 缓存常用命令路径
  • 避免频繁同步调用
  • 合理使用异步 API