一、创建自定义全局指令
npm link是Node.js开发中一个非常实用的命令,它允许你在本地项目和本地npm包之间建立连接,方便在开发过程中进行模块调试,而无需反复发布到npm仓库。
简单来说,npm link通过创建符号链接(类似于快捷方式),让你正在开发的npm包能够被其他本地项目实时引用。当你对包的代码进行修改后,引用它的项目可以立即看到效果,无需重新安装。
1.1 主要使用场景
-
开发自己的Vue/React组件库、工具库等npm包。
-
维护内部或私有的npm包。
-
需要频繁修改某个依赖包,并立即在项目中测试其效果。
1.2 基本用法
假设有两个项目:
-
npm包项目:
my-awesome-lib。 -
使用包的项目:
my-project。
第一步:创建全局链接。
-
在终端中进入你的npm包项目目录。
shellcd path/to/my-awesome-lib -
执行
npm link命令。shellnpm link这个命令会在全局的
node_modules目录下创建一个指向my-awesome-lib项目目录的符号链接。
第二步:在项目中链接本地包。
-
进入你想要使用该包的项目目录。
shellcd path/to/my-project -
执行
npm link <包名>命令。这里的<包名>是你的npm包package.json文件中name字段的值,而不是文件夹名称。shellnpm link my-awesome-lib这个命令会在
my-project的node_modules目录下创建一个符号链接,指向全局的那个链接,最终指向你的my-awesome-lib源码目录。
现在,你就可以在my-project中像使用普通依赖一样import或require这个包了。你对my-awesome-lib源码的任何修改都会立即反映在my-project中。
1.3 进阶用法
如果你的npm包和使用它的项目在同一个父目录下,可以使用相对路径一步完成链接。
shell
# 在my-project目录下执行。
cd path/to/my-project
npm link ../my-awesome-lib
1.4 特殊用法:链接命令行工具
如果你正在开发一个命令行工具(CLI),需要在package.json中配置bin字段。执行npm link后,你就可以在全局环境中直接使用该命令。
package.json 示例:
shell
{
"name": "my-cli-tool",
"version": "1.0.0",
"bin": {
"my-cli-tool": "./cli.js"
}
}
在my-cli-tool项目目录下执行npm link后,你就可以在任何地方通过my-cli-tool命令来运行它。
1.5 如何解除链接
当你完成开发或调试后,需要解除链接。
-
解除项目中的链接:在使用包的项目目录下执行。
shellcd path/to/my-project npm unlink my-awesome-lib -
解除全局链接:在npm包项目目录下执行。
shellcd path/to/my-awesome-lib npm unlink
通常建议按照先项目后全局的顺序来解除。
1.6 Shebang
js
#! /usr/bin/env node
// ...
这行代码被称为Shebang(或Hashbang),它的作用是让JavaScript文件能够像一个独立的可执行程序一样,直接在命令行中运行,而无需每次都手动输入node命令。
这是开发Node.js命令行工具(CLI)或脚手架时的标准配置。
1.6.1 逐段解析
把#!/usr/bin/env node拆解成三个部分来理解:
-
#!(Shebang)。这是一个特殊的标记,告诉操作系统(特别是Unix/Linux/macOS系统):"不要用Shell来执行这个脚本,而是用后面指定的解释器来运行它。"
-
/usr/bin/env(环境工具)。这是一个标准的系统命令,它的作用是在环境变量
PATH中查找指定的程序。-
为什么用它?
因为不同用户的电脑上,Node.js的安装路径可能不同(例如
/usr/bin/node、/usr/local/bin/node等)。如果直接写死路径(如#!/usr/bin/node),一旦路径不对脚本就会报错。 -
好处。
使用
env可以让系统自动去环境变量里找node在哪里,从而实现了跨平台兼容。
-
-
node(解释器)。这是
env命令要查找的目标程序。意思是告诉系统:"请找到Node.js解释器,并用它来执行当前这个文件。"
1.6.2 它解决了什么问题?
如果没有这行代码,你运行一个JS文件必须这样写:
shell
node index.js
加上这行代码并赋予执行权限后,你可以直接运行文件:
shell
./index.js
1.6.3 如何让它生效?
仅仅在文件第一行写上它是不够的,你还需要给文件添加可执行权限。
完整步骤如下:
-
在文件第一行添加代码。
js#! /usr/bin/env node console.log("我是一个可执行的脚本!"); -
赋予执行权限(在终端执行)。
jschmod +x index.js -
直接运行。
js./index.js
1.6.4 常见应用场景
-
开发脚手架工具 :比如Vue CLI(
vue create ...)或Create React App,它们的入口文件第一行都有这个标记。 -
npm包的bin命令 :当你在
package.json中配置bin字段时,对应的文件必须包含这行代码,这样用户安装你的包后才能通过命令直接调用。
1.6.5 注意事项
-
必须放在第一行:它必须是文件的绝对第一行,前面不能有空行或注释,否则无效。
-
Windows系统:原生Windows CMD或PowerShell不直接支持Shebang,但在Git Bash、WSL(Windows Subsystem for Linux)或通过npm全局安装时,它依然能正常工作。
二、处理命令参数
当我们按照第一章第1.4节中的方法创建好全局命令后,
2.1 process.argv命令行参数
js
#! /usr/bin/env node
console.log(process.argv);
执行命令:
shell
./index.js --help -p
输出结果:
shell
[
'C:\\Users\\CyrusCJA\\dev_kits\\nvm4w\\nodejs\\node.exe',
'C:\\Users\\CyrusCJA\\Desktop\\workspace\\node_demo\\index.js',
'--help',
'-p'
]
-
第一个参数表示的是使用了哪一个脚本来执行代码的。
-
第二个参数表示的是执行的代码文件所在的位置。
-
后面的参数则是执行命令时添加的参数值。
所以我们可以通过如下代码获取命令参数:
js
if (process.argv[2] === '--help') {
// ...
}
参数较多时,这样写很麻烦,我们可以使用commander命令参数处理工具依赖包。
2.2 commander命令行指令设置
shell
npm install commander
js
#! /usr/bin/env node
import { program } from "commander";
program.option(
"-f --framework <framework>",
"描述(尖括号表示参数后面必须有值)",
"默认值",
);
program.option("-p --port", "描述(尖括号表示参数后面必须有值)", "默认值");
/*
[other...]是命令参数,表示可以有多个参数,参数名称可以自定义,参数名称可以省略。
去掉它则只能有一个参数<project-name>。
.alias(...)是为命令create起了一个别名,使用时既可以 ... create ...,也可以... crt ...。
.description(...)是为命令create添加一个描述,使用时可以查看命令的描述。
.action(...)是为命令create添加一个操作函数,使用时会执行该函数。
该函数的参数options是命令行参数,参数args是命令行参数的数组。
*/
program
.command("create <project-name> [other...]")
.alias("crt")
.description("创建项目")
.action((options, args) => {
// 命令的执行逻辑代码。
console.log(options);
console.log(args);
});
program
.command("deploy <environment>") // 1. 定义一个'deploy'命令,需要一个环境参数。
.description("部署到指定环境")
.option("-b, --branch <name>", "指定要部署的分支") // 2. 为'deploy'命令添加一个'--branch'选项。
.option("-f, --force", "强制部署") // 3. 为'deploy'命令添加一个'--force'选项。
.action((environment, options) => {
// 4. 在action回调中,可以同时获取命令参数和选项。
console.log(`正在部署到: ${environment}`);
console.log(`options参数值: ${options}。具体内容如下:`);
console.log(options);
if (options.branch) {
console.log(`使用分支: ${options.branch}`);
}
if (options.force) {
console.log("强制部署模式开启");
}
});
// .parse(...)是解析命令行参数,必须放在最后。
program.parse(process.argv);
/*
测试用例:
./index.js -f
./index.js -f xxx
./index.js -f xxx abc
./index.js -p
./index.js -p xxx
./index.js create
./index.js crt xxx
./index.js crt xxx abc def
./index.js deploy xxx
./index.js deploy xxx --branch xxx
./index.js deploy xxx --branch xxx --force
*/

2.3 inquirer命令行问答交互
shell
npm install inquirer
js
#! /usr/bin/env node
import { program } from "commander";
import inquirer from "inquirer";
program
.command("create <project-name> [other...]")
.alias("crt")
.description("创建项目")
.action(() => {
// 命令的执行逻辑代码。
inquirer
.prompt([
// 对象中的属性如下图所示。
{
// 问题类型:input,表示用户需要输入一个值。
type: "input",
// 问题名称,用于在answers对象中获取用户输入的值。
name: "projectName",
// 问题默认值,用户不输入时使用。
default: "my-project",
// 问题提示,用户输入时显示。
message: "请输入项目名称",
},
{
// 即可通过上下键切换选项并使用Enter键选择。也可输入相应选项号并按Enter键选择。
type: "rawlist",
name: "framework",
message: "请选择要使用的框架:",
choices: ["React", "Vue", "Angular"],
// 此时默认值无效。
// default: "VueVue",
},
{
// 只能通过上下键切换选项并使用Enter键选择。
type: "select",
name: "like",
message: "请选择你喜欢的明星:",
choices: ["张三", "李四", "王五"],
// 此时默认值无效。
// default: "VueVue",
},
])
.then((answers) => {
console.log(answers);
console.log(answers.projectName);
});
});
program.parse(process.argv);

2.4 download-git-repo下载远程仓库模板代码
shell
npm install download-git-repo
js
#! /usr/bin/env node
import { program } from "commander";
import inquirer from "inquirer";
import downloadGitRepo from "download-git-repo";
program
.command("create <project-name> [other...]")
.alias("crt")
.description("创建项目")
.action(() => {
// 命令的执行逻辑代码。
inquirer
.prompt([
{
type: "rawlist",
name: "framework",
message: "请选择要使用的框架:",
choices: ["React", "Vue", "Angular"],
},
])
.then((answers) => {
switch (answers.framework) {
case "React":
/*
参数:
第一个:仓库地址,第二个:下载到哪个目录,
第三个:控制下载的行为,第四个:回调函数。
第三个参数对象中的属性:
1、clone(类型:Boolean,默认值:false):是否使用git clone模式。
1.1 true: 使用git clone(适合私有仓库或需要完整git历史)。
1.2 false: 使用git archive或下载zip包(速度更快,不包含.git目录)。
2、cache(类型:Boolean,默认值:true):是否启用缓存。
2.1 true: 如果本地已有该仓库的缓存,则直接使用,不再重新下载。
2.2 false: 强制重新下载。
3、force(类型:Boolean,默认值:false):是否强制覆盖目标目录。
3.1 true: 如果目标文件夹已存在,强制删除并重新下载。
3.2 false: 如果目标文件夹存在,可能会报错或跳过。
4、head(类型:Boolean,默认值:false):是否下载HEAD版本。
4.1 true: 忽略指定的分支或 tag,直接下载默认分支的最新提交。
5、checkout(类型:String,默认值:-):指定要检出的分支或Tag。
5.1 例如:'main', 'dev', 'v1.0.0'。
第四个参数:回调函数。
1、err:错误信息。
2、err:null,下载成功。
注意:该函数是异步的。
*/
downloadGitRepo(
"direct:https://gitee.com/ascend/DrivingSDK.git",
"./node_demo",
{ clone: true },
(err) => {
if (err) {
console.log("下载失败!");
} else {
console.log("下载成功!");
}
},
);
break;
case "Vue":
console.log("使用 Vue 创建项目");
break;
case "Angular":
console.log("使用 Angular 创建项目");
break;
}
});
});
program.parse(process.argv);

2.5 ora命令行任务等待工具
shell
npm install ora
js
#! /usr/bin/env node
import { program } from "commander";
import inquirer from "inquirer";
import downloadGitRepo from "download-git-repo";
import ora from "ora";
program
.command("create <project-name> [other...]")
.alias("crt")
.description("创建项目")
.action(() => {
// 命令的执行逻辑代码。
inquirer
.prompt([
{
type: "rawlist",
name: "framework",
message: "请选择要使用的框架:",
choices: ["React", "Vue", "Angular"],
},
])
.then((answers) => {
switch (answers.framework) {
case "React":
// spinner1和spinner2两种写法等效。
// const spinner1 = ora("downloading111...");
const spinner2 = ora();
// spinner2.text = "downloading222...";
// 此时会一直显示等待中动画。哪怕其他异步代码执行完后,它也会接着显示。
// spinner2.start();
// 也可将spinner2.text写在start('...')中。
spinner2.start("downloading333...");
downloadGitRepo(
"direct:https://gitee.com/ascend/DrivingSDK.git",
"./node_demo",
{ clone: true },
(err) => {
if (err) {
// 中断等待动画函数。
// 提示字符串前面有一个对号。
// spinner2.succeed('执行成功!');
// 提示字符串前面有一个叉号。
spinner2.fail("执行失败!");
// 提示字符串前面有一个倒立的感叹号。
// spinner2.info('执行通知!');
} else {
spinner2.succeed("执行成功!");
}
},
);
break;
case "Vue":
console.log("使用 Vue 创建项目");
break;
case "Angular":
console.log("使用 Angular 创建项目");
break;
}
});
});
program.parse(process.argv);

2.6 chalk命令行样式渲染工具
shell
npm install chalk
js
#! /usr/bin/env node
import { program } from "commander";
import inquirer from "inquirer";
import downloadGitRepo from "download-git-repo";
import ora from "ora";
import chalk from "chalk";
program
.command("create <project-name> [other...]")
.alias("crt")
.description("创建项目")
.action(() => {
// 命令的执行逻辑代码。
inquirer
.prompt([
{
type: "rawlist",
name: "framework",
message: "请选择要使用的框架:",
choices: ["React", "Vue", "Angular"],
},
])
.then((answers) => {
switch (answers.framework) {
case "React":
ora(
chalk.green("正在下载中...") +
chalk.red("请稍后...") +
chalk.rgb(255, 165, 0)("不要急..."),
).start();
downloadGitRepo(
"direct:https://gitee.com/ascend/DrivingSDK.git",
"./node_demo",
{ clone: true },
(err) => {
if (err) {
spinner.fail("执行失败!");
} else {
spinner.succeed("执行成功!");
}
},
);
break;
case "Vue":
console.log("使用 Vue 创建项目");
break;
case "Angular":
console.log("使用 Angular 创建项目");
break;
}
});
});
program.parse(process.argv);
