本文是从我们公司的需求为出发点,编写一个简单的脚手架工具。
首先,让我们分析需求:目前公司的项目比较多,分属于不同的客户,每个项目都是独立部署的,但是项目的前端框架基本分为三种:
- 以 app 为载体的 内嵌 H5 页面: vue 全家桶 + vant ui
- admin 后台管理系统:vue 全家桶 + element ui
- admin 后台管理系统: react + ant design
本文不涉及具体前端项目框架的实现,只解决创建项目时,根据业务的需求,使用脚手架工具生成简单的项目模板。
创建项目
mkdir gt-cli
cd gt-cli
npm init
package.json
{
"name": "gt-cli",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"gt": "bin/gt.js"
},
"url": "",
"email": "",
"author": "Iris Mei",
"repository": {
"type": "git",
"url": ""
},
"license": "ISC",
"dependencies": {
"chalk": "^4.1.0",
"commander": "^7.0.0",
"inquirer": "^7.3.3",
"log-symbols": "^4.0.0"
}
}
安装依赖
npm install
chalk: 处理命令行输出文字的样式(颜色、背景色)
commander:node命令行解决方案
inquirer:node 命令行交互(信息录入、选择、信息确认等......)
log-symbols: 打印日志图标,如: √,×
目录结构
┌─── gt-cli
│├── bin文件夹
│ ├─ gt.js
│├── command文件夹
│ ├─ init.js
│├── node_module文件夹
│├── .gitignore
│├── package.json
│├── README.md
│├── template.json
gt.js: 入口文件
init.js: 初始化项目逻辑代码
template.json: 不同项目模板的描述和git仓库地址
开始coding
首先,在template.json 中 配置不同项目模板的名称、描述、仓库地址:
{
"vue-admin": {
"gitUrl": "git 仓库地址",
"name": "admin system(vue + element)"
},
"vue-web": {
"gitUrl": "git 仓库地址",
"name": "web H5 (vue + vint)"
},
"react-admin": {
"gitUrl": "git 仓库地址",
"name": "admin system(react + ant design)"
}
}
在gt.js 中,针对不同的命令执行不同的操作,除了查看版本号,使用帮助以外,现在仅有初始化项目操作,使用 gt init <projectName>
初始化项目模板。
#!/usr/bin/env node --harmony
'use strict'
// 终端图标
const symbols = require('log-symbols')
// 终端命令行美化
const chalk = require('chalk')
// node命令行解决方案
const program = require('commander')
// cli 版本
program
.version(require('../package.json').version)
// 使用说明
program
.usage('<command>')
// 初始化一个项目 命令:gt init <projectName> || gt i <projectName>
program
.command('init') // 命令
.description('init a new project') // 描述
.alias('i') // 命令别名
.action(() => { // 命令执行的操作
// 如果参数中没有项目名称,退出命令行
if (program.args.length < 2) {
console.log(symbols.error, chalk.red('Please input the project name, for example: gt init vue-app'))
process.exit()
}
require('../command/init')(program.args[1])
})
// 处理非法命令
program
.arguments('<command>')
.action(cmd => {
// 输出帮助信息
console.log(symbols.error, chalk.bgRed(`Unknown command: ${cmd}.`));
program.help()
});
// 命令行参数解析
program
.parse(process.argv)
// 命令行没有参数时,执行help命令!
program.args.length && program.help()
init.js 用来处理初始化项目的逻辑:用户录入项目信息,选择模板,脚手架从git仓库拉取项目模板,然后安装依赖。完整代码:
'use strict'
// 执行复杂的命令行命令
const exec = require('child_process').exec
// 终端图标
const symbols = require('log-symbols')
// 响应用户命令行交互
const inquirer = require('inquirer')
// 终端命令行美化
const chalk = require('chalk')
// 模板类型
const projectTpl = require('../template.json')
module.exports = async (name) => {
// 项目需要录入的信息
const promptList = [{
type: 'input',
message: `project name(default: ${name}): `,
name: 'projectName',
default: name
}, {
type: 'input',
message: `The project description: `,
name: 'description',
}, {
type: 'list',
message: 'please choose a project template: ',
name: 'projectType',
choices: Object.keys(projectTpl),
filter: function (val) {
return projectTpl[val];
}
}];
let answers = await inquirer.prompt(promptList)
// 确认项目信息
let confirm = await inquirer.prompt([{
type: 'confirm',
message: `Please confirm the project infomation: \n project name: ${answers.projectName} \n project description: ${answers.description} \n project template: ${answers.projectType.name} \n are you sure? `,
name: 'isSure',
default: ''
}])
// 如果没有确认项目信息,退出命令行
if (!confirm.isSure) {
console.log(chalk.bgYellow('The process already exited.'))
process.exit()
}
// 拉取项目模板,使用master分支
const cmd = `git clone ${answers.projectType.gitUrl} ${answers.projectName} `
// 开始初始化项目
console.log('The project start cloning......')
exec(cmd, (error) => {
if (error) {
console.log(symbols.error, chalk.red(error))
process.exit()
}
console.log(symbols.success, chalk.green('git clone is successed!'))
console.log('\n npm install......')
exec(`cd ${answers.projectName} && npm install`, (error) => {
if (error) {
console.log(symbols.error, chalk.red(error))
console.log(symbols.error, chalk.red(`please npm install again or use cnpm install`))
process.exit()
}
console.log(symbols.success, chalk.green('Generation completed!'))
process.exit()
})
})}
本地调试
在package.json 同级目录中执行 npm link
,创建本地npm模块的链接。
执行后显示下面的信息,说明link 成功:
audited 39 packages in 1.408s
6 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
C:\Users\你的用户名\AppData\Roaming\npm\gt -> C:\Users\你的用户名\AppData\Roaming\npm\node_modules\gt-cli\bin\gt.jsC:\Users\你的用户名\AppData\Roaming\npm\node_modules\gt-cli -> C:\Users\你的用户名\Desktop\workspace\gt-cli
现在可以试试用自己写的脚手架创建一个空项目模板了,在终端中执行:gt init <项目名称>
然后根据提示,输入项目信息:
? project name(default: demo): (demo)
? The project description: A test project
? please choose a project template: (Use arrow keys)
> vue-admin
vue-web
react-admin
? Please confirm the project infomation:
project name: demo
project description: A test project
project template: admin system(vue + vue router + vuex)
are you sure? (Y/n) Yes
The project start cloning......
√ git clone is successed!
npm install......
C:\Users\iris.mei\Desktop\test process.cwd
√ Generation completed!
需要取消link时,在 C:\Users\你的用户名\AppData\Roaming\npm\node_modules 目录中执行npm unlink <模块名称> :
写在最后
一个简单的脚手架写完了,还有很多可以优化的点:
- 方法封装
- 项目目录验证
- 目录名称验证
- ......
我先抛个砖,以后有空再优化 ~~
最后,部分代码借鉴其他大佬的博客,向大佬学习~~
文中如有不妥之处,欢迎指正~~