越努力,越幸运,做个ccode~

0%

写一个cli脚手架

1 新建项目目录

1
mkdir my-cli
2
cd my-cli
3
npm init —y  # 生成package.json 文件

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

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

3 在package.json 配置bin 字段

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

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

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

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

5 执行test2

6 引入commander 添加子命令

test2.js 初始化项目

1
#!/usr/bin/env node
2
const program = require('commander')
3
// 以package.json 的version 作为test2 的version
4
program.version(require('../package.json').version)
5
program
6
  .command('init <name>')
7
  .description('init project  ')
8
  .action(require('../lib/init'))
9
10
// 解析主进程参a
11
program.parse(process.argv)

7 使用 figlet chalk 输出欢迎界面

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

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

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

使用 ora 在下载时显示loading

1
// download.js
2
const { promisify } = require('util')
3
const ora = require('ora')
4
5
module.exports.clone = async function(repo,desc){
6
  const download = promisify(require('download-git-repo'))
7
  const process = ora(`下载..... ${repo}`)
8
  process.start()
9
  await download(repo,desc)
10
  console.log(repo,desc)
11
  process.succeed()
12
}

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

9 执行命令 自动安装依赖

使用 spawn 执行 命令

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

10 自动启动项目

使用open 打开浏览器

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

11 实现约定路由功能

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

1
const fs = require('fs')
2
const handlebars = require('handlebars')
3
4
const program = require('commander')
5
program
6
  .command('create component <name>')
7
  .description('init project  ')
8
  .action(refresh)
9
program.parse(process.argv)
10
11
// module.exports = async () => {
12
async function refresh (name){
13
  console.log(name)
14
  // 获取列表
15
  const list = fs.readdirSync('./src/views')
16
    .filter(v => v !== 'Home.vue')
17
    .map(v => ({
18
      name: v.replace('.vue','').toLowerCase(),
19
      file: v
20
    }))
21
  console.log(list)
22
23
  // 生成路由定义
24
  compile({list},'./src/router/index.js','./template/router.js.hbs')
25
26
  // 生成菜单
27
  compile({list},'./src/App.vue','./template/App.vue.hbs')
28
29
  /**
30
   * 模板编译 
31
   * @param {*} meta 
32
   * @param {*} filePath 
33
   * @param {*} templatePath 
34
   */ 
35
  function compile(meta,filePath,templatePath){
36
    if(fs.existsSync(templatePath)){
37
      const content = fs.readFileSync(templatePath).toString()
38
      const result = handlebars.compile(content)(meta)
39
      fs.writeFileSync(filePath,content)
40
      console.log(`${filePath}创建成功`)
41
    } 
42
  }
43
}