750 字
4 分钟
写一个cli脚手架
2022-01-14

1 新建项目目录#

Terminal window
mkdir my-cli
cd my-cli
npm init —y # 生成package.json 文件

2 新建 bin 目录 创建脚本 test.js#

#!/usr/bin/env node //指定一个解释器 告诉shell 用node解释器去执行脚本
console.log('test')

3 在package.json 配置bin 字段#

bin字段用来指定各个内部命令对应的可执行文件的位置。

test 有毒不生效 所以这里新建了一个test2.js 脚本(命令)

"bin": {
"test": "./bin/test.js", // 好像test 命令有毒 不打印 其他可以
"test2": "./bin/test2.js"
},

将包中的任何bin链接到{prefix}/bin/{name} npm config get prefix 查看前缀 test2 命令已被添加到环境变量中 npmlink

5 执行test2#

test2

6 引入commander 添加子命令#

test2.js 初始化项目

#!/usr/bin/env node
const program = require('commander')
// 以package.json 的version 作为test2 的version
program.version(require('../package.json').version)
program
.command('init <name>')
.description('init project ')
.action(require('../lib/init'))
// 解析主进程参a
program.parse(process.argv)

7 使用 figlet chalk 输出欢迎界面#

init.js
const { promisify } = require('util')
const figlet = promisify(require('figlet'))
const chalk = require('chalk') // chalk 5 是ESM chalk 4 是CSM
const log = content => console.log(chalk.green(content))
module.exports = async name => {
const data = await figlet('Welcome')
log(data)
log(`下载项目: ${name}`)
}

welcome

8 download-git-repo 从远程仓库拉取项目#

const {clone} = require('./download')
// init.s
log(`下载项目: ${name}`)
// 现在默认分支不是master了 所以要指定分支main
await clone('github:cxyxiaoyuyu/xinyi-ui#main',name)

使用 ora 在下载时显示loading

download.js
const { promisify } = require('util')
const ora = require('ora')
module.exports.clone = async function(repo,desc){
const download = promisify(require('download-git-repo'))
const process = ora(`下载..... ${repo}`)
process.start()
await download(repo,desc)
console.log(repo,desc)
process.succeed()
}

执行完成后项目会多了一个执行命令时输入的目录

9 执行命令 自动安装依赖#

使用 spawn 执行 命令

init.js
const {spawn} = require('child_process')
/**
spawn 开一个子进程 用来执行脚本(暂且这么了解吧)
_spawn 自己封装的函数 开一个子进程 让子进程的输出信息打印给主进程 并监听子进程结束
直接用spawn 命
*/
const _spawn = async (...args) => {
return new Promise((resolve)=>{
const proc = spawn(...args)
proc.stdout.pipe(process.stdout) // 为了打印出日志输出
proc.stderr.pipe(process.stderr)
proc.on('close',()=>{ // 知道子进程结束了
resolve()
})
})
}
log('安装依赖')
await _spawn('npm',['install'],{cwd: `./${name}`})
log(`安装完成`)

10 自动启动项目#

使用open 打开浏览器

init.js
log('启动')
// 直接写spawn 是在主进程执行 不会打印输出信息
// 如果需要打印信息 需要使用自己封装的_spawn 但是这个进程不会结束 所以下面的open 不会执行 所以open 要放在上面
// spawn('npm',['run','dev'],{cwd: `./${name}`})
open(`http://localhost:3001`)
_spawn('npm',['run','dev'],{cwd: `./${name}`})

11 实现约定路由功能#

根据views下的文件 自动生成路由 使用handlebars 模板引擎

const fs = require('fs')
const handlebars = require('handlebars')
const program = require('commander')
program
.command('create component <name>')
.description('init project ')
.action(refresh)
program.parse(process.argv)
// module.exports = async () => {
async function refresh (name){
console.log(name)
// 获取列表
const list = fs.readdirSync('./src/views')
.filter(v => v !== 'Home.vue')
.map(v => ({
name: v.replace('.vue','').toLowerCase(),
file: v
}))
console.log(list)
// 生成路由定义
compile({list},'./src/router/index.js','./template/router.js.hbs')
// 生成菜单
compile({list},'./src/App.vue','./template/App.vue.hbs')
/**
* 模板编译
* @param {*} meta
* @param {*} filePath
* @param {*} templatePath
*/
function compile(meta,filePath,templatePath){
if(fs.existsSync(templatePath)){
const content = fs.readFileSync(templatePath).toString()
const result = handlebars.compile(content)(meta)
fs.writeFileSync(filePath,content)
console.log(`${filePath}创建成功`)
}
}
}
写一个cli脚手架
https://blog.cxyxiaoyu.top/posts/写一个cli脚手架/
作者
小宇
发布于
2022-01-14
许可协议
CC BY-NC-SA 4.0