很多小伙伴一直很纠结什么是脚手架?其实核心功能就是创建项目初始文件,那么问题来了,市面上脚手架不够用?为什么还要自己写?
只要提到脚手架你就会想到,vue-cli,create-react-app,dva-cli,他们的特点不用多说那就是专一,但是在公司中你会发现以下一系列问题
- 业务类型多
- 多次造轮子,项目升级等问题
- 公司代码规范,无法统一
很多时候我们开发需要新建项目,把已有项目代码复制一次,保留基础能力(但是这个过程非常琐碎而又耗时),那我们可以自己定制化模版,自己实现一个属于自己的脚手架,来解决这个问题
1.必备模块
我们先从大家众所周知的vue-cli入手,先来看看他用了那些npm包来实现
- commander:参数解析,--help就是借助了他
- inquirer:交互式命令行工具,有他就可以实现
- download-git-repo 将模版下载下来
将包关联成全局
先看好目录结构
1.先创建可执行的脚本 #! /usr/bin/env node
创建bin文件夹,创建zhu文件
#! /usr/bin/env node
console.log('执行了')
2.配置package.json 中bin字段
{
"name": "zhu",
"version": "1.0.0",
"description": "",
"main": "index.js",
"bin": "./bin/zhu",
-----
3.npm link 链接到本地环境(默认以name为基准)
- link 相当于将当前本地模块链接到npm目录下,这个npm目录可以直接访问,所以当前包就可以直接访问了
npm link
最后执行可以看到
2.解析命令传入的参数
我们都知道vue可以执行 vue --help
,vue config
,vue create
等命令,那我们第一步就是来实现命令
1.先下载commander
npm install commander
2.利用commander中方法的来写一些命令,先实现版本的查看zhu -V
命令
program.version(`zhufeng-cli @${require('../package.json').version}`).usage(`<command> [option]`)
//解析命令传入的参数
program.parse(process.argv)
3.实现zhu create
命令
program
.command('create <app-name>') //命令
.description('config a new project') //描述
.option('-f, --force', 'overwrite target if it exists') //是否强制覆盖
.action((name, cmd) => {
//调用create模块去实现
require('../lib/create')(name, cmd) //去调用创建方法
// console.log(name, cmd) //需要提取这个cmd的属性
})
4.实现zhu config
命令
program
.command('config [value]')
.description('inspect and modify ')
.option('-g --get <path>', 'get value from option')
.option('-s --set <path> <value>')
.option('-d --delete <path>')
.action((value, cmd) => {
//调用config模块
console.log(value, cmd)
})
5.实现zhu ui
命令
program
.command('ui')
.description('start and open zhufeng-cli')
.option('-p --port <port>', 'Port use for UI Server')
.action((value, cmd) => {
console.log(value, cmd)
})
6.我们这个时候可以通过 zhu --help
来查看命令
执行了 //这是我们的测试代码
Usage: zhu <command> [option] //这是用来描述命令的参数
Options:
-V, --version output the version number
-h, --help display help for command
Commands: //这是我们自己写的命令
create [options] <app-name> config a new project
config [options] [value] inspect and modify
ui [options] start and open zhufeng-cli
help [command] display help for command
Run zhufeng-cli
3.创建项目
先判断当前目录下面有重复的项目没有,有重名的项目就先删掉后,再开始创建
- crete.js
const path = require('path')
const fs = require('fs-extra')
const Inquirer = require('inquirer')
let Creator = require('./Creator')
module.exports = async function (projectName, options) {
console.log(projectName, options)
const cwd = process.cwd() //获取当前命令执行的工作目录
const targetDir = path.join(cwd, projectName)
if (fs.existsSync(targetDir)) {
if (options.force) { //如果强制创建,则删除
//强制创建
await fs.remove(targetDir)
} else {
//提示用户是否确定覆盖
const { 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('Removeing')
await fs.remove(targetDir)
}
}
}
//创建项目
const creator = new Creator(projectName, targetDir)
creator.create()
}
我们脚手架的创建分成3部 1.先去拉取当前组织下面的模版 2.再通过模版找到版本号 3.最后下载模版 那么我们的思路就很清楚了 先下载对应的包
npm install download-git-repo ora axios
Creator.js
module.exports = class Creator {
constructor(projectName, targetDir) {
this.name = projectName
this.target = targetDir
//通过util.promisify转换成为promise请求
this.downloadGitRepo = util.promisify(downloadGitRepo)
}
//真实开始创建
async create() {
// 1 先去拉取当前组织下面的模版
let repo = await this.fetchRepo()
console.log('repo', repo)
//2 再通过模版找到版本号
let tag = await this.fetchTag(repo)
//3.下载
let downloadUrl = await this.download(repo, tag)
}
async fetchRepo() {
}
async fetchTag(repo) {
}
async download(repo, tag) {
}
}
wrapLoading 制作一个等待loading
async function sleep(n) {
return new Promise(resolve => {
setTimeout(resolve, n)
})
}
async function wrapLoading(fn, message, ...args) {
const spinner = ora(message)
spinner.start()
try {
const res = await fn()
spinner.succeed()
return res
} catch (error) {
spinner.fail('Request failed, retrying')
await sleep(1000)
return wrapLoading(fn, message, ...args)
}
}
request.js 拉取的地址
//通过axios来获取结果
const axios = require('axios')
axios.interceptors.response.use(res => {
console.log(res)
return res.data
})
async function fetchRepoList() {
//
return axios.get('https://api.github.com/orgs/zhu-cli/repos')
}
async function fetchTagList(repo) {
return axios.get(`https://api.github.com/repos/zhu-cli/${repo}/tags`)
}
module.exports = {
fetchRepoList,
fetchTagList
}
fetchRepo拉取当前组织下面的模版
async fetchRepo() {
//失败重新获取
let repos = await wrapLoading(fetchRepoList, 'wating fetch template')
if (!repos) {
return
}
repos = repos.map(item => item.name)
let { repo } = await Inquirer.prompt({ //找到对应的模版下载
name: 'repo',
type: 'list',
message: '请选择要下载的模版',
choices: repos
})
console.log('repo', repo)
return repo
}
fetchTag 通过模版找到对应的版本
async fetchTag(repo) {
let tags = await wrapLoading(fetchTagList, 'wating fetch tag', repo)
if (!tags) return
tags = tags.map(item => item.name)
let { tag } = await Inquirer.prompt({
type: 'tag',
name: 'list',
message: '请选择要下载的版本',
choices: tags
})
return tag
}
download下载模版
async download(repo, tag) {
//1.需要拼接处一个下载路径
let requestUrl = `zhu-cli/${repo}${tag ? '#' + tag : ''}`
//2.把资源下载到某个路径下(后续可以增加缓存功能,应该下载到系统目录中,稍后可以在ejs handlerbar 去渲染模版 最后生成结果,再输出)
//请求路径和目标目镜
this.downloadGitRepo(requestUrl, path.resolve(process.cwd(), `${repo}@${tag}`))
return this.target
}