我亲手造了一个 npm CLI 命令,然后发现 vite、eslint 全是这么来的
实习第一天,我连「npm 包怎么变成一个命令行命令」都没认真想过。直到今天,我和一个 AI 搭子从零搓了一个
hello命令------然后突然意识到,我每天在用的那些工具,背后藏的是同一套小秘密。
一、先建一个 npm 项目
bash
bash
mkdir hello-cli
cd hello-cli
npm init -y
跑完之后,目录里多了一个 package.json:
go
hello-cli/
└── package.json
这一步的意义其实就一句话:
告诉 npm:「这里是一个 Node.js 包项目。」
二、在 package.json 里声明一个命令
我们往 package.json 里加了一行关键的东西:
json
json
{
"name": "hello-cli",
"version": "1.0.0",
"bin": {
"hello": "./cli.js"
}
}
重点就是这段:
json
json
"bin": {
"hello": "./cli.js"
}
它的意思是:
如果用户在终端输入
hello,就去执行./cli.js。
bin 本质上是一张「命令名 → 文件」的地图 🗺️。
三、写真正干活的代码
然后我们创建了 cli.js:
javascript
javascript
#!/usr/bin/env node
console.log("🎉 Hello! 我的第一个 npm CLI 包!");
开头这行:
javascript
javascript
#!/usr/bin/env node
是告诉系统:
用 Node.js 来运行这个文件。
下面那行 console.log,就是这个命令真正要做的事。
四、把它装到全局
接下来执行:
bash
erlang
npm install -g .
这里两个细节:
.表示「当前目录这个包」-g表示「全局安装」
npm 做了一件很关键的事------它读了 package.json,发现了 bin,然后在系统里注册了 hello 这个命令。
五、终于可以直接运行了
以前要这样跑:
bash
node cli.js
现在只要:
bash
hello
终端立刻输出:
objectivec
🎉 Hello! 我的第一个 npm CLI 包!
🎉 你的电脑刚刚「认识」了一个你自己创造的新命令。
整个流程串起来
go
创建项目
│
↓
package.json
│
↓
定义 bin:hello → cli.js
│
↓
npm install -g .
│
↓
系统注册 hello 命令
│
↓
终端输入 hello
│
↓
Node 执行 cli.js
│
↓
看到输出
我们真正搞懂的,其实就这几样东西:
| 东西 | 作用 |
|---|---|
package.json |
描述 npm 包的信息 |
bin |
暴露命令入口(命令名 → 文件) |
cli.js |
命令实际执行的代码 |
#!/usr/bin/env node |
指定用 Node 执行(主要给 Unix 看) |
npm install -g |
全局安装命令 |
| CLI | Command Line Interface,命令行工具 |
让我「哦!」一下的三个彩蛋
这个实验做完,我又追问了几个问题,结果挖出三个挺反直觉的点:
1. 在 Windows 上,那行 shebang 其实「没生效」
我写的 #!/usr/bin/env node,是 Unix / Mac 的规矩。Windows 根本不看这行。
那我的 hello 为什么还能跑?因为 npm install -g 在 Windows 上不是建软链接,而是生成了几个包装文件:
objectivec
hello ← 给 Git Bash 用
hello.cmd ← 给 CMD 用
hello.ps1 ← 给 PowerShell 用
真正找到 node 去执行 cli.js 的,是这几个 .cmd / .ps1。shebang 只是「跨平台留个后手」,让同一份代码在 Mac 上也能跑。很多人以为删了 shebang Windows 就坏了------其实没关系。
2. 开发时别用 npm install -g .,用 npm link
install -g . 是「复制一份装进全局」。你每改一次 cli.js,都得重装,很烦。
开发阶段应该这样:
bash
bash
npm link # 在包目录里执行一次
它会做一个软链接 指向你的项目。之后你改代码,hello 命令立刻是最新的,不用重装。用完 npm unlink 解绑即可。
3. 命令还分「全局」和「本地」
你装的是全局命令 (-g)。但真实项目里,90% 的命令其实是本地的,躺在:
bash
node_modules/.bin/
比如你 npm install 之后,vite 其实装在 node_modules/.bin/vite,并没进全局。你能在 package.json 的 scripts 里直接写 "dev": "vite",是因为 npm 跑 script 时会自动把 node_modules/.bin 加进 PATH。
结尾:你刚打开的是一扇门
最有意思的一点是------你刚刚做的事,和很多大型工具本质一样:
markdown
你的 hello
↓
npm 包
↓
bin
↓
终端命令
和:
markdown
vite
↓
npm 包
↓
bin
↓
vite 命令
是同一套机制。
只是 Vite 里面不是打印一句 Hello,而是启动开发服务器、构建项目 😂
所以你刚刚其实不是「玩了个小例子」,而是亲手拆开了 npm 生态的一扇门 🚪✨ 以后看到 vite、eslint、tsc 这些命令,你会知道它们背后都藏着一个类似的 bin 小秘密。
一句话钉进脑子:
bin定义命令、scripts编排命令、.bin存放本地命令、npm link用于开发、shebang 是给 Unix 看的。

本文所有示例均为脱敏后的通用技术演示,不涉及任何公司项目细节。