likes
comments
collection
share

Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

作者站长头像
站长
· 阅读数 40

前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的脚手架,适合快速创建模板项目

1、提取命令后续参数

在上一章节中,我们创建了 test 的全局命令,这个时候我们需要通过提取后续参数,来为创建的新文件夹进行命名或者直接生成到打开的文件夹中

比如:

  • 调用命令 test abc,那么会在本地生成一个名为 aa 的文件夹,并且将模板 copy 进去
  • 调用命令 test .,那么不会生成新的文件夹,而是把模板文件直接生成到打开的文件夹中

接下去继续我们的代码编写:

  1. src 文件夹中,创建一个 util.ts 文件,这个文件来放置我们封装好的常用函数

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

  2. 创建一个获取参数的函数,通过 process.argv 来获取用户输入的参数

 export const getArgs = () => process.argv.slice(2)
  1. 如果你的 vscode 正常提示了,无需惊慌,只需要下载个 node 的类型依赖即可,TS会自动检查

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

npm i --save-dev @types/node
  1. index.ts 中进行引入并使用,并将值存储到 process.env 对象中,方便我们去全局使用
import { getArgs } from "./util.js"

// 1) 获取参数
const ARGUMENTS = getArgs()
process.env.__CREATE_NAME__ = ARGUMENTS[0];

console.log(process.env.__CREATE_NAME__);
  1. 通过命令打开监听,并开始测试,可以看到正常输出 test 即完成
npm run watch
test abc
  • Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的 Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

2、模板文件夹的自动读取和修改

这一节的目的是为了帮助我们将我们的模板文件夹中的文件名进行自动读取和转化成 json 格式吗,方便我们在后续提问的时候,可以展示给用户去确定选用哪个模板。

并且自动化整理的格式为:a-p1,a-p2,b-p1,b-p2 这样可以展示给用户为选择 a | b,再选择 a-p1、a-p2 | b-p2、b-p2

这样做有个非常大的优点不再需要每次添加新的模板时,去改动我们的代码,非常实用和方便

  1. 在根目录创建一个 template 文件夹,用于放置我们需要生成的初始模板

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

  2. 创建初始化模板文件夹:vue2-a,vue2-b,vue3-a,vue3-b

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

  3. src 文件夹中,创建一个 contain.ts 文件,这个文件来放置我们跟执行函数比较密切相关的函数

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

  4. 这个时候在 contain.ts 创建我们的第一个相关函数:getPathFromDir,这个函数的作用用于从根目录提取对应路径

import { resolve, dirname, join } from "node:path"
import { fileURLToPath } from "node:url";

// 1 从根目录合并目录,获取路径
export function getPathFromRoot(path: string): string {
  const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "../")
  return join(rootDir, path)
}
  1. 第二步我们需要将读取到的模板文件夹生成 json 格式并返回,方便我们后期去使用,再创建一个 DirToJson 的函数:
// 2 读取文件夹转化为 json 格式并返回
export const dirToJson = (dirPath: string, exclude?: string[]) => {
  const obj: Record<string, string[]> = {}
  for (const file of fs.readdirSync(dirPath, { withFileTypes: true })) {
    if (exclude?.includes(file.name)) continue;
    if (file.isDirectory()) {
      const NAME = file.name
      // 1 获取 - 前面的字段
      const prop = NAME.match(/(\w+)-/)
      // 2 如果 prop 存在
      if (prop) {
        obj[prop[1]] ? obj[prop[1]].push(NAME) : (obj[prop[1]] = [NAME])
      } else {
        obj[NAME] ? obj[NAME].push(NAME) : (obj[NAME] = [NAME])
      }
    }
  }
  return obj
}

可以看一下演示效果:

Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

通过这两个函数,就可以提取模板选项的 json 对象了!

3、创建通用模板文件夹

template 文件夹中创建一个 ``文件夹,这一个文件主要存储所有生成模板共用的一些文件,如 .gitignore.editconfig 等等

这也是为什么第二个章节创建的函数 dirToJson 要预留第二个参数的问题,用于隔离 common 文件夹

Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

4、开始提问

第一步获取参数完成后,我们就需要向用户进行提问,获取用户的需求来进行对应的生成,这个时候需要用到一个库 inquirer,来进行交互式命令生成

  1. src 文件夹中,创建一个 create-inquir.ts 文件,这个文件来放置我们封装好的提问函数

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

  2. 安装需要使用到的依赖库

npm i inquirer@9
npm i -D @types/inquirer@9
  1. 这一步我们需要创建问题集合

    • 第一个问题项目名,如果用户有输入,则使用用户输入的名称,否则为默认的 project 名称
    • 第二个问题则是询问使用的框架,将我们第二个章节创建的函数去进行读取生成对应选项,提供给用户选择
    • 第三个问题是选择好框架后,让用户选择需要使用的模板,如选了 vue2 ,则提供 vu2-a | vue2-b 给用户继续选

import inquirer, { type DistinctQuestion } from "inquirer"
import { getPathFromDir } from "./contain"
import { DirToJson } from "./util"

// 1) 创建问题集合
function createQuestions() {
  const QUESTIONS: Array<DistinctQuestion> = []
  const TEMPLATE = DirToJson(getPathFromDir("template"), ['common'])
  const FRAMEWORK = Object.keys(TEMPLATE)
  const PROJECT_NAME = process.env.__CREATE_NAME__ === "undefined" ? "project" : process.env.__CREATE_NAME__

  // 1 项目名
  QUESTIONS.push({
    message: 'projectName',
    name: "projectName",
    default: PROJECT_NAME
  })
  // 2 对应框架
  QUESTIONS.push({
    message: 'Select a framework',
    name: "framework",
    type: 'rawlist',
    choices: FRAMEWORK,
  })
  // 3 可选模板
  QUESTIONS.push({
    message: 'Select a variant',
    name: "variant",
    type: 'rawlist',
    choices: (res: any) => (TEMPLATE[res.framework])
  })
  return QUESTIONS
}
  1. 在文件中导出一个默认的执行函数,返回一个异步的 promise 对象,当问题全部完成后会将答案成功返回
// 2) 执行函数
export default function startQA() {
  return new Promise((resolve, reject) => {
    const questions = createQuestions()
    inquirer.prompt(questions).then(res => {
      resolve(res)
    })
  })
}
  1. src/index.ts 文件中引入并使用
import { getArgs } from "./util.js"
import startQA from "./create-inquir.js"

// 1) 获取参数
const ARGUMENTS = getArgs()
process.env.__CREATE_NAME__ = ARGUMENTS[0];

// 2) 初始化项目
async function init() {
  try {
    const ANSWERS = await startQA()
    console.log(ANSWERS);
  } catch (error) {
  }
}
init()
  1. 测试看看,这个时候你应该可以看到提问了:

    Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的

5、总结

这一篇文章主要讲解了关于脚手架初始命令行的写法,重点有三个:

  1. 提取用户输入的参数
  2. 转化模板文件夹
  3. 收集用户选择的选项

至此,我们完成脚手架最重要的一步,收集用户的需求! 再下一大章节里面,我们会将选项使用起来,并且生成对应的文件夹,敬请期待!

大家如果有成功的结果或者有任何疑问的话,可以直接截图发到评论,作者看到会回复滴~

转载自:https://juejin.cn/post/7389278362240729125
评论
请登录