Appearance
自定义命令和入口配置
首先,执行 npm init -y 生成一个package.json文件
接着在创建lib/index.js
javascript
console.log("my cli code exec~");我们希望在命令行输入whycli就可以执行lib/index.js这个文件
那么就需要找到package.json增加以下配置:
json
{
...
"bin": {
"whycli": "./lib/index.js"
},
}紧接着在lib/index.js头部添加
javascript
#!/usr/bin/env node表示去环境变量找node,让node运行整个文件
目前执行whycli会报这个命令找不到,因为我们还没有将whycli添加到环境变量
如果我们通过npm publish发布到了npm,后面使用的时候通过npm install安装就会把whycli添加到环境变量
但是我们目前在本地,还没有发布,那么就需要执行
json
npm link执行完之后,就会将whycli添加到环境变量,whycli和./lib/index.js会建立一个软连接
到这里,就可以在命令行执行whycli,它就会去执行lib/index.js里面的代码
查看当前版本号
如果我们想要通过执行whycli --version或whycli -v查看package.json中的版本号,那么就需要借助一个工具叫commander
首先,安装这个库
json
npm install commanderlib/index.js
javascript
#!/usr/bin/env node
const { program } = require("commander");
// 处理--version 的操作
const version = require("../package.json").version;
program.version(version, "-v --version");
// 让program解析process.argv参数
program.parse(process.argv);增加options选项和封装
lib/core/help-options.js
javascript
const { program } = require('commander')
function helpOptions() {
// 1.处理--version的操作
const version = require('../../package.json').version
program.version(version, '-v --version')
// 2.增强其他的options的操作
program.option('-w --why', "a why cli program~")
// <dest>表示传进去的参数
program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/components')
program.on('--help', () => {
console.log("")
console.log("others:")
console.log(" xxx")
console.log(" yyy")
})
}
module.exports = helpOptions
想要获取这个额外的参数
javascript
console.log(pagram.opts().dest)
lib/index.js
javascript
...
const helpOptions = require('./core/help-options')
// 1.配置所有的options
helpOptions()此时,我们在命令行敲whycli --help就可以看到以下信息

create命令创建项目的过程
我们现在的脚手架实现的功能非常有限,仅仅只是能查看版本号和help的一些信息,这样并没有什么意义
我们希望通过在命令行执行whycli create xxx,就可以创建一个叫xxx的项目,但是它是从哪里来的呢?
我们可以将一个模板放到github,然后执行whycli create xxx的时候就会去克隆这个模板,具体实现如下:
lib/index.js
javascript
...
const { createProjectAction } = require('./core/actions')
// 2.增加具体的一些功能操作
program
.command("create <project> [...others]") // others表示可以跟其他一些参数
.description("create vue project into a folder, 比如: whycli create airbnb")
.action(createProjectAction)lib/core/actions.js
这里需要安装download-git-repo这个库
json
npm install download-git-repojavascript
const { promisify } = require('util')
const download = promisify(require('download-git-repo'))
const { VUE_REPO } = require('../config/repo')
async function createProjectAction(project) {
try {
// 1.从编写的项目模板中clone下来项目
await download(VUE_REPO, project, { clone: true })
} catch (error) {
console.log("github连接失败, 请稍后重试")
}
}util是node内置的一个工具库,里面的promisify方法可以支持使用Promise语法
lib/config/repo.js
javascript
const VUE_REPO = "direct:https://github.com/coderwhy/vue3_template.git#main" // 指定从main分支克隆
module.exports = {
VUE_REPO
}脚手架执行安装和运行命令
很多脚手架在执行create命令创建完项目之后,都会给提示

