实现一个cli,用于生成简单的Koa服务模板代码
背景
在日常开发中,有时会编写重复的Koa模板代码;这里笔者想像vue-cli一样,通过命令行输入对应字段,从而生成简单的koa模板。
学到什么
整体流程
cli任务划分
从上面的流程图中,可以大致了解到cli的主要任务是:
- 命令行数据获取
- 模板编写
- 模板与数据结合
cli项目目录
cli实现
命令行配置项获取
这里我们通过 inquirer npm包来获取用户命令行输入的配置项,主要实现如下:
// question/index.js
import inquirer from 'inquirer';
import packageQuestion from './package.js';
import portQuestion from './port.js';
export default () => {
return inquirer.prompt([
...packageQuestion(), // package.json所需字段值
...portQuestion(), // 端口值
])
}
// question/package.js
export default () => {
return [
// 每一项都按照 inquirer 所需的结构
{
type: 'input',
name: 'name',
message: 'package name',
default: 'koa-template'
}, {
type: 'input',
name: 'description',
message: 'package description'
}, {
type: 'input',
name: 'author',
message: 'package author'
}
]
}
// question/port.js
export default () => {
return [{
type: 'input',
name: 'port',
message: 'server port',
default: '3000'
}]
}
模板获取及数据填充
编写模板
这里文件代码模板我们用ejs(js模板)来处理。其中主要文件模板代码如下:
// bin/template/index.ejs
const Koa = require('koa');
const server = new Koa();
server.use(async (ctx) => {
ctx.body = 'Hello world'
});
server.listen(<%-port%>, () => {
console.log("start with localhost: <%-port%>");
})
// bin/template/package.ejs
{
"name": "<%-name%>",
"version": "1.0.0",
"description": "<%-description%>",
"main": "<%-main%>",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "<%-author%>",
"license": "ISC",
"dependencies": {
"koa": "^2.13.4"
}
}
数据填充
我们根据命令行获得到输入的配置项,并对上述的模板进行数据填充,以获取我们需要生成的代码,实现如下:
// bin/config.js
// 默认配置项
const defaultConfig = {
name: '',
description: '',
main: 'index.js',
author: '',
port: ''
}
export function createConfig(anwser = {}) {
return Object.assign({}, defaultConfig, anwser)
}
// index.js
import ejs from 'ejs';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import question from './question/index.js';
import { createConfig } from './config.js';
function getFileContent(filePath) {
return fs.readFileSync(filePath, { encoding: 'utf-8'});
}
// 获取命令行输入的值
let answer = await question();
// 合并默认Config
let config = createConfig(answer);
// esm 下没有 __dirname 变量
let __dirname = fileURLToPath(import.meta.url);
// 获取模板内容
let mainTemplate = getFileContent(path.resolve(__dirname, '../template/index.ejs'));
let packageJsonTemplate = getFileContent(path.resolve(__dirname, '../template/package.ejs'));
// 拼装模板内容
let mainData = ejs.render(mainTemplate, { port: config.port});
let packageJsonData = ejs.render(packageJsonTemplate, {
...config
});
数据与模板结合
经过上述模板数据填充后,我们可以直接使用fs模块生成项目以及相应的文件,实现如下:
// bin/index.js
// .... 省略上面部分
// 生成模板 map
let dirname = `./${config.name}`;
let fileMapping = {
[config.main]: mainData,
'package.json': packageJsonData
}
// 创建文件夹
fs.mkdirSync(dirname);
// 根据map,创建对应的文件
Object.keys(fileMapping).forEach((filename) => {
fs.writeFileSync(`${dirname}/${filename}`, fileMapping[filename]);
});
安装依赖
在上述项目生成后,我们可以为生成的项目进行依赖安装,省略用户手动安装依赖的过程。
这里我们借助实现execa来实现,如下:
// bin/index.js
import { execa } from 'execa';
// ....省略步骤代码
// 安装依赖
try {
execa("npm i", {
cwd: dirname,
stdio: [2, 2, 2]
});
}catch(err) {
console.log(err);
}
至此,Cli的实现我们就算完成了。
本地调试
经过上述几个步骤,cli的任务基本就完成了,后面我们需要进行调试。
由于cli基本是通过命令行方式,这里我们需要做一些处理来进行调试:
- package.json配置:配置bin属性
{
"name": "mini-koa-template-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"bin": "./bin/index.js", // 运行文件
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"ejs": "^3.1.6",
"execa": "^6.1.0",
"inquirer": "^8.2.1"
},
"type": "module"
}
- 入口文件配置:顶部添加: #!/usr/bin/env node,用于备注当前是在node下执行
// bin/index.js
#!/usr/bin/env node
import ejs from 'ejs';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// ....省略
- 软链到全局目录
在当前目录下,命令行输入 npm link,当前项目就被软链到全局中,此时我们可以通过全局命令来执行。
发包
在上述调试完毕后,如果需要,我们可以把当前项目发包到npm官网上,供其他开发者使用~
发包流程:
- npm官网注册账号
- 命令行登录npm:npm login
- 发布:npm publish
总结
通过实现mini-koa-template-cli,我们可以了解到cli内部的工作流程是什么,以及如何去实现一个简单的模板cli;学习本地如何调试npm包、怎么发包到npm。