背景
在搭建环境过程中,发现总有一些常用的工具,要经过繁琐的步骤才能生成。
思考着怎么简化这个工作。偶然想起各种脚手架挺方便的,那就需要看看cli是如何生成的。
笔者分享一下整个入门的过程中。
内容
了解到的node的脚手架,是基于Commander生成。
对应github: github.com/tj/commande…
来看官网的介绍:
编写代码来描述你的命令行界面。 Commander 负责将参数解析为选项和命令参数,为问题显示使用错误,并实现一个有帮助的系统。
功能简介
可能可看到几个重要的入门点:
选择值,命令参数,判断,输入,拷贝文件,函数等。
实例扩展
1)新建项目: 安装cb
- npm init,输入对应的数据,生成package.json
- 定义bin命令,笔者的cli命令为cb。
执行index,js的代码,故:
"bin": {
"cb": "index.js"
},
-
引入commander,分享最终的package.json:
{ "name": "cb-cli", "version": "1.0.0", "description": "分享搭建cli过程", "directories": { "lib": "lib" }, "bin": { "cb": "index.js" }, "scripts": { "build": "cb build" }, "main": "index.js", "repository": { "type": "git", "url": "git+https://github.com/zhuangweizhan/cb-cli.git" }, "keywords": [ "cb", "cli" ], "author": "zhuangweizhan", "license": "ISC", "bugs": { "url": "https://github.com/zhuangweizhan/cb-cli/issues" }, "dependencies": { "commander": "^6.2.1", "fs-extra": "^10.0.0", "execa": "^5.0.0", "inquirer": "^7.3.3", "minimist": "^1.2.6", "mustache": "^4.2.0" }, "homepage": "https://github.com/zhuangweizhan/cb-cli#readme" }
值得一提的是,需要声明,如果都需要用es6的import,需要"type": "module", 不然后续会影响import的引入。
2)入门案例: cb build
上边配置的bin, key就是命令名称,而value就是对应的路径。
所以新建index.js:
#!/usr/bin/env node
const program = require('commander')
program.version('1.0.0')
program.command('build').description('打包cli成功')
program.parse(process.argv)
此时第一个cli已经写好,本地安装一下: npm link
即可执行:cb 与 cb build
3)命令传参: cb param
假设需要接收外部的参数,供脚手架使用,可以借助action的options获取,代码如下:
#!/usr/bin/env node
const program = require('commander')
program.version('1.0.0')
const getParams = options => {
options || (options = [])
return options.reduce((prev, cur) => {
if (cur.includes('=')) {
const array = cur.split('=')
return { ...prev, [array[0]]: array[1] }
} else {
return prev
}
}, {})
}
program
.command('build')
.description('打包cli命令')
.action((appName, options) => {
// console.log(`打包cli成功, options`, options.template)
const params = getParams(options)
console.log(`打包cli成功, 对应的参数是`, params)
})
program.parse(process.argv)
4)读取配置文件: cb config
如果参数角度的情况下,命令传参已经无法满足。此时需要借助配置文件,如常见的vue.config.js等。教程如下:
新建cb.config.js
module.exports = {
name: '成功咯',
}
再新建脚本:
program
.command('config')
.description('读取配置文件')
.action((appName, options) => {
let config = {
path: 'svg',
}
const configPath = CWD + '\\cb.config.js'
// 如果使用了配置文件,则以配置文件为准
if (existsSync(configPath)) {
const userConfig = require(resolve(CWD, 'cb.config.js'))
config = { ...config, ...userConfig }
console.log(`存在配置文件cb.config.js,获取到的名字为`, config.name)
} else {
console.log(`不存在配置文件`)
}
})
5)条件/判断: cb condition
cli少不了,让用户快速选择,快速输入的情况。 如vue-cli,需要让你输入项目名称,选择是否需要eslint等。
这一般都是借助inquirer实现。直接查看案例:
#!/usr/bin/env node
const CWD = process.cwd()
import commander from 'commander'
import fs from 'fs'
import inquirer from 'inquirer'
import path from 'path'
const { resolve } = path
const { program } = commander
program
.command('condition')
.description('选择属性')
.action((appName, options) => {
inquirer
.prompt([
{
type: 'confirm',
name: 'language',
message: '新建项目是否引入typescript?',
},
{
type: 'input',
name: 'desc',
message: '请输入项目备注',
},
])
.then(result => {
console.log('请输入', result)
})
})
program.parse(process.argv)
6)新建/拷贝页面: cb create
实际脚手架中,有很多使用到的页面。如vue-cli,他自带了许多页面。那如何新建一个页面呢?
直接看代码:
program
.command('create')
.description('创建index文件')
.action(async (appName, options) => {
const copyPath = `/template/demo.vue`
const pageName = 'new'
console.log(path.join(CWD, copyPath))
let template_content = await fs.readFile(path.join(CWD, copyPath))
template_content = template_content.toString()
const result = Mustache.render(template_content, {
pageName,
})
//开始创建文件
await fs.writeFile(path.join('./dist/', `${pageName}.vue`), result)
console.log('\n页面创建成功!\n')
})
program.parse(process.argv)
此时,执行cb create后,将产生新文件:
7)注入依赖: cb rely vue
我们还会经常遇到一个脚手架的方法,通常会帮你配置好package.json, 然后帮你npm install。这就是自动注入依赖。
我们需要借助execa来实现。
// 注入依赖
program
.version('0.1.0')
.command('rely <name>')
.description('新建一个项目注入依赖')
.action(name => {
create(name)
})
// create.js
const dir = process.cwd()
const execa = require('execa')
const fs = require('fs-extra')
const path = require('path')
async function create(projectPath) {
// package.json 文件内容
const packageObject = {
name: projectPath,
version: '0.1.0',
dependencies: {
vue: '^2.6.12',
},
devDependencies: {},
}
const packagePath = projectPath + '/package.json'
const filePath = path.join(dir, packagePath)
fs.ensureDirSync(path.dirname(filePath))
fs.writeFileSync(filePath, JSON.stringify(packageObject))
console.log('\n正在下载依赖...\n', filePath)
// // 下载依赖
execa('npm install', [], {
cwd: path.join(dir, projectPath),
stdio: ['inherit', 'pipe', 'inherit'],
})
console.log(`下载成功 ${projectPath}`)
console.log(`cd ${projectPath}`)
console.log(`npm run dev`)
}
module.exports = create
8)发布npm
此时,已经完成一个简单的脚手架,就剩下最后一步,发布npm。
这里暂不分享,下篇整理后为专文输出。
借鉴
- Kay_: juejin.cn/post/701287…
- 谭光志: juejin.cn/user/143341…
源码
记得切换v1分支。