1.自定义cli优势
- 规范所有项目的文件夹,文件的命名规范
- 规范第三方库的版本
- 规范所有配置信息
- 规范内部git的提交标准与代码风格
- 规范所有自定义的工具,内置方法的版本
2.脚手架执行流程
- 通过提示输入 项目的关键信息
- 拉取下载项目模板
- 根据输入关键字替换配置文件与描述
- 自动执行install方法安装
- 自动打开浏览器
3. 程序入口
{
"main": "index.js",
"bin": {
"mycli": "index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
}
- 通过package.json 的 main 我们可以指定库的入口
- 在bin下面定义的命令,如bin:{ "mycli": "index.js"},就是在命令行直接执行mycli ,会对应执行的js文件
- 通过npm link,就可以在本地终端 直接使用 mycli命令, 当然发布到网上,通过npm -g 安装后也是一样的效果
4.前置工具
- commander : 命令行解决方案
- chalk:命令行彩色文字
- figlet:命令行艺术字
- inquirer : 命令行输入交互,提供多种问答方式
- ora : loading效果
- download-git-repo : 远程下载
- handlebars : 模板引擎替换html的占位内容 {{name}}
- ejs : 模板引擎替换html的占位内容,<%= name %>替换
- clear 清屏
- open : 打开浏览器
5.开发cli
- 构建结构与依赖
mkdir mycli
cd mycli
npm init -y
npm i commander download-git-repo ora handlebars figlet clear chalk open -s
- 设置启动命令 创建启动脚本 bin/run.js
#!/usr/bin/env node
// #!是shebang命令 /usr/bin/env 用来告诉用户到path目录下去寻找node,#!/usr/bin/env node 可以让系统动态的去查找node
//可以通过 which node 查找当前node的安装位置 /usr/local/bin/node
const program = require("commander")
// const init = require("../lib/init")
//定义版本 -v 响应
program.version(require("../package.json").version)
//< >和[ ]分别代表必填和选填 ,在action 就会返回对应的录入参数 name
program.command("init <name>").description("init project").action(require("../lib/init"))
// init()
//开始监听输入
program.parse(process.argv)
对应 package.json 修改 新增如下
"bin": {
"mycli": "./bin/run.js"
},
3. 定制初始化界面 lib/init.js
const {promisify} = require("util")
const figlet = promisify(require('figlet'))
const chalk = require("chalk")
const clear = require("clear")
const clone = require("../lib/download")
//添加颜色输出控制台
const colorLog = content => console.log(chalk.redBright(content))
module.exports = async name => {
clear()//清屏
const data = await figlet("welcome to jason cli")//把文字转化成输出
colorLog(data)//打印
//name 为刚刚init 创建的 文件夹名称
await clone('github:mjsong07/myComponent', name)
}
- 封装下载代码逻辑 lib/download.js
const {promisify} = require("util")
const ora = require("ora")
module.exports = async (repo,filderPath) => {
const download = promisify(require("download-git-repo"))
//进程提示
const process = ora(`downloading ${repo}`)
process.start()
await download(repo,filderPath)
process.succeed();
}
- 新增依赖安装与自动打开网页,运行命令 优化 init.js 代码
const { promisify } = require('util')
const figlet = promisify(require('figlet'))
const clear = require('clear')
const chalk = require('chalk')
const { clone } = require('./download')
const spawn = async (...args) => {
const { spawn } = require('child_process')
return new Promise(resolve => {
const proc = spawn(...args)
proc.stdout.pipe(process.stdout)
proc.stderr.pipe(process.stderr)
proc.on('close', () => {
resolve()
})
})
}
const log = content => console.log(chalk.green(content))
module.exports = async name => {
// 打印欢迎画面
clear()
const data = await figlet('Welcome')
log(data)
// 创建项目
log(`🚀创建项目:` + name)
// 克隆代码
await clone('github:mjsong07/myComponent', name)
log('安装依赖')
await spawn('npm', ['install'], { cwd: `./${name}` })
log(`
👌安装完成:
To get Start:
===========================
cd ${name}
npm run serve
===========================
`)
const open = require('open')
open('http://localhost:8080')
await spawn('npm', ['run', 'serve'], { cwd: `./${name}` })
}
bin\serve.js 启动服务
const spawn = (...args) => {
const { spawn } = require('child_process');
const proc = spawn(...args)
proc.stdout.pipe(process.stdout)
proc.stderr.pipe(process.stderr)
return proc
}
module.exports = async () => {
const watch = require('watch')
let process
let isRefresh = false
watch.watchTree('./src', async (f) => {
if (!isRefresh) {
isRefresh = true
process && process.kill()
await require('./refresh')()
setTimeout(() => { isRefresh = false }, 5000)
process = spawn('npm', ['run', 'serve'])
}
})
}
bin\refresh.js 刷新路由代码
const fs = require('fs')
const handlebars = require('handlebars')
const chalk = require('chalk')
module.exports = async () => {
// 获取页面列表
const list =
fs.readdirSync('./src/views')
.filter(v => v !== 'Home.vue')
.map(v => ({
name: v.replace('.vue', '').toLowerCase(),
file: v
}))
compile({
list
}, './src/router.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 reslut = handlebars.compile(content)(meta)//使用模板引擎替换内容
fs.writeFileSync(filePath, reslut)
}
console.log(chalk.red(`🚀${filePath} 创建成功`))
}
}
发布代码到npm
./publish.sh
#!/usr/bin/env bash
npm config get registry # 检查仓库镜像库
npm config set registry=https://registry.npmjs.org
echo '请进行登录相关操作:'
npm login # 登陆
npm whoami # 查看登录用户
echo "-------publishing-------"
npm publish # 发布
npm config set registry=https://registry.npmmirror.com # 还原本地淘宝源地址
echo "发布完成"
exit
执行: sh ./publish.sh
6.扩展
1. 使用ejs进行模板特殊字符替换
vue-template.vue.ejs
<template>
<div class="<%= data.lowerName %>">
<h2>{{ message }}</h2>
</div>
</template>
<script>
export default {
name: "<%= data.name %>",
components: {
},
mixins: [],
props: {
},
data: function() {
return {
message: "Hello <%= data.name %>"
}
},
created: function() {
},
mounted: function() {
},
computed: {
},
methods: {
}
}
</script>
<style scoped>
.<%= data.lowerName %> {
}
</style>
util\utils.js 工具类
// 编译模板
const compiler = (templateName, data) => {
// 根据用户执行的命令,拿到指定路径的模板,进行渲染创建
const templateCurrentPath = `../templates/${templateName}`;
const templateAbsolutePath = path.resolve(__dirname, templateCurrentPath);
// 读取HTML 标签
return new Promise((resolve, reject) => {
ejs.renderFile(templateAbsolutePath, { data }, {}, (err, result) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
};
// 递归创建不存在的文件
const createNotFileName = (pathName) => {
if (fs.existsSync(pathName)) {
return true;
} else {
// 找到当前路径的父路径
if (createNotFileName(path.dirname(pathName))) {
fs.mkdirSync(pathName);
return true;
}
}
};
// 写入文件夹操作
const writeToFile = (pathName, result) => {
//先判断文件夹是否存在,不存在则创建
const dirname = path.dirname(pathName);
if(!fs.existsSync(dirname)){
fs.mkdirSync(dirname);
}
// path: 目标文件夹的绝对路径(只支持绝对路径)
return fs.promises.writeFile(pathName, result);
};
module.exports = {
compiler,
writeToFile,
createNotFileName,
changeJson,
};
测试替换代码
// create VUE component template
async function createVueComponentTemplateAction(project, filepath) => {
// 编译 ejs 模板
const result = await compiler("vue-template.vue.ejs", {
name: project,
lowerName: project.toLowerCase(),
});
// 将 result 写入 .vue 文件中
const targetPath = path.resolve(filepath, `${project}.vue`);
// write in file
await writeToFile(targetPath, result);
};