前提条件
在开始之前,请确保安装了 Node.js 的最新版本。使用 Node.js 最新的长期支持版本(LTS - Long Term Support),是理想的起步。使用旧版本,你可能遇到各种问题,因为它们可能缺少 webpack 功能以及/或者缺少相关 package 包。
建一个空文件夹
让我们在桌面建一个项目文件夹,名为 xp-cli ,并使用你的编辑器打开它。
打开终端,快捷键(Ctrl + ~)。
执行以下命令:
npm init -y
上面命令会在 xp-cli 的根目录生成 package.json 文件,该文件定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、描述信息等数据)。npm install 命令也是根据这个配置文件,自动下载所需的模块。
package.json
{
"name": "xp-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
+ "bin": {
+ "xp": "index.js"
+ }
}
创建index.js
#!/usr/bin/env node
console.log('123');
index.js 最上面必须加上 #!/usr/bin/env node 这行代码,不然绑定指令之后运行会报错。
绑定指令
这样我们就可以在全局任何地方通过输入 xp 命令来运行 index.js。
npm link
解绑指令:
npm unlink
此时我们项目的文件结构如下:
project
xp-cli
|- index.js
|- package-lock.json
|- package.json
现在执行xp结果为:
原生获取命令行参数的方式
使用:process.argv 关键字。
使用 Commander 解析命令行参数
npm install commander
在 index.js 文件中输入以下代码:
#!/usr/bin/env node
const { Command } = require("commander");
const program = new Command();
program.version("0.0.1");
program
.command("init <projectName> <tempName>")
.description("项目初始化")
.action((projectName, tempName) => {
console.log(projectName, tempName);
});
program
.command("list")
.alias("ls")
.description("查看可用项目模块")
.action(() => {
console.log(`
webpack5 + React
webpack5 + Vue
`);
})
program.parse(process.argv);
执行xp命令如下:
安装 download-git-repo 下载模板
npm i download-git-repo
仅支持以下三个仓库源:
- GitHub
- GitLab
- Bitbucket
可使用 clone-repo 下载
NPM 地址:
- 安装
npm i clone-repo
- 使用
clone-repo 是国内大佬基于 download-git-repo 3.0.2 版本扩展开发的,与 download-git-repo 不同的是 clone-repo 会返回一个 Promise,且 clone-repo 支持以下四个仓库源:
- GitHub
- GitLab
- Gitee
- Bitbucket
download的配置
const download = require("download-git-repo");
const templates = {
react: {
downloadUrl: "xiehaisheng/webpack5_react",
dec: "webpack5 + react CLI",
},
vue: {
downloadUrl: "xiehaisheng/webpack5_vue",
dec: "webpack5 + react CLI",
},
utils: {
downloadUrl: "xiehaisheng/js-utils.git",
dec: "TypeScript开发js函数库",
};
program
.command("init <projectName> <tempName>")
.description("项目初始化")
.action((projectName, tempName) => {
console.log(`项目名称为:${projectName}, 使用模板名:${tempName}`);
const { downloadUrl } = templates[tempName];
download(
// 下载目标,格式为:用户名/仓库名字#分支
downloadUrl,
// 下载完成后的项目名称,也就是文件夹名
projectName,
// 下载结束后的回调
(err) => {
// 如果错误回调不存在,就表示下载成功了
console.log(err ? `下载失败!${err}` : "下载成功!");
}
);
});
运行结果:
使用 inquirer 和 handlebars 采集处理用户信息
npm i inquirer handlebars
命令行交互 inquirer, 能够与用户在命令行进行参数选择交互。
修改模板的 package.json
注意:这里是修改模板的配置文件,而不是当前项目的配置文件。
inquirer 和 handlebars 的使用, 将其放在 download 的下载回调中。
const inquirer = require("inquirer");
const handlebars = require("handlebars");
const fs = require("fs");
download(
// 下载目标,格式为:用户名/仓库名字#分支
downloadUrl,
// 下载完成后的项目名称,也就是文件夹名
projectName,
// 下载结束后的回调
(err) => {
// 如果错误回调不存在,就表示下载成功了
if (err) {
return console.log(`下载失败!${err}`);
} else {
inquirer
.prompt([
{
type: "input",
name: "name",
message: "请输入项目名称",
},
{
type: "input",
name: "description",
message: "请输入项目简介",
},
{
type: "input",
name: "author",
message: "请输入作者名称",
},
])
.then((data) => {
const packagePath = `${projectName}/package.json`;
const packageContent = fs.readFileSync(packagePath, "utf-8");
var packageRes = handlebars.compile(packageContent)(data);
fs.writeFileSync(packagePath, packageRes);
console.log(`初始化模板成功`);
});
}
}
);
执行结果:
视觉美化
npm i ora chalk log-symbols
下载中 loading 效果
字体美化 chalk
日志符号 logSymbols
起因 ora 最新版只能使用 import 来导入,所以要设置当前项目的默认包管理为 ES6Module。
修改引入方式
import { Command } from "commander";
import download from "download-git-repo";
import inquirer from "inquirer";
import handlebars from "handlebars";
import fs from "fs";
import ora from 'ora';
const program = new Command();
分别在模板下载开始前,下载中,下载完后调用 ora 的 start()、fail()、succeed() 方法。
使用ora:
const spinner = ora('下载中...');
// 添加下载中样式,开始
loading.start();
// 调用 ora 下载失败方法,进行提示
loading.fail("下载失败:");
// 调用 ora 下载成功方法,进行提示
loading.succeed("下载成功!");
chalk 能够自定义使输出的命令行字体颜色
使用chalk:
import chalk from 'chalk';
console.log(chalk.blue('Hello world!'));
通过调用 logSymantec 的 success 和 error 输出成功与失败的日志符号。
使用logSymbols:
import logSymbols from 'log-symbols';
console.log(logSymbols.success, 'Finished successfully!');
综合使用:
.action((projectName, tempName) => {
// 添加下载中样式,开始
const spinner = ora(`项目名称为:${projectName}, 使用模板为:${tempName} 下载中...`);
spinner.start();
// console.log(`项目名称为:${projectName}, 使用模板名:${tempName}`);
const { downloadUrl } = templates[tempName];
download(
// 下载目标,格式为:用户名/仓库名字#分支
downloadUrl,
// 下载完成后的项目名称,也就是文件夹名
projectName,
// 下载结束后的回调
(err) => {
// 如果错误回调不存在,就表示下载成功了
if (err) {
// 调用 ora 下载失败方法,进行提示
spinner.fail(chalk.red('下载失败:'));
console.log(err);
return;
}
spinner.succeed('下载成功!');
inquirer
.prompt([
{
// 输入类型
type: "input",
// 字段名称
name: "name",
// 提示信息
message: "请输入项目名称",
},
{
type: "input",
name: "description",
message: "请输入项目简介",
},
{
type: "input",
name: "author",
message: "请输入作者名称",
},
])
.then((data) => {
const packagePath = `${projectName}/package.json`;
const packageContent = fs.readFileSync(packagePath, "utf-8");
var packageRes = handlebars.compile(packageContent)(data);
fs.writeFileSync(packagePath, packageRes);
console.log(logSymbols.success ,chalk.green('初始化模板成功'));
});
}
);
});
失败执行结果:
成功执行结果:
源码
#!/usr/bin/env node
import { Command } from "commander";
import download from "download-git-repo";
import inquirer from "inquirer";
import handlebars from "handlebars";
import fs from "fs";
import ora from "ora";
import chalk from "chalk";
import logSymbols from "log-symbols";
const program = new Command();
const templates = {
react: {
downloadUrl: "xiehaisheng/webpack5_react",
dec: "webpack5 + react CLI",
},
vue: {
downloadUrl: "xiehaisheng/webpack5_vue",
dec: "webpack5 + react CLI",
},
};
program.version("0.0.1");
program
.command("init <projectName> <tempName>")
.description("项目初始化")
.action((projectName, tempName) => {
// 添加下载中样式
const spinner = ora(
`项目名称为:${projectName}, 使用模板为:${tempName} 下载中...`
);
spinner.start();
const { downloadUrl } = templates[tempName];
download(
// 下载目标,格式为:用户名/仓库名字#分支
downloadUrl,
// 下载完成后的项目名称,也就是文件夹名
projectName,
// 下载结束后的回调
(err) => {
// 如果错误回调不存在,就表示下载成功了
if (err) {
spinner.fail(chalk.red("下载失败:"));
console.log(err);
return;
}
spinner.succeed("下载成功!");
inquirer
.prompt([
{
type: "input",
name: "name",
message: "请输入项目名称",
},
{
type: "input",
name: "description",
message: "请输入项目简介",
},
{
type: "input",
name: "author",
message: "请输入作者名称",
},
])
.then((data) => {
const packagePath = `${projectName}/package.json`;
const packageContent = fs.readFileSync(packagePath, "utf-8");
var packageRes = handlebars.compile(packageContent)(data);
fs.writeFileSync(packagePath, packageRes);
console.log(logSymbols.success, chalk.green("初始化模板成功"));
});
}
);
});
program
.command("list")
.alias("ls")
.description("查看可用项目模块")
.action(() => {
for (const key in templates) {
console.log(templates[key].dec);
}
});
program.parse(process.argv);
npm 发包
- 上 NPM 官网注册一个账号,官网:
- 上 NPM 搜索看有无重名包。
- 把 package.json 中的 name 修改为发布到 NPM 上的包名。
这个和本地项目名称无关的。
- 打开项目,执行登录命令:
npm login
- 登陆成功以后,在项目下执行发布命令:
npm publish
- 进行验证并下载我们自己的包
// 取消本地连接的全局指令
npm unlink
// 安装我们自己的包
npm i item-cli
{
"name": "item-cli",
"version": "0.0.2",
"description": "",
"type": "module",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"bin": {
"xhs": "index.js"
},
"dependencies": {
"chalk": "^5.0.1",
"clone-repo": "^1.0.2",
"commander": "^9.2.0",
"download-git-repo": "^3.0.2",
"handlebars": "^4.7.7",
"inquirer": "^8.2.2",
"log-symbols": "^5.1.0",
"ora": "^6.1.0"
}
}
测试
npm install -g item-cli
xhs init my-project react
# or
xhs init my-project vue