问题描述
前端开发的过程中通常不可避免的会使用到脚手架来构建项目,其中最常见的就是vue-cli、create-react-app、angular-cli 等等,脚手架工具的使用可以大幅度的提升项目的构建速度,通过简单的命令行的交互,可以快速的集成依赖,完成项目结构的创建。
准备工作
在开始构建脚手架前首先需要知道脚手架具备的最基本的功能
- 可以通过命令行交互询问用户问题
- 根据用户回答的结果生成文件
比如说我们在使用vue-cli 创建项目的时候在终端键入命令👇
vue create my_project
这里会有询问用户问题的操作


选择了需要的依赖后就会去集成依赖生成相应的目录结构
创建自己的脚手架
知道了脚手架的运行流程后我们来根据流程一步一步来创建自己的脚手架,此次我们要做的只是利用脚手架去拉取固定的模板,自定义创建依赖模板的流程留到后面再讲。
1、首先创建一个文件夹生成package.json文件
mkdir yd_cli_vue
cd yd_cli_vue/
npm init
2、创建文件夹 “bin” 以及文件夹 “lib” ,“bin”文件夹用来存放程序的入口文件,“lib”文件夹用来存放一些工具函数等文件。文件夹结构如下 👇
yd_cli_vue
├─ bin
├─ lib
└─ package.json
3、在文件夹“bin”下创建程序的入口文件“cli.js”,文件目录结构如下👇
yd_cli_vue
├─ bin
│ ├─ cli.js
├─ lib
└─ package.json
4、在package.json里面指定程序的入口文件为bin下面的cli.js👇
{
"name": "yd_cli_vue",
"version": "1.0.0",
"description": "simple_cli",
"main": "index.js",
"bin": {
"yd": "./bin/cli.js" // 手动添加入口文件为 cli.js yd为程序的快速启动命令
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "youngdan",
"license": "ISC"
}
5、在入口文件cli.js中进行编辑。
#! /usr/bin/env node
console.log('hello cli')
在上面的代码中,我们可以看到第一行出现了比较奇怪的命令,那么这句命令是什么意思呢,首先我们先来认识一下 #!这个符号,这个符号在linux或者unix中叫做shebang,在unix类的操作系统里,文件开头如果带有 #!的话,那么这个文件就会被当做一个执行文件来执行,执行文件也即是可以由操作系统进行加载执行的文件。如果带有这个符号,说明这个文件可以当做脚本来运行。
那么后面的 /usr/bin/env node 是什么意思呢,他的意思是这个文件要用node来执行,但是node是怎么来的呢?所以会去用户的安装根目录下的env环境变量里面去寻找node(/usr/bin/env node)然后用node 来执行整个的脚本文件。
6、检查入口文件是否可以正常的执行,在终端键入以下命令
npm link// npm 链接到全局
当出现以下情况的时候说明执行成功了

此时我们输入程序的快速启动命令 yd (在package.json里面进行配置的),可以看到入口文件成功执行了

入口文件执行成功了,我们就可以正式开始脚手架的构建了
完成脚手架最基本的功能
首先在构建脚手架前,需要引入一些脚手架构建中必须用到的工具
commander
commander 可以自定义一些命令行指令,在输入自定义的命令行的时候,会去执行相应的操作
inquirer
inquirer 可以在命令行询问用户问题,并且可以记录用户回答选择的结果
fs-extra
fs-extra 是fs的一个扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持。
chalk
chalk 可以美化终端的输出
figlet
figlet 可以在终端输出logo
ora
ora 控制台的loading样式
download-git-repo
download-git-repo 下载远程模板
利用commander自定义命令行指令
在入口文件中引入下载的commander
const program = require('commander')
program
.version('0.1.0')
.command('create <project_name>')
.description('create a new project')
.action(res => {
// 打印命令行输入的值
console.log(res)
})
program.parse()
执行程序快捷运行命令 yd

可以看到出现了cli使用的命令信息,其中有一条命令是 create <project_name> 现在我们试着输入一下这个命令,可以看到出现了以下的信息,也就是说,程序拿到了我们输入的项目的名称,那么之后就可以用我们输入的名称作为我们要创建的项目的名字。

我们经常可以看到别人的npm包,输入了命令后终端上会出现彩色的字体,以及logo。是怎么做的呢,其实就是利用了chalk和figlet,示例代码👇🏻
program
// 监听 --help 执行
.on('--help', () => {
console.log('\r\n' + figlet.textSync('YangDan', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 100,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${
chalk.cyan(`yd <command> --help`)} for detailed usage of given command\r\n`)
})
我们再次输入脚手架快速启动命令可以看到出现了logo和彩色的字体

询问用户是否需要强行覆盖已有文件
首先我们在项目的根目录下找到lib文件夹,在lib文件夹的下面创建 create.js文件,这个文件的作用就是用来创建项目文件的,我们在create.js 文件下面键入以下代码👇🏻
// lib/create.js
module.exports = async function (name, options) {
console.log('项目名称以及配置项:', name, options)
}
然后在cli.js中引入 create.js 将 cli.js 中的部分代码修改为👇🏻
program
.command('create <project_name>')
.description('create a new project')
.option('-f, --force', 'overwrite target directory if it exist') // 文件夹存在的时候强制创建文件夹
.action((name, options) => {
// 在 create.js 中执行创建任务
require('../lib/create.js')(name, options)
})
接着我们来执行和一下我们的脚手架的命令,看一下create.js有没有成功的引入成功,以及在create.js里面能不能成功的拿到输入的项目的名称以及options。👇🏻

可以看到成功的打印出了create.js以及配置项,其中的配置项-f的意思是,当我创建一个文件夹的时候,这个文件夹的名字可能已经存在了,这个时候我们是否需要强行覆盖掉之前的文件夹,创建一个新的文件夹。
然后我们需要完善一下create.js的逻辑
// lib/create.js
const path = require('path')
const fs = require('fs-extra')
module.exports = async function (name, options) {
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetCwd = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetCwd)) {
// 是否为强制创建?
if (options.force) {
console.log('进入了强制创建')
} else {
console.log('需要询问用户是否强行创建')
// 询问用户是否需要强行创建文件夹
}
} else {
console.log('目录尚不存在,去创建');
}
}
接着我们执行一下这块的逻辑,分别看在存在目录,和不存在目录的情况下分别会输出什么
不存在的时候

当存在目录的时候,但是在创建的时候不输入 -f (强制覆盖)

当存在目录的时候,但是在创建的时候直接输入 -f (强制覆盖)

可以看到,确实是可以在我们输入不同的命令的情况下,根据有没有已经创建的目录,进入不同的逻辑里面。那么我们就可以分别根据三种不同的情况,来做不同的处理。
如果说想创建一些自定义的指令,根据自定义的指令来做不同的处理,那么也可以仿照强行覆盖的这个逻辑来进行自定义,此处就不再赘述。
询问用户拿到用户的创建信息
要做到终端和用户进行交互,并且拿到用户选择的信息,那么就需要前面介绍过的工具 inquirer 来帮助我们,inquirer 可以在命令行询问用户问题,并且可以记录用户回答选择的结果。便于我们拿到终端与用户的交互信息。
npm install inquirer
接下来我们就需要解决询问用户是否覆盖已经存在的文件夹,然后用户选择模板,选择版本,进行模板的下载
首先,如果目录已经存在,就看用户输入的指令里面存不存在 -f 或者 --force ,假如有的话,那么直接覆盖掉之前存在的目录,然后创建。假如没有,那么久需要询问用户是否需要强行覆盖掉原本存在的文件,用户选择是的情况下覆盖,用户选择否的情况下不覆盖。 如果用户不存在,创建的时候,直接走新建的逻辑。根据这个逻辑,我们可以完善create.js的逻辑如下。👇🏻
// lib/create.js
const path = require('path')
const fs = require('fs-extra')
module.exports = async function (name, options) {
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetCwd = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetCwd)) {
// 是否为强制创建?
if (options.force) {
console.log('进入了强制创建')
//移除掉原本存在的文件
await fs.remove(targetCwd)
} else {
console.log('需要询问用户是否强行创建')
// 询问用户是否需要强行创建文件夹
let {
action
} = await inquirer.prompt([{
name: 'action',
type: 'list',
message: 'Target directory already exists Pick an action:',
choices: [{
name: 'Over

4253

被折叠的 条评论
为什么被折叠?



