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. 询问项目类型

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

下面是代码实现:

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 });

最终对话结果

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

总结一下

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

相关推荐
雨季mo浅忆14 小时前
记录Node.js安全漏洞及其内置Brotli解码器问题
node.js·安全性测试
tommyjiatong14 小时前
资安底座连环雷:从 PHP2Shell 到 Node2Shell
node.js·php
SEO_juper17 小时前
Bash 交叉验证 robots.txt 和 sitemap:两个文件自相矛盾,页面永远收录不了
开发语言·python·node.js·json·bash
SEO_juper18 小时前
Go 站点地图生成器:从首页爬全站,一键生成 sitemap.xml
xml·开发语言·人工智能·golang·node.js·seo·外贸独立站
Moshow郑锴1 天前
Ansible 批量部署 Node.js 服务:ZIP 直解与共享目录分发两种方案对比
node.js·ansible
少年的范儿2 天前
Windows 安装 Node.js 教程:nvm 版本管理 + pnpm 安装配置
windows·npm·node.js
peach2 天前
同样是装依赖,为什么 npm 长成 node_modules,Maven 长成 .m2
npm·node.js·maven
FungLeo2 天前
成为全栈·Go 后端篇·重写之前先锁基线:契约、Node 版本与行为清单
golang·node.js·go·openapi·契约测试·成为全栈
FungLeo2 天前
成为全栈·Go 后端篇·契约一致性与差分测试:两套实现如何互相校验
golang·node.js·go·合约测试·差分测试·成为全栈·api兼容
月夏3 天前
前端第一次接自己的后端:mock 切真接口踩了 2 个坑,组件只改了一行 import
前端·后端·node.js