目标
- 最少代码实现
- 选择包管理器,npm源功能
- 插件化,自由进行扩展
输入命令,准备创建项目
- 解析用户命令,弹出交互语句,用户自己选择
- 脚手架创建package.json文件
- 执行npm i安装依赖
技术解决点:
1. 处理用户命令:commander.js(解析用户的命令,提取出用户的输入交给脚手架)
#!/usr/bin/env node
const program = require('commander')
const create = require('../lib/create')
program
.version('0.1.0')
.command('gz <name>')
.description('create a new project')
.action(name => {
create(name)
})
program.parse()
注册了gz命令,放在项目bin目录,cli.js,在package.json里添加“bin”:{ “gz”:“./bin/cli.js” },npm link将其注册成全局命令。
2. 和用户交互
询问用户创建项目需要哪些功能。使用Inquirer.js, 弹出一个问题和一些选项,让用户选择。
例如下面的代码:
const questions = [
{
type: "input",
name: "compZhName",
prefix: "欢迎使用dvp-cli快速搭建新组件,",
message: "请输入组件中文名",
validate(value) {
if (value) return true;
return "组件中文名称必填"
}
},
{
type: "rawlist",
name: "compClassify",
prefix: "欢迎使用dvp-cli快速搭建新组件,",
message: "请选择组件分类",
choices: [
{ name: "图表" },
{ name: "控件" },
{ name: "媒体" },
{ name: "3D" },
{ name: "GIS" },
{ name: "容器" },
]
}
]
inquirer.prompt(questions)
3. 渲染模板
使用ejs和prettier
fs.writeFileSync(`${getCompPath()}/index.vue`, createVueTemplate(answer));
import fs from 'fs';
import ejs from 'ejs';
import path from 'path';
import { fileURLToPath } from "url";
import prettier from "prettier";
export default (config) => {
const __dirname = fileURLToPath(import.meta.url);
const template = fs.readFileSync(path.resolve(__dirname, "../template/view.ejs"));
const code = ejs.render(template.toString(), {
compEnName: config.compEnName,
})
return prettier.format(code, { "trailingComma": "none", parser: "vue" });
}
{
"name": "<%= packageName %>",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
<% if(middleware.husky) { %>
,"prepare": "husky install"
<% } %>
},
"dependencies": {
"vue": "3.2.8",
"lint-staged": "^12.3.2",
"axios": "^0.25.0",
"vue-router": "4.0.11",
"vuex": "4.0.2"
<% if(middleware.dayjs) { %>
,"dayjs": "^1.10.7"
<% } %>
<% if(middleware.driver) { %>
,"driver.js": "^0.9.8"
<% } %>
<% if(middleware.element) { %>
,"element-plus": "^1.3.0-beta.9"
<% } %>
<% if(middleware.screenfull) { %>
,"screenfull": "5.1.0"
<% } %>
<% if(middleware.i18n) { %>
,"vue-i18n": "^9.2.0-beta.30"
<% } %>
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"core-js": "^3.20.3",
"cz-customizable": "^6.3.0",
"svg-sprite-loader": "^6.0.9",
"unplugin-auto-import": "^0.5.11",
"unplugin-vue-components": "^0.17.15",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2"
<% if(middleware.husky) { %>
,"husky": "7.0.1"
<% } %>
<% if(middleware.eslint) { %>
,"eslint": "^6.7.2",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.0.0"
<% } %>
<% if(middleware.commitLink) { %>
,"@commitlint/cli": "12.1.4"
<% } %>
<% if(middleware.commitLink) { %>
,"@commitlint/config-conventional": "^12.1.4"
<% } %>
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
},
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
}
}
4. 下载依赖
execa,它可以调用子进程执行命令。
execa("npm i", {
cwd: getRootPath(),
stdio: [2, 2, 2]
})
console.log('\n依赖下载完成! 执行下列命令开始开发:\n')
console.log(`cd ${name}`)
console.log(`npm run dev`)
调用 executeCommand()
开始下载依赖,参数为 npm install
和用户创建的项目路径。为了能让用户看到下载依赖的过程,我们需要使用下面的代码将子进程的输出传给主进程,也就是输出到控制台:
child.stdout.on('data', buffer => {
process.stdout.write(buffer)
})
5. 未来完善点
- 创建项目时判断该项目是否已存在,支持覆盖和合并创建。(fs.existsSync(targetDir))
- 选择功能时提供默认配置和手动选择两种模式。
- 如果用户的环境同时存在 yarn 和 npm,则会提示用户要使用哪个包管理器。
- 如果 npm 的默认源速度比较慢,则提示用户是否要切换到淘宝源。
- 如果用户是手动选择功能,在结束后会询问用户是否要将这次的选择保存为默认配置。
参考资料:https://github.com/vuejs/vue-cli