在平时开发中一般用的 vue create projectName
来创建项目。所以想着是否可以模仿着写一个简易版的脚手架。下面是开发一个简易脚手架的步骤。觉得好玩的话可以试着敲一下。
# 1、搭建项目环境
- 初始化目录
├── bin
│ └── tj-cli // 全局命令执行的根文件
├── package.json
├── lib
│ ├── main.js // 入口文件
│ └── utils // 存放工具方法
│── .huskyrc // git hook
│── .eslintrc.json // 代码规范校验
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 在目录创建
bin
文件夹,并添加一个脚手架文件
//bin/tj-cli
//表示在node环境下运行
#! /usr/bin/env node
# 引入入口文件
require('../src/main.js');
1
2
3
4
5
2
3
4
5
- 在
package.json
中配置跟scripts
同级的bin
字段
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
"tj": "./bin/tj",
"tkj-cli": "./bin/tj"
},
1
2
3
4
5
6
7
2
3
4
5
6
7
npm link
建立软连接,映射到全局(默认放到node设置的全局安装目录) 此时你可以在命令行(cmd
)或者gitbash
中输入 tj-cli或者tj
都会运行tj-cli
文件。(tj/tj-cli是你在package.json
中配置的名称)。此时你也可以将tj/tj-cli
添加到scripts
中使用了
# 2、代码编写
在编写代码之前要先安装一下项目中用到的包。
commander
——核心模块,主要用于参数解析inquirer
——交互式命令行工具,有他就可以实现命令行的选择功能download-git-repo
—— 在git中下载模板chalk
——帮我们在控制台中画出各种各样的颜色metalsmith
——读取所有文件,实现模板渲染consolidate
——统一模板引擎
# 3、使用commander
npm install commander
1
在路口文件 main.js
开始编写我们的代码。可以参考commander
的官方文档地址 https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md
commander
主要有以下 API
commander.version()
//动态获取版本信息
program
.version(`tj-cli@${require('../package.json').version}`)
.usage(`<command> [option]`)
1
2
3
4
2
3
4
# 4、编写help命令
program.on('--help', () => {
console.log()//是help命令离其他命令远一点
console.log(`Run ${chalk.cyan("tj-cli < command > --help")} show detail`)
})
1
2
3
4
2
3
4
# 5、配置拉取模板项目的代码
const program = require("commander");
const chalk = require('chalk');
const cleanArgs = (cmd) => {
const args = {};
cmd.options.forEach(o => {
const key = o.long.slice(2);
if (cmd[key]) {
args[key] = cmd[key];
}
})
return args;
}
program
.command("create <app-name>")
.description("crreat a new project")
.option("-f, --force", "overwrite target directory if it exist")
.action((name, cmd) => {
require("../lib/create")(name, cleanArgs(cmd))
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5、拉取模板
模板一般放在github
上,所以在拉取模板时需要用到axios
包。实现一些交互的包 inquirer, ora
。
//lib/create.js 在这里创建项目目录
const path = require('path');
const fs = require('fs-extra');
const Inquirer = require('inquirer');
const Creator = require('./Creator');
module.exports = async function (projectName, options) {
// 创建项目
const cwd = process.cwd(); // 获取当前命令执行时的工作目录
const targetDir = path.join(cwd, projectName); // 目标目录
if (fs.existsSync(targetDir)) {
if (options.force) { // 如果强制创建 ,删除已有的
await fs.remove(targetDir);
} else {
// 提示用户是否确定要覆盖
let { action } = await Inquirer.prompt([ // 配置询问的方式
{
name: 'action',
type: 'list', // 类型非常丰富
message: `Target directory already exists Pick an action:`,
choices: [
{ name: 'Overwrite', value: 'overwrite' },
{ name: 'Cancel', value: false }
]
}
]);
if (!action) {
return
} else if (action === 'overwrite') {
await fs.remove(targetDir)
}
}
}
// 创建项目
const creator = new Creator(projectName, targetDir);
creator.create(); // 开始创建项目
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//lib/Creator
async create () {
// 真实开始创建了
// 1) 先去拉取当前组织下的模板
let repo = await this.fetchRepo();
// 2) 在通过模板找到版本号
let tag = await this.fetchTag(repo);
// 3) 下载
await this.download(repo, tag);
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
- 获取模板
async fetchRepo () {
// 失败重新拉取
let repos = await wrapLoading(fetchRepoList, 'waiting fetch template');
if (!repos) return;
repos = repos.map(item => item.name);
let { repo } = await Inquirer.prompt({
name: 'repo',
type: 'list',
choices: repos,
message: 'please choose a template to create project'
});
return repo
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
//lib/request.js
//下载模板
async function fetchRepoList () {
// 可以通过配置文件 拉取不同的仓库对应的用户下的文件
return axios.get('https://api.github.com/orgs/zhu-cli/repos');
}
1
2
3
4
5
6
2
3
4
5
6
- 根据tag标签获取指定版本
async fetchTag (repo) {
let tags = await wrapLoading(fetchTagList, 'waiting fetch tag', repo);
if (!tags) return;
tags = tags.map(item => item.name);
let { tag } = await Inquirer.prompt({
name: 'tag',
type: 'list',
choices: tags,
message: 'please choose a tag to create project'
});
return tag;
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 下载模板
async download (repo, tag) {
// 1.需要拼接处下载路径来
// zhu-cli/vue-template#1.0
let requestUrl = `zhu-cli/${repo}${tag ? '#' + tag : ''}`
// 2.把资源下载到某个路径上 (后续可以增加缓存功能, 应该下载到系统目录中,稍后可以在使用ejs handlerbar 去渲染模板 最后生成结果 在写入)
// 放到系统文件中 -> 模板 和用户的其他选择 =》 生成结果 放到当前目录下
await this.downloadGitRepo(requestUrl, path.resolve(process.cwd(), `${repo}@${tag}`));
return this.target;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 6、项目发布
将你自己的项目发布到npm
。
//nrm是一个包可以全局安装一下 npm i nrm -g
nrm use npm
//登录 输入你自己的用户名和密码
npm adduser
//发布
npm publish
1
2
3
4
5
6
2
3
4
5
6