lib/core/actions.js
javascript
...
async function createProjectAction(project) {
try {
// 2.很多的脚手架, 都是在这里给予提示
console.log(`cd ${project}`)
console.log(`npm install`)
console.log(`npm run dev`)
} catch (error) {
console.log("github连接失败, 请稍后重试")
}
}如果我们想要在脚手架内部执行安装和运行命令又要怎么实现呢?
lib/core/actions.js
javascript
const execCommand = require('../utils/exec-command')
async function createProjectAction(project) {
try {
// 1.从编写的项目模板中clone下来项目
await download(VUE_REPO, project, { clone: true })
// 2.很多的脚手架, 都是在这里给予提示
// console.log(`cd ${project}`)
// console.log(`npm install`)
// console.log(`npm run dev`)
// 3.帮助执行npm install
console.log(process.platform) // window32的是npm.cmd
const commandName = process.platform === 'win32'? 'npm.cmd': 'npm'
await execCommand(commandName, ["install"], { cwd: `./${project}` })
// 4.帮助执行npm run dev
await execCommand(commandName, ["run", "dev"], { cwd: `./${project}` })
} catch (error) {
console.log("github连接失败, 请稍后重试")
}
}lib/utils/exec-command.js
需要借助node的子进程
javascript
const { spawn } = require('child_process')
function execCommand(...args) {
return new Promise((resolve) => {
// npm install/npm run dev
// 1.开启子进程执行命令
const childProcess = spawn(...args)
// 2.获取子进程的输出和错误信息
childProcess.stdout.pipe(process.stdout)
childProcess.stderr.pipe(process.stderr)
// 3.监听子进程执行结束, 关闭掉
childProcess.on('close', () => {
resolve()
})
})
}
module.exports = execCommand脚手架创建添加组件的过程
我们希望执行whycli addcpn xxx,就可以创建一个叫xxx.vue的组件到指定目录中
lib/index.js
javascript
const { addComponentAction } = require('./core/actions')
program
.command("addcpn <cpnname> [...others]")
.description("add vue component into a folder, 比如: whycli addcpn NavBar -d src/components")
.action(addComponentAction)lib/core/actions.js
javascript
...
const compileEjs = require('../utils/compile-ejs')
const writeFile = require('../utils/write-file')
async function addComponentAction(cpnname) {
// 1.创建一个组件: 编写组件的模板, 根据内容给模板中填充数据
const result = await compileEjs("component.vue.ejs", {
name: cpnname,
lowername: cpnname.toLowerCase()
})
// 2.将result写到到对应的文件夹中
const dest = program.opts().dest || "src/components"
await writeFile(`${dest}/${cpnname}.vue`, result)
console.log("创建组件成功:", cpnname + ".vue")
}
module.exports = {
addComponentAction
}lib/utils/compile-ejs.js
需要安装ejs这个库
json
npm install ejsjavascript
const path = require('path')
const ejs = require('ejs')
function compileEjs(tempName, data) {
return new Promise((resolve, reject) => {
// 1.获取当前模板的路径
const tempPath = `../template/${tempName}`
const absolutePath = path.resolve(__dirname, tempPath)
// 2.使用ejs引擎编译模板
ejs.renderFile(absolutePath, data, (err, result) => {
if (err) {
console.log("编译模板失败:", err)
reject(err)
return
}
resolve(result)
})
})
}
module.exports = compileEjslib/utils/write-file.js
javascript
const fs = require('fs')
function writeFile(path, content) {
return fs.promises.writeFile(path, content)
}
module.exports = writeFile组件模板
template/component.vue.ejs
Vscode搜索EJS language support,安装这个插件,就会有提示
ejs
<template>
<div class="<%= lowername %>">
<h2><%= name %>: {{ message }}</h2>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref("哈哈哈")
</script>
<style>
.<%= lowername %> {
color: red;
}
</style>至此,我们执行whycli addcpn TabBar,就会在src/components目录下创建一个TabBar组件
html
<template>
<div class="tarbar">
<h2>TarBar: {{ message }}</h2>
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref("哈哈哈")
</script>
<style>
.tarbar {
color: red;
}
</style>当然如果我们也可以指定路径,比如创建一个Banner组件到src/views/home/c-cpns
