Youyuxi recommends artifact ni, which can replace npm/yarn/pnpm?

Welcome to pay attention to the front-end morning tea and advance together with Guangdong liangzai

Front end morning tea focuses on the front end, walks together, and keeps up with the development pace of the industry~


1. Preface

In this paper, Ni analysis is used to find a star^_^[1]

The articles are written and used   yarn  . The little partner pulled the latest warehouse code and found that   yarn install   Can't install dependency, report an error to me. So I went   github warehouse   A look, found youyuxi   Vue3 warehouse   from   yarn   Instead  ` pnpm`[2]. There is a sentence in the contribution document [3].

We also recommend installing ni[4] to help switching between repos using different package managers. ni also provides the handy nr command which running npm scripts easier.

We also recommend installing   ni[5]   To help switch between repos using different package managers. ni   It also provides convenient nr   Command to make it easier to run npm scripts.

this   ni   Although the project source code is   TS, I haven't used it   ts   Small partners are also very easy to understand, and the main file is actually not available   100 lines, very suitable for our study.

After reading this article, you will learn:

1. learn ni Use and understand its principle
2. Learn to debug learning source code
3. It can also be used in daily work ni
4. wait

2. Principle

github warehouse ni#how[6]

ni   Suppose you use a lock file (and you should)

Before it runs, it will detect your   yarn.lock  /  pnpm-lock.yaml  /  package-lock.json   To understand the current package manager and run the appropriate commands.

It may be difficult to understand from this sentence alone, but I still don't know what it is. Let me explain.

use `ni` When installing dependencies in a project:
   Suppose you have a lock file in your project `yarn.lock`,Then it will eventually execute `yarn install` Command.
   Suppose you have a lock file in your project `pnpm-lock.yaml`,Then it will eventually execute `pnpm i` Command.
   Suppose you have a lock file in your project `package-lock.json`,Then it will eventually execute `npm i` Command.

use `ni -g vue-cli` When installing global dependencies
    Default use `npm i -g vue-cli`

Of course not only `ni` Install dependencies.
    also `nr` - run
    `nx` - execute
    `nu` - upgrade
    `nci` - clean install
    `nrm` - remove

Looking at the source code, I found that all commands related to ni can be appended with \?, It means only printing, not real execution.

So global installation   ni   After, you can test as much as you like, for example   ni \?, nr dev --port=3000 \?, Because of printing, it can be executed in various directories, which is helpful for understanding   ni   Source code. I tested, as shown in the figure below:


Assuming that there is no lock file in the project directory, the user will select from npm, yarn and pnpm by default, and then execute the corresponding commands. However, if the global default configuration is set in the ~ /. nirc file, the corresponding command is executed using the default configuration.


; ~/.nirc

; fallback when no lock found
defaultAgent=npm # default "prompt"

; for global installs

Therefore, we can know that this tool must do three things:

1. Guess which package manager to use from the lock file npm/yarn/pnpm 
2. Smooth out the command differences of different package managers
3. Finally, run the corresponding script

Then keep looking   README   The use of other commands will be easy to understand.

3. Use

see   ni github documentation [7].

npm i in a yarn project, again? F**k!

ni - use the right package manager

Global installation.

npm i -g @antfu/ni

If the global installation encounters a conflict, we can add  -- force   Parameters force installation.

Give some common examples.

3.1 ni - install


# npm install
# yarn install
# pnpm install
ni axios

# npm i axios
# yarn add axios
# pnpm i axios

3.2 nr - run

nr dev --port=3000

# npm run dev -- --port=3000
# yarn run dev --port=3000
# pnpm run dev -- --port=3000
# Interactive selection of commands to execute
# interactively select the script to run
# supports convention
nr -

# Re execute the last executed command
# rerun the last command

3.3 nx - execute

nx jest

# npx jest
# yarn dlx jest
# pnpm dlx jest

4. Preparation before reading the source code

4.1 cloning

# It is recommended to clone my warehouse (my guarantee corresponds to the article version)
git clone
cd ni-analysis/ni
# npm i -g pnpm
# Installation dependency
pnpm i
# Of course, you can also use it directly ni

# Or clone the official warehouse
git clone
cd ni
# npm i -g pnpm
# Installation dependency
pnpm i
#  Of course, you can also use it directly   ni

