04-node脚手架 基于inquirer.js实现脚手架对话流程

上一篇文章我们确定了 项目的目标及方案,最后也搭建了一个基础的项目并关联了基础的命令。接下来我们会着手与核心的创建流程的对话设计,并生成逻辑进行封装。

代码地址:github.com/ZhahaSy/sy-... 欢迎Star fork

对话式流程

生成流程大概是怎样的呢?我画了一张简图

如图,整个create 命令可以拆分为三个功能: 生成配置功能创建项目功能下载模板功能。 而生成配置功能最主要的功能就是一直在设置选项供用户选择。在上篇文章中我们提到了inquirer用于做对话,为保证篇幅,不提供详细介绍,大家可以自行学习!

这篇文章讲了inquirer的基础使用。

juejin.cn/post/708514...

需要注意的是,最新版本的inquirer只支持 es6 module方案。但我们这个系列选择的是commonjs方案。大家在下载依赖的时候需要把inquirer改为8.x.x

使用之前请执行以下命令。

shell 复制代码
cd packages/cli

pnpm i  inquirer@8

pnpm i  chalk@4.1.1

pnpm i  fs-extra

基础使用

js 复制代码
const { prompt } = require('inquirer');

(async() => {
  // questions 可以传递对象也可以传递对象数组
  const questions = [{
    type: 'list',
    name: `projectType`,
    message: '你要创建什么项目?
    choices: [
      { name: 'web', value: 'web' },
      { name: 'service', value: 'service' },
      { name: 'template', value: 'template' },
    ]       
  }]
  const answers = await prompt(questions);
  console.log(answers); // { projectType: 'web' }
})()

代码结构拆分

通过上面流程图可以看出来,整个对话流程存在多个选择对应不同的问题。而inquirer并没有提供分支能力,所以我们需要自己控制问题分支。好在现在的流程不算复杂,我们本章节先实现一个简单版本的分支控制器。

下面我们先实现全流程通用的文件夹重名流程。

1.判断文件名是否存在

还记得在 cli/bin/sy.js文件的 create命令中的 --force后缀吗?force 翻译过来就是 强制的意思。在我们这里用于项目文件夹重名时强制删除旧文件夹。 如果没有设置force则会询问用户是否要替换。

下面是代码实现:

js 复制代码
const cwd = options.cwd || process.cwd() // 获取当前工作目录
const targetDir = path.resolve(cwd, projectName || '.') // 解析目标目录路径

// 检查目标目录是否已存在
if (fs.existsSync(targetDir)) {
  // 使用逻辑运算符简化判断
  if (!options.force) {
    const { isOverwrite } = await ask({
      type: 'confirm',
      name: 'isOverwrite',
      message: `当前路径下,项目 ${chalk.cyan(projectName)} 已存在,是否覆盖?`,
    });

    if (!isOverwrite) return; // 早期返回减少嵌套
  }

  console.log(`\nRemoving ${chalk.cyan(targetDir)}...`);
  await fs.remove(targetDir);
  console.log(`\nRemove ${chalk.cyan(targetDir)} success!`);
}

2. 询问项目类型

在上面的流程图中,我们期望脚手架提供 webservicetemplate三种项目类型,而不同的项目类型还对应着不同的对话分支,所以我们还是选择单独创建一个对话来控制分支

下面是代码实现:

js 复制代码
// 询问用户要创建的项目类型
const { projectType } = await ask([{
  type: 'list',
  name: 'projectType',
  message: '你要创建什么项目?',
  choices: [
    { name: 'web', value: 'web' },
    { name: 'service', value: 'service' },
    { name: 'template', value: 'template' },
  ]
}]);

3. 创建不同项目类型的promptOption

接下来就得创建不同项目类型的问题配置了。透过流程图可以看到,后面的问题都是插拔或者注入了,那么就可以把每个项目类型的问题配置都是单独的文件用于设置问题,公共的问题就抽出来一个公共的问题配置。

web

js 复制代码
const defaultPrompt = require('./default')
module.exports = [
  {
    type: 'list',
    name: `frameName`,
    message: '你要使用什么框架',
    choices: [
      { name: 'vue', value: 'vue' },
      { name: 'react', value: 'react' },
    ]        
  },
  ...defaultPrompt,
]

service

js 复制代码
const defaultPrompt = require('./default')
module.exports = [
  {
    type: 'list',
    name: `frameName`,
    message: '你要使用什么框架',
    choices: [
      { name: 'express', value: 'express' },
      { name: 'koa', value: 'koa' },
    ]        
  },
  ...defaultPrompt,
]

default

js 复制代码
module.exports = [
  {
    type: 'confirm',
    name: `isUseTs`,
    message: '是否使用Ts',
  },
  {
    type: 'confirm',
    name: `isLinter`,
    message: '是否接入tslint,eslint,commitLint',
  },
]

template

tips: 当前 git下载并非最终版本,还需要鉴权,登录git 等操作。这只是一个简单的模板。并非最终版本,只是为了理清思路

js 复制代码
module.exports = [
  {
    type: 'list',
    name: `templateUrl`,
    message: '请选择要下载的模板',
    choices: [
      { name: 'vue + mobile', value: 'vue-m' },
      { name: 'react + mobile', value: 'react-m' },
    ]
  },
  ...defaultPrompt,
]

调用map

最终我们应该通过一个对象或map整理所有的项目配置。projectType是我们在询问项目类型 的时候得到的。接下来就可以调用inquire.prompt方法来链式询问了。

js 复制代码
// 根据用户选择的项目类型,获取相应的配置选项并显示
const result = await ask(promptOptions[projectType]);
console.log('配置', { projectType, ...result });

最终对话结果

最终我们会得到如下图的一个配置对象。至此,本章要实现的功能已经实现了!

总结一下

开发中不要过分关注代码封装问题。先保做最基础的封装,不要过度设计,增加心理负担。

相关推荐
海上彼尚6 小时前
Nodejs也能写Agent - 17.LangGraph篇 - Checkpointer 与 Memory
前端·javascript·人工智能·langchain·node.js
特立独行的猫a10 小时前
HarmonyOS鸿蒙PC三方库移植:Node.js 三方库鸿蒙 PC 移植指南
华为·node.js·node·harmonyos·三方库移植·鸿蒙pc
jump_jump1 天前
Kysely 源码解剖:类型安全 SQL 如何走到 AST
node.js·orm·源码阅读
星栈1 天前
深度复盘:比 EventLoop 更隐蔽!Node 微任务堆积引发的线上接口雪崩事故
后端·node.js
万敏1 天前
Vue3 全栈实战第二周:Pinia + Vite + Router + axios 完整记录
vue.js·node.js·全栈
大家的林语冰1 天前
✌️ Deno 2.9 来了,ESM 直接导入 CSS,甚至能开发桌面应用?!
前端·javascript·node.js
laoli_coding2 天前
如何配置HTTPS站点访问的免费SSL域名证书
网络协议·https·node.js·ssl
To_OC2 天前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
大家的林语冰2 天前
👍 Rust 为 Node 插上翅膀,反超 Bun 和 pnpm,一体化工具我也有!
前端·javascript·node.js
花椒技术2 天前
Skill 落地系列一:Harness 试点复盘6 类真实业务场景的 Skill,全拆开给你看
agent·ai编程·前端工程化