前言
创建一个脚手架,本质上就是实现一个全局包,只不过其职能是根据配置项创建项目
实现思路
- 配置可执行命令 可以基于
commander
- 要实现脚手架 先做一个命令行交互的功能 可以基于
inquirer
- 项目模板下载 可以基于
download-git-repo
- 根据用户的选择动态的生成内容 可以基于
metalsmith
具体实现
首先构建npm包的基本配置
创建可执行的脚本 并指明运行环境是node
/bin/start.js
#! /usr/bin/env node
将项目执行脚本指向创建的脚本
配置package.json
中的bin字段
将包链接到全局下,以直接用命令方式执行
npm link
此时 这个包就变成了全局包,执行命令为name
属性对应的值,执行时会默认执行指定的脚本文件
继而配置可执行命令
安装依赖
npm i command
在执行脚本start.js中实现逻辑
// 1、配置可执行命令 commander
const program = require('commander');
// 配置文件相关
program
.command('config [value]')
.description('inspect and modify the config')
.option('-g, --get <path>','get value from option')
.option('-s, --set <path> <value>')
.option('-d, --delete <path>','delete option from config')
.action((value,opts)=>{
console.log(opts);
// console.log(value,cleanArgs(cmd));
})
// 创建ui
program
.command('ui')
.description('start and open wzy-cli ui')
.option('-p,--port <port>','Port used for the UI Server')
.action(cmd => {
console.log(cmd);
})
// 查看版本 `wzy-cli --version`
program
.version(`zhufeng-cli ${require('../package.json').version}`)
.usage(`<command> [option]`)
// 当用户输入`wzy-cli --help`时 执行回调 提示用户可以查看具体命令详情
program.on('--help',function (){
console.log();
console.log(`Run ${chalk.cyan('wzy-cli <command> --help ')} show details>`);
console.log();
})
// 解析命令执行的参数
program.parse(process.argv);
中间如果存在交互式配置,比如新建项目时目标文件路径已经存在,则需问询用户是否进行覆盖,则可以使用inquier
npm i inquier
start.js中逻辑
const path = require('path');
const fs = require('fs-extra');
const Inquirer = require('inquirer');
// 需要支持强制创建功能 创建项目
program
.command('create <app-name>')
.description('create a new project')
.option('-f, --force','overwrite target directory if it exists')
.action((name,opts) => {
const cwd = process.cwd(); // 获取当前命令执行时的工作目录
const targetDir = path.join(cwd,projectName); // 目标目录
async function overWrite() {
// 先移除
return await fs.remove(targetDir)
}
// 首先判断项目是否已经存在
if(fs.existsSync(targetDir)){
// 继而判断是否是强制覆盖模式
if(opts.force){
await overWrite()
}else {
// 提示用户是否确定覆盖
let {action} = await Inquirer.prompt([
{
name: 'action',
type: 'list', // 还有很多其他类型
message: 'Target directory already exited Pick an action',
choices: [
{
name: 'Overwrite', value: 'overwrite'
},
{name: 'cancel', value: false}
]
}
]);
console.log(action);
if(!action){
return
}else if(action === 'overwrite') {
await overWrite()
}
}
}
})
功能完善:选择指定模板(仓库和tag)
架子已经搭建起来了,其实我们这时候就是在不同的命令下去执行不同的逻辑处理,重点关注实现
- 创建项目以及根据用户的选择动态的生成项目的内容
承接上文,我们很自然能想到,目前只需要实现下载模板的逻辑就可以了,我们可以抽离出来一个创建类
,用于下载模板项目,大体思路如下:
// 1. 先拉取当前组织下的模板
let repo = await this.fetchRepo();
// 2. 在通过模板找到版本号
let tag = await this.fetchTag();
// 3. 下载
let downloadUrl = await this.download(repo,tag);
// 4. 编译模板
相应的,我们只需要找到模板项目存储的平台对外暴露的API进行下载即可,中间需要做两步
- 实现选择功能,下载模板列表后,让用户自行选择下载哪个模板
- 实现失败重发功能,下载时可能遇到网络等不可控影响导致下载失败,此时要添加loading态并重新发起下载请求
第一点,我们依然可以使用inquirer
;
第二点,我们可以使用一个第三方库ora
,其作用是添加loading态,重传的实现我们可以进行tryCatch
检测。
Show me Code
Creator.js
const inquirer = require("inquirer");
const { fetchRepoList } = require("./request");
const { wrapLoading } = require("./util");
class Creator {
constructor (projectName,target){
this.name = projectName;
this.target = target;
}
async fetchRepo(){
// 新增loading 以及 失败重新获取
let repos = await wrapLoading(fetchRepoList,'waiting fetch template');
// let repos = await fetchRepoList();
// // console.log(repos,'仓库列表');
if(!repos) return;
repos = repos.map(item => item.name);
let { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'please choose a template to create a project'
})
// // console.log(repo);
}
async fetchTag(){
}
async download(){
}
async create(){
// 1. 先拉取当前组织下的模板
let repo = await this.fetchRepo();
// 2. 在通过模板找到版本号
let tag = await this.fetchTag();
// 3. 下载
let downloadUrl = await this.download(repo,tag);
// 4. 编译模板
}
}
module.exports = Creator;
util.js
const ora = require('ora')
// 挂起函数
export async function sleep(n) {
return new Promise((resolve,reject) => {
setTimeout(resolve,n)
})
}
// loading + 失败重新发起fetch
export async function wrapLoading(fn,message) {
const spinner = ora(message);
spinner.start(); // 开始加载
try {
let repos = await fn();
spinner.succeed(); // 成功
return repos;
} catch (error) {
spinner.fail('request failed, refetch...')
await sleep(1000);
console.log(error);
return wrapLoading(fn,message);
}
}
功能完善:下载选择好的模板至指定位置
此处从github上下载仓库我们可以借助一个第三方包download-git-repo
npm i download-git-repo
然后在下载函数中执行如下逻辑
- 拼接处下载路径
- 将资源下载到指定的位置 (后续亦可增加缓存功能)
Show me Code
Creator.js
// 根据仓库和tag下载指定模板
async download(repo,tag){
// 1. 拼接处下载路径
let requestUrl = `zhu-cli/${repo}${tag ? '#' + tag : ''}`;
// 2. 将资源下载到指定的位置 (后续可以增加缓存功能 w-todo)
await wrapLoading(this.downloadGitRepo,'waiting down repo', requestUrl,path.resolve(process.cwd(),`${repo}@${tag}`));
return this.target;
}
总结
至此,我们就完成了一个简易版的cli
工具,可以看到,其实也就是包的使用+思路的理清,完整版代码的仓库地址,如果对您有任何帮助吧,赏小菜个star吧,谢谢阅读
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!