为什么要搭建脚手架
脚手架就是在项目启动的时候询问一些简单的问题,并且通过用户回答的结果去渲染对应的模板文件。
搭建脚手架的目的就是快速搭建项目的基本结构并提供项目规范和约定。
目前日常工作中常用的脚手架有 vue-cli、create-react-app、angular-cli
等等,都是通过简单的初始化命令,完成内容的快速构建。
脚手架是我们经常使用的工具,也是团队提效的重要手段,所以系统性的掌握脚手架相关知识,对前端开发者来说是非常重要的。
站在学习的角度,我们创建项目如果只是使用脚手架,我们永远不知道如何搭建一个项目。
👌那这篇文章将手把手教你撸一个属于自己的脚手架!
搭建流程梳理
我们先来看一下脚手架的基本工作流程:
- 通过命令行交互询问用户问题
- 根据用户回答的结果生成文件
下面呢是我们这次手撸脚手架的步骤:
-
创建自定义全局命令 npm link
-
命令参数接受处理 commander
-
终端交互 inquirer
-
下载远程代码 download-git-repo
-
下载等待提示 ora
-
命令行字体美化 chalk
OK👌,接下来话不多说,直接开撸!
初始化脚手架
我们先来初始化脚手架项目:
mkdir mycli # 新建脚手架项目目录
cd mycli
mkdir bin # 创建 bin 目录
cd bin # 进入 bin 目录
touch cli.js # 新建一个程序入口文件 cli.js 文件
cd ..
npm init # 生成 package.json 文件
此时项目目录结构:
mycli
├─ bin
└─ cli.js
└─ package.json
我们打开 cli.js
文件进行编辑:
#! /usr/bin/env node
// #! 用于指定脚本的解释程序
// Node CLI 应用入口文件必须要有这样的文件头
// 用于检查入口文件是否正常执行
console.log('mycli')
然后我们需要用 npm link
来把这个脚本链接到全局:
sudo npm link
然后我们在命令行输入 mycli
,命令行会输入 cli.js
里log
的内容
命令参数处理
我们用过很多知名的脚手架,一个成熟的脚手架一定有非常完善的命令参数处理机制。
例如我在这里使用docker --help
,控制台会打印:
接下来我们也来让我们的脚手架实现这样的参数处理功能。
process.argv
属性返回一个数组,这个数组包含了启动 Node.js 进程时的命令行参数
我们在cli.js
里打印一下process.argv[2]
会得到我们输入的命令行参数
如果我们输入的参数是--help
,就打印获取到了help命令参数
// cli.js
if(process.argv[2] == '--help') {
console.log('获取到了help命令参数');
}
如果这里通过原生 Node 提供的这个属性去操作命令行中的参数,那肯定是非常的麻烦的,不可能每一个命令我们都自己去实现
所以我们使用第三方提供包,也就是 commander
首先我们npm install commander
来安装commander
,然后我们在cli.js
引入:
#! /usr/bin/env node
const { program } = require('commander')
console.log(process.argv[2]);
if(process.argv[2] == '--help') {
console.log('获取到了help命令参数');
}
program.option('-f --framework <framework>','设置框架')
program.parse(process.argv); // parse() 用于解析命名行参数
我们来优化完善一下配置,增加一个Commands
配置用于 create project
创建项目:
program
.command('create <project> [other...]') // 定义命令
.alias('crt') // 定义别名
.description('创建项目') // 描述
.action((project,args) => { // 定义命令处理方法
// 命令行的执行逻辑代码
// console.log(project);
// console.log(args);
})
program.parse(process.argv);
询问用户信息
处理好了命令参数,接下来我们来实现脚手架的询问用户信息的功能
我们这里需要用到inquirer
,先来安装一下 npm install inquirer
上面我们写的program.action
里面就是命令处理的方法,我们来使用inquirer.prompt
来与用户进行信息交互:
program
.command('create <project> [other...]') // 定义命令
.alias('crt') // 定义别名
.description('创建项目') // 描述
.action((project,args) => { // 定义命令处理方法
// 命令行的执行逻辑代码
inquirer.prompt([
{
type: 'input', //type: input, number, list, checkbox ...
name: 'username', // key 名
message: '请输入你的名字', // 提示信息
// default: 'defaultName' // 默认值
},
{
type: 'list',
name: 'framework',
choices: ['express', 'koa', 'egg'],
message: '请选择你所使用的框架',
}
])
.then(answer => {
// 打印用户输入结果
console.log(answer); // {username:'ruimengmeng',framework:'express'}
})
})
program.parse(process.argv);
下载模版代码
当用户选择完所要用的框架之后,我们就需要从远程仓库下载对应的框架模版代码
download-git-repo
是一个用于下载 GitHub、Gitlab 上的公共仓库的库
我们这里用download-git-repo
来实现下载功能,安装npm install download-git-repo
,使用方法如下:
// 下载代码模版
download(
'direct:git@gitee.com:xxx/express-template.git', // 远程仓库的地址
project, // './xxx', 下载到本地的路径
{ clone: true }, // 配置参数
(err) => { // 回调函数
console.log(err);
}
)
好我们来完成下载功能:
program
.command('create <project> [other...]') // 定义命令
.alias('crt') // 定义别名
.description('创建项目') // 描述
.action((project,args) => { // 定义命令处理方法
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([
{
type: 'input',
name: 'username',
message: '请输入你的名字',
},
{
type: 'list',
name: 'framework',
// choices: ['express', 'koa', 'egg'],
choices: config.framework,
message: '请选择你所使用的框架',
}
])
// .then(answer => {
// console.log(answer);
// })
// 下载代码模版
downloadFunction(config.frameworkUrl[answer.framework], project);
})
const downloadFunction = function (url, project) {
download(
"direct:" + url,
project,
{ clone: true },
(err) => {
console.log(err);
}
);
};
program.parse(process.argv);
为了代码的可读性和拓展性,我们将选择的框架和框架对应的下载地址放在config.js
里:
module.exports = {
// 可选择的框架
framework: ['express', 'koa', 'egg'],
// 框架对应的下载地址
frameworkUrl: {
express: 'https://gitee.com/xxx/express-template.git',
koa: 'https://gitee.com/xxx/koa-template.git',
egg: 'https://gitee.com/xxx/egg-template.git',
}
}
我们来试一下我们写的下载功能有没有生效:
可以看到,选择完框架之后,过了一会框架模版代码成功下载到了bin目录
到目前为止,我们的脚手架基本功能已经实现
但是这个过程非常不友好,没有任何的交互体验可言,就是单纯的用户选择完然后下载,没有任何的下载过程和完成提示
要是网速慢一点,用户简直一脸懵逼,不能忍!我们来优化一下!
等待提示交互
ora
是一个实现命令行 loading 效果的库
安装npm install ora
,使用方法如下:
const ora = require('ora')
const spinner = ora().start() // 定义一个loading并启动
spinner.text = 'Loading...'
使用方法非常简单,接下来我们来添加到我们的脚手架里面:
const downloadFunction = function (url, project) {
const spinner = ora().start();
spinner.text = '代码正在下载...';
// 下载代码模版
download(
"direct:" + url,
project, // './xxx',
{ clone: true },
(err) => {
// console.log(err);
if(!err) {
spinner.succeed('代码下载成功!🐮');
console.log('项目已经准备就绪! 😄 你可以运行:');
console.log('👀 cd ' + project);
console.log('🌟 npm install ');
console.log('🔥 npm run dev');
} else {
spinner.fail('代码下载失败!🥺')
}
}
);
};
脚手架命令行美化
chalk
是一个命令行美化工具,能够输出五彩斑斓的命令
// npm install chalk
const chalk = require('chalk');
const downloadFunction = function (url, project) {
const spinner = ora().start();
spinner.text = '代码正在下载...';
// 下载代码模版
download(
"direct:" + url,
project,
{ clone: true },
(err) => {
if(!err) {
spinner.succeed(chalk.green.bold('代码下载成功!🐮'));
console.log(chalk.blue.bold('项目已经准备就绪! 😄 你可以运行:'));
console.log(chalk.yellow.bold('👀 cd ' + project));
console.log(chalk.yellow.bold('🌟 npm install '));
console.log(chalk.yellow.bold('🔥 npm run dev'));
} else {
spinner.fail('代码下载失败!🥺')
}
}
);
};
到此为止呢,我们的脚手架就算基本完成了
完整代码戳👇此处:https://github.com/KongC-X/mycli
接下来我们就可以将我们的脚手架发布到 npm 上
发布脚手架到npm
第一步:获取 npm 配置
npm config list -l --json
-l
列表表示所有默认配置选项
--json
以 json 格式显示配置选项
这一步是为了确认"registry": "https://registry.npmjs.org/"
无误
第二步:登录 npm 账号 npm login
第三步:修改 npm 包版本
更新补丁版本号:npm version patch 0.0.1
更新次要版本号:npm version minor 0.1.1
更新主要版本号:npm version major 1.1.1
第四步:正式发布 sudo npm publish
发布成功!我们可以在npm官网上找到我们刚刚发布的 npm 包
我们可以来验证一下:
安装:npm install -g kongccli
测试:kongccli --help
创建项目:kongccli create test-project
完美!下班!