As we all know, to see an open source project, start with the package.json file.

4.2 package.json file

    "name": "@antfu/ni",
    "version": "0.10.0",
    "description": "Use the right package manager",
    //  Six commands were exposed
    "bin": {
        "ni": "bin/ni.js",
        "nci": "bin/nci.js",
        "nr": "bin/nr.js",
        "nu": "bin/nu.js",
        "nx": "bin/nx.js",
        "nrm": "bin/nrm.js"
    "scripts": {
        //  Other commands are omitted   use   esno   implement   ts   file
        //  You can add  ?  It is convenient for debugging, or it can not be added
        //  Or a terminal   npm   run   dev  \?
        "dev": "esno src/ni.ts ?"

according to   dev   Command, we found the main entry file   src/ni.ts.

4.3 start debugging from the main source code entry

// ni/src/ni.ts
import { parseNi } from './commands'
import { runCli } from './runner'

//  We can break here

find   ni/package.json   of   scripts, move the mouse to   dev   Command, the run script and debug script commands appear. As shown in the following figure, select the debug script.


5. Main process runner - runCli function

This function is to parse the command line parameters passed in by the terminal. It was finally implemented   run   Function.

about   process   Readers who don't understand can read the process object written by Mr. Ruan Yifeng [8]

// ni/src/runner.ts
export async function runCli(fn: Runner, options: DetectOptions = {}) {
  // process.argv: returns an array whose members are all command line parameters of the current process.
  //  The first and second elements of process.argv are the fully qualified file system paths of Node executable files and executed JavaScript files, whether you enter them like this or not.
  const args = process.argv.slice(2).filter(Boolean)
  try {
    await run(fn, args, options)
  catch (error) {
    // The process.exit method is used to exit the current process. It can accept a numeric parameter. If the parameter is greater than 0, it indicates execution failure; If equal to 0, the execution is successful.

Let's go on, run   Function.

6. Main process runner - run main function

This function mainly does three things:

1. Guess which package manager to use from the lock file npm/yarn/pnpm - detect function
2. Smooth out the command differences of different package managers - parseNi function
3. Finally, run the corresponding script - execa tool
// ni/src/runner.ts
//  The source code has been deleted
import execa from 'execa'
const DEBUG_SIGN = '?'
export async function run(fn: Runner, args: string[], options: DetectOptions = {}) {
  //  Command parameters include   question mark?   Is debug mode and does not execute scripts
  const debug = args.includes(DEBUG_SIGN)
  if (debug)
    //  In debug mode, delete this question mark
    remove(args, DEBUG_SIGN)

  //  cwd   Method returns the current directory (absolute path) of the process
  let cwd = process.cwd()
  let command

  //  Support assignment   File directory
  // ni -C packages/foo vite
  // nr -C playground dev
  if (args[0] === '-C') {
    cwd = resolve(cwd, args[1])
    //  Delete these two parameters  - C   packages/foo
    args.splice(0, 2)

  //  If it is a global installation, use the global package manager
  const isGlobal = args.includes('-g')
  if (isGlobal) {
    command = await fn(getGlobalAgent(), args)
  else {
    let agent = await detect({ ...options, cwd }) || getDefaultAgent()
    //  Guess which package manager to use. If no lock file is found, it will return   null, call   getDefaultAgent   Function. The default return is for the user to select   prompt
    if (agent === 'prompt') {
      agent = (await prompts({
        name: 'agent',
        type: 'select',
        message: 'Choose the agent',
        choices: => ({ title: value, value })),
      if (!agent)
    //  there   fn   yes   Function passed in parsing code
    command = await fn(agent as Agent, args, {
      hasLock: Boolean(agent),

  //  If there is no command, return directly to the previous one   runCli   Function reports an error and exits the process
  if (!command)

  //  If it is in debug mode, print the command directly. Debugging is very useful.
  if (debug) {
    // eslint-disable-next-line no-console

  //  Final use   execa   Execute commands, such as   npm   i
  //  Introduction: Process execution for humans

  await execa.command(command, { stdio: 'inherit', encoding: 'utf-8', cwd })

After learning the main process, let's look at two important functions: detect   Function, parseNi   Function.

According to the entrance, we can know.



here fn then is parseNi

6.1 guess which package manager (npm/yarn/pnpm) - detect function to use according to the lock file

The code is relatively small, so I put it all out.

Three main things have been done

1. Locate the lock file under the project root path. Return to the corresponding package manager `npm/yarn/pnpm`.
2. If not, go back `null`.
3. If it is found, but the user's computer does not have this command, ask the user whether to install it automatically.
// ni/src/agents.ts
export const LOCKS: Record<string, Agent> = {
  'pnpm-lock.yaml': 'pnpm',
  'yarn.lock': 'yarn',
  'package-lock.json': 'npm',
// ni/src/detect.ts
export async function detect({ autoInstall, cwd }: DetectOptions) {
  const result = await findUp(Object.keys(LOCKS), { cwd })
  const agent = (result ? LOCKS[path.basename(result)] : null)

  if (agent && !cmdExists(agent)) {
    if (!autoInstall) {
      console.warn(`Detected ${agent} but it doesn't seem to be installed.\n`)

      if (process.env.CI)

      const link = terminalLink(agent, INSTALL_PAGE[agent])
      const { tryInstall } = await prompts({
        name: 'tryInstall',
        type: 'confirm',
        message: `Would you like to globally install ${link}?`,
      if (!tryInstall)

    await execa.command(`npm i -g ${agent}`, { stdio: 'inherit', cwd })

  return agent

Then let's see   parseNi   Function.

6.2 smoothing command differences between different package managers - parseNi function

// ni/src/commands.ts
export const parseNi = <Runner>((agent, args, ctx) => {
  //  ni  - v   Output version number
  if (args.length === 1 && args[0] === '-v') {
    // eslint-disable-next-line no-console
    console.log(`@antfu/ni v${version}`)

  if (args.length === 0)
    return getCommand(agent, 'install')
  //  Omit some code

adopt   getCommand   Get command.

// ni/src/agents.ts
//  With deletion
//  A configuration and write commands in these three package managers.

export const AGENTS = {
  npm: {
    'install': 'npm i'
  yarn: {
    'install': 'yarn install'
  pnpm: {
    'install': 'pnpm i'
// ni/src/commands.ts
export function getCommand(
  agent: Agent,
  command: Command,
  args: string[] = [],
) {
  //  The package manager is not present   AGENTS   An error is reported in the middle
  //  such as   npm   be not in
  if (!(agent in AGENTS))
    throw new Error(`Unsupported agent "${agent}"`)

  //  Get command   Installation corresponds to   npm   install
  const c = AGENTS[agent][command]

  //  If it is a function, execute the function.
  if (typeof c === 'function')
    return c(args)

  //  command   If it is not found, an error is reported
  if (!c)
    throw new Error(`Command "${command}" is not support by agent "${agent}"`)
  //  Finally spliced into command string
  return c.replace('{0}', args.join(' ')).trim()

6.3 finally run the corresponding script

Get the corresponding command, such as   npm i, finally use this tool   execa[9]   Execute the resulting corresponding script.

await execa.command(command, { stdio: 'inherit', encoding: 'utf-8', cwd })

7. Summary

After reading the source code, we can know this artifact   ni   Three main things have been done:

1. Guess which package manager to use from the lock file npm/yarn/pnpm - detect function
2. Smooth out the command differences of different package managers - parseNi function
3. Finally, run the corresponding script - execa tool

In our daily development, it may be easy   npm,yarn,pnpm   Mix. Yes   ni   After, it can be used for daily development. Vue   Core members   Anthony Fu[10]   Found the problem and finally developed a tool   ni[11]   solve the problem. This ability to find and solve problems is what our front-end development engineers need.

In addition, I found that   Vue   Many ecological systems have basically been switched to use   pnpm[12].

Because the article should not be too long, it does not fully describe all the details in the source code. It is highly recommended that readers use VSCode debugging according to the method in the article   ni   Source code. After learning to debug the source code, the source code is not as difficult as expected.

reference material

[1] In this paper, we use Ni analysis to find a star ^ ^:


[3] Contribution document:



[6]github warehouse ni#how:

[7]ni github documentation:

[8] The process object written by Mr. Ruan Yifeng:


[10]Anthony Fu:



Posted on Wed, 10 Nov 2021 17:50:30 -0500 by macdumbpling