最近有要开发一个新的 Vue3 的需求,过往总是用 vite 或 vue-cli 新建一个项目,太多内容需要重新配置,效率未免太低,为了简化以后的开发,同时也尝试统一一下开发的项目架构,于是自己开发了一个 cli —— xmo-cli 。
npm地址 www.npmjs.com/package/xmo…
gitee地址 gitee.com/dXmo/xmo-cl…
思路
其实开发一个 cli 的程序难度并不大,实际上就是用命令行提问,然后根据回答执行相应的事宜。作为一个项目初始化的程序,有两种选择,一种是写好模板,再根据 cli 的问答来初始化模板;另一种是直接先写好一个初始的项目,根据 cli 的回答来 clone 不同的项目。
由于我初始化之后的项目规模比较大,所以我选择的是后者。
然后就是选择开发要用到的库,经过整理,开发一个 cli 需要如下库支持,
chalk- 自定义命令行console.log,改改命令行输出的字体颜色和背景颜色之类的。commander- 读取命令行的参数,例如rm -rf ./*这里的-rf、./*就是我所谓的参数。download-git-repo-clonegit项目。inquirer- 在命令行中提问并收集回答。ora- 显示加载中的loading效果。
项目规划
准备好库,就能开始
首先新建项目
npm init
得到 package.json ,接着创建目录 bin 和 src 。bin 是最终启动的程序的起点,而 src 是其中的抽离出来的细节逻辑。
在 bin 中创建程序 xmo-cli.mjs ,因为我想要用 ES6 的语法写,所以添加了后缀 .mjs 如果你打算用 require 的形式写则没有这个必要(即直接创建 xmo-cli 不要后缀)。
然后修改 package.json ,添加两个字段。
{
...
"type": "module",
"bin": {
"xmo-cli": "./bin/xmo-cli.mjs"
},
...
}
然后对 xmo-cli.mjs 稍作修改以做测试。(第一行指定运行这个程序所用的程序)
#!/usr/bin/env node
console.log('Hello Cli');
这个时候你已经可以运行这个 bin 程序了
./bin/xmo-cli.mjs
也可以在项目根目录的命令行里输入
npm install -g
然后你就可以全局调用 xmo-cli 了。
xmo-cli
# Hello Cli
接着安装相关依赖
npm install chalk commander download-git-repo inquirer ora
具体实现
具体实现中的难点在于学习 commander 、inquirer ,而 chalk 和 ora download-git-repo 的使用是简单的。如果有不理解的地方,直接看官方文档就好。
除了这几个外部调用的包之外,还用到了自带的 fs 和 path。下面是代码
/bin/xmo-cli.mjs
#!/usr/bin/env node
import commander from 'commander';
import { readFile } from 'fs/promises';
const pack = JSON.parse(
await readFile(new URL('../package.json', import.meta.url))
);
import init from '../src/init.js';
const _version = pack.version;
commander.version(_version);
commander
.command('init [dir]')
.alias('i')
.description('vue admin 项目初始化工具')
.action((dir) => {
init(dir);
});
commander.parse(process.argv);
/src/init.js
import { promises } from 'fs';
import Creator from './creator.js';
export const remote = 'direct:https://gitee.com/dXmo/xmo-cli.git';
export function isDirEmpty(dirname) {
return promises.readdir(dirname).then((files) => {
return files.length === 0;
});
}
const init = async (dir) => {
const project = new Creator();
await project.init(dir);
};
export default init;
/src/creator.js
import chalk from 'chalk';
import inquirer from 'inquirer';
import { existsSync } from 'fs';
import { remote, isDirEmpty } from './init.js';
import clone from './clone.js';
import { readFile, writeFile } from 'fs/promises';
import path, { dirname } from 'path';
class Creator {
constructor() {
// 存储命令行获取的数据,作为demo这里只要这两个;
this.options = {
name: '',
description: '',
};
}
// 初始化;
async init(dir) {
console.log(chalk.blueBright('Xmo-cli start creating project'));
if (dir) {
console.log(`将在${chalk.green(dir)}文件夹下创建项目。`);
if (existsSync(dir) && !(await isDirEmpty(dir))) {
console.log(`${chalk.green(dir)}${chalk.red('目录不为空。')}`);
return;
}
}
try {
const { name, description, type } = await this.ask(dir);
this.options.name = name;
this.options.description = description;
if (!dir) {
dir = name;
console.log(`将在${chalk.green(dir)}文件夹下创建项目。`);
if (existsSync(name) && !(await isDirEmpty(name))) {
console.log(`${chalk.green(dir)}${chalk.red('项目目录不为空。')}`);
return;
}
}
if (name) {
await clone(remote + '#' + type, dir);
await this.write(dir);
console.log(chalk.greenBright('Success!'), '运行代码,请执行如下指令');
console.log(chalk.greenBright(' cd ' + dir));
console.log(chalk.greenBright(' yarn'));
console.log(chalk.greenBright(' yarn dev'));
} else {
console.log(chalk.red('程序提前结束。'));
}
} catch (error) {
console.log(chalk.red(error));
}
}
// 和命令行交互;
async ask(dir) {
const initQuestions = (dir) => [
{
type: 'input',
name: 'name',
message: '请输入项目名称',
default: dir,
validate(input) {
if (!input) {
return '请输入项目名称!';
}
return true;
},
},
{
type: 'input',
name: 'description',
message: '请输入项目描述',
},
{
type: 'list',
name: 'type',
message: '请选择项目类型',
choices: ['mini'],
},
];
// 返回promise
return inquirer.prompt(initQuestions(dir));
}
// 写数据;
async write(dir) {
const pack = JSON.parse(await readFile(path.join(dir, 'package.json')));
Object.assign(pack, this.options);
await writeFile(
path.join(dir, 'package.json'),
JSON.stringify(pack, null, 2)
);
}
}
export default Creator;
/src/clone.js
克隆 git 代码
import { promisify } from 'util';
import ora from 'ora';
import chalk from 'chalk';
import downloadGit from 'download-git-repo';
const download = promisify(downloadGit);
const clone = async function (repo, dir, options = { clone: true }) {
const process = ora(`开始下载 ${chalk.blue(repo)}`);
process.start();
process.color = 'yellow';
process.text = `正在下载..... ${chalk.yellow(repo)} `;
try {
await download(repo, dir, options);
process.color = 'green';
process.text = `下载成功 ${chalk.green(repo)} `;
process.succeed();
} catch (error) {
console.log(error);
process.color = 'red';
process.text = '下载失败';
process.fail();
}
};
export default clone;
总结
代码很短,都是一些很简单的 IO ,我参考了这两个项目
成果
因为目前暂时只写了一个初始化项目,所以只能初始化构建一种项目类型,后期会再添加。
发布到npm
首先需要在 npm官网 注册一个账号并通过邮箱验证,然后在命令行登录并在项目根目录 publish 就可以发布了。
npm login
npm publish
记得写好 README.md 文件,提供一些使用指南。