mini-koa-template-cli实现

304 阅读3分钟

实现一个cli,用于生成简单的Koa服务模板代码

背景

在日常开发中,有时会编写重复的Koa模板代码;这里笔者想像vue-cli一样,通过命令行输入对应字段,从而生成简单的koa模板。

学到什么

  • 通过inquirer达到与命令行的交互
  • ejs模板引擎使用
  • execa使用
  • 简单cli实现
  • npm的发包

整体流程

image.png

cli任务划分

从上面的流程图中,可以大致了解到cli的主要任务是:

  • 命令行数据获取
  • 模板编写
  • 模板与数据结合

cli项目目录

image.png

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。