Node 的命令交互式写法,与用户对话(2)前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的
前言:听说不会做脚手架的前端不是好前端,本系列带你从0到1创建一个属于个人的脚手架,适合快速创建模板项目
1、提取命令后续参数
在上一章节中,我们创建了 test
的全局命令,这个时候我们需要通过提取后续参数,来为创建的新文件夹进行命名或者直接生成到打开的文件夹中
比如:
- 调用命令
test abc
,那么会在本地生成一个名为aa
的文件夹,并且将模板 copy 进去 - 调用命令
test .
,那么不会生成新的文件夹,而是把模板文件直接生成到打开的文件夹中
接下去继续我们的代码编写:
-
在
src
文件夹中,创建一个util.ts
文件,这个文件来放置我们封装好的常用函数 -
创建一个获取参数的函数,通过
process.argv
来获取用户输入的参数
export const getArgs = () => process.argv.slice(2)
-
如果你的 vscode 正常提示了,无需惊慌,只需要下载个 node 的类型依赖即可,TS会自动检查
npm i --save-dev @types/node
- 在
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__);
- 通过命令打开监听,并开始测试,可以看到正常输出
test
即完成
npm run watch
test abc
2、模板文件夹的自动读取和修改
这一节的目的是为了帮助我们将我们的模板文件夹中的文件名进行自动读取和转化成 json 格式吗,方便我们在后续提问的时候,可以展示给用户去确定选用哪个模板。
并且自动化整理的格式为:a-p1,a-p2,b-p1,b-p2 这样可以展示给用户为选择 a | b,再选择 a-p1、a-p2 | b-p2、b-p2
这样做有个非常大的优点:不再需要每次添加新的模板时,去改动我们的代码,非常实用和方便
-
在根目录创建一个
template
文件夹,用于放置我们需要生成的初始模板 -
创建初始化模板文件夹:vue2-a,vue2-b,vue3-a,vue3-b
-
在
src
文件夹中,创建一个contain.ts
文件,这个文件来放置我们跟执行函数比较密切相关的函数 -
这个时候在
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)
}
- 第二步我们需要将读取到的模板文件夹生成 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
}
可以看一下演示效果:
通过这两个函数,就可以提取模板选项的 json 对象了!
3、创建通用模板文件夹
在 template
文件夹中创建一个 ``文件夹,这一个文件主要存储所有生成模板共用的一些文件,如 .gitignore
、.editconfig
等等
这也是为什么第二个章节创建的函数 dirToJson
要预留第二个参数的问题,用于隔离 common
文件夹
4、开始提问
第一步获取参数完成后,我们就需要向用户进行提问,获取用户的需求来进行对应的生成,这个时候需要用到一个库 inquirer,来进行交互式命令生成
-
在
src
文件夹中,创建一个create-inquir.ts
文件,这个文件来放置我们封装好的提问函数 -
安装需要使用到的依赖库
npm i inquirer@9
npm i -D @types/inquirer@9
-
这一步我们需要创建问题集合
- 第一个问题项目名,如果用户有输入,则使用用户输入的名称,否则为默认的
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
}
- 在文件中导出一个默认的执行函数,返回一个异步的 promise 对象,当问题全部完成后会将答案成功返回
// 2) 执行函数
export default function startQA() {
return new Promise((resolve, reject) => {
const questions = createQuestions()
inquirer.prompt(questions).then(res => {
resolve(res)
})
})
}
- 在
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()
-
测试看看,这个时候你应该可以看到提问了:
5、总结
这一篇文章主要讲解了关于脚手架初始命令行的写法,重点有三个:
- 提取用户输入的参数
- 转化模板文件夹
- 收集用户选择的选项
至此,我们完成脚手架最重要的一步,收集用户的需求! 再下一大章节里面,我们会将选项使用起来,并且生成对应的文件夹,敬请期待!
大家如果有成功的结果或者有任何疑问的话,可以直接截图发到评论,作者看到会回复滴~
转载自:https://juejin.cn/post/7389278362240729125