背景
做新的项目时,每次复制文件夹,然后修改package.json、README.md等,感觉很不“优雅”,想使用类似vue-cli
,使用vue init
的方式在github下载我自己的前端工程,这样显得很“优雅”。
初始化项目结构
首先你已经有了自己搭建的前端工程,也就是模板,假设起名为x-liu,并且已经上传到github。
此时新建一个新的项目,起名为x-liu-cli
,我是参考vue的做法,这样即使x-liu
更新,x-liu-cli
不更新,也可以拉取到最新的x-liu
。
mkdir x-build-cli
cd x-build-cli
npm init
复制代码
创建名为x-liu-cli
的文件夹,使用npm初始化,在文件夹内创建bin
目录,并创建x-liu-cli.js
,此时的项目结构:
x-build-cli
|- bin
| |- x-build.js
|- package.json
复制代码
配置package.json
"bin": { "x-liu": "./bin/x-liu-cli.js" }
复制代码
在package.json增加"bin","x-liu"就是命令号要输入的指令,"./bin/x-liu-cli.js"是命令执行时的文件。 而发布到npm后是执行npm install x-liu-cli -g
配置x-liu-cli.js
#! /usr/bin/env node const program = require('commander');
const download = require('download-git-repo');
const chalk = require('chalk');
const ora = require('ora');
const inquirer = require('inquirer')
const fs= require('fs-extra')
复制代码
#! /usr/bin/env node
是指定这个文件使用node执行。
需要安装的模块npm i commander download-git-repo chalk ora --save:
commander
可以解析用户输入的命令。
download-git-repo
拉取github上的文件。// 不支持 Promise 可以借助util.promisify
chalk
改变输出文字的颜色
ora
小图标(loading、succeed、warn等)
fs
fs.existsSync(targetAir) 目录是否已经存在
inquirer
询问用户并让用户做出选择
完整的x-liu-cli
#! /usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const figlet =require('figlet')
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, options) => {
console.log(value, options)
})
// 配置 ui 命令
program
.command('ui')
.description('start add open roc-cli ui')
.option('-p, --port <port>', 'Port used for the UI Server')
.action((option) => {
console.log(option)
})
// 定义命令和参数
program
.command('create <app-name>')
.description('create a new project')
// -f or --force 为强制创建,如果创建的目录存在则直接覆盖
.option('-f, --force', 'overwrite target directory if it exist')
.action((name, options) => {
// 打印执行结果
// console.log('name:', name, 'options:', options)
require('../lib/create')(name, options)
})
// 配置版本号信息
program
.version(`v${require('../package.json').version}`)
.usage('<command> [option]')
program
.on('--help', () => {
// 使用 figlet 绘制 Logo
console.log('\r\n' + figlet.textSync('x-build', {
font: 'Ghost',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true
}));
// 新增说明信息
console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
})
// 解析用户执行命令传入参数
program.parse(process.argv);
复制代码
lib目录
lib
|- cteate.js
|- Generator.js
|- http.js
复制代码
create.js 内容
主要是判断是否覆盖原来的文件
const path= require('path')
const fs= require('fs-extra')
const inquirer = require('inquirer')
const Generator = require('./Generator')
module.exports = async function (name, options) {
// 执行创建命令
// 当前命令行选择的目录
const cwd = process.cwd();
// 需要创建的目录地址
const targetAir = path.join(cwd, name)
// 目录是否已经存在?
if (fs.existsSync(targetAir)) {
// 是否为强制创建?
if (options.force) {
await fs.remove(targetAir)
} else {
// 询问用户是否确定要覆盖
let { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: 'Target directory already exists Pick an action:',
choices: [
{
name: 'Overwrite',
value: 'overwrite'
},{
name: 'Cancel',
value: false
}
]
}
])
if (!action) {
return;
} else if (action === 'overwrite') {
// 移除已存在的目录
console.log(`\r\nRemoving...`)
await fs.remove(targetAir)
}
}
}
// 创建项目
const generator = new Generator(name, targetAir);
// 开始创建项目
generator.create()
}
复制代码
generator
选择之前预定好的模板,并远程下载
const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const path = require('path')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise
const chalk = require('chalk')
async function wrapLoading(fn, message, ...args) {
// 使用 ora 初始化,传入提示信息 message
const spinner = ora(message);
// 开始加载动画
spinner.start();
try {
// 执行传入方法 fn
const result = await fn(...args);
// 状态为修改为成功
spinner.succeed();
return result
} catch (error) {
// 状态为修改为失败
spinner.fail('Request failed, refetch ...')
}
}
class Generator {
constructor(name, targetDir) {
this.name = name
this.targetDir = targetDir
// 改造 download-git-repo 支持 promise
this.downloadGitRepo = util.promisify(downloadGitRepo);
// this.downloadGitRepo = downloadGitRepo;
}
// 请求接口拿到自己定义的模板
// 从远程拉取模板数据
// 用户选择自己想下载的模板名称
// return 用户选择的名称
async getRepo() {
let repoList = await wrapLoading(getRepoList, 'waiting fetch template')
if (!repoList) return;
// 过滤我们需要的模板名称filter
const repos = repoList.filter(i => i.is_template).map(item => item.name);
// 2)用户选择自己新下载的模板名称
const { repo } = await inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'Please choose a template to create project'
})
// 3)return 用户选择的名称
return repo;
}
// 下载远程模板
// 1)拼接下载地址
// 2)调用下载方法
async download(repo, tag) {
// 1)拼接下载地址
// const requestUrl = `zhurong-cli/${repo}${tag?'#'+tag:''}`;
const requestUrl = `json1204/${repo}`;
// 2)调用下载方法
await wrapLoading(
this.downloadGitRepo, // 远程下载方法
'waiting download template', // 加载提示信息
requestUrl, // 参数1: 下载地址
path.resolve(process.cwd(), this.targetDir)) // 参数2: 创建位置
}
async create() {
// 1)获取模板名称
const repo = await this.getRepo()
// 2) 获取 tag 名称
// const tag = await this.getTag(repo)
// 3)下载模板到模板目录
// await this.download(repo, tag)
await this.download(repo)
console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
console.log(`\r\n cd ${chalk.cyan(this.name)}`)
console.log(' npm run dev\r\n')
}
}
module.exports = Generator
复制代码
http
axios 请求github 的api拿到远程的模板 github api文档
// 通过 axios 处理请求
const axios = require('axios')
axios.interceptors.response.use(res => {
return res.data;
})
async function getRepoList() {
return axios.get(`https://api.github.com/users/json1204/repos`)
}
/**
* 获取版本信息
* @param {string} repo 模板名称
* @returns Promise
*/
async function getTagList(repo) {
return axios.get(`https://api.github.com/repos/json1204/${repo}/tags`)
}
module.exports = {
getRepoList,
getTagList
}
复制代码
上传npm
没有账号的同学去npm注册一个账号。
// 登录账号
npm login
// 上传项目
npm publish
复制代码
复制代码
上传成功之后,通过npm install x-liu-cli -g安装到全局环境中。
输入 x-liu create my
使用x-liu init [项目名]
就可以从github拉取相应的文件。
到此脚手架就完成
结语
参考
搭建自己的脚手架—“优雅”生成前端工程 从 0 构建自己的脚手架/CLI知识体系(万字) 从 16 个方向逐步搭建基于 vue3 的前端架构