记得一开始接触脚手架时,就以为这是个很神奇的东西。不用复杂的配置,不用去查文档,拿@vue/cli
为例,直接一行vue create my-project
命令,按一下回车,ok。接下来就是愉快的等待时间,看着项目快速的自动生成出来,与以前一项一项手动配置相比,效率不是快了一星半点。这简直就是解放生产力的工具。项目与项目之间有不少配置和结构都是能够复用的,并且项目的类型是有限的,能够枚举出来的,因此再搭配上相应的模板的话,就能够快速开始一个新项目了。vue
目前采用最多而且耦合度最低的一种思路是脚手架工具与模板项目分离,即模板项目放远程仓库或者什么地方,脚手架工具将模板拉取到本地,再根据相关配置命令生成相应的项目。一句话就解释完了,好像没什么难度。这一篇主要分析脚手架工具,至于模板就是另外一个话题了,下一篇《Re从零开始的路由模块编写》会讲到。接下来看看这个工具是实现的细节。node
咱们最终使用时,应该要如同@vue/cli
同样,能在命令行中全局使用。这就不得不提到npm
配置文件package.js
中的bin
字段了。git
//就像这样去配置,myCli是自定义的命令名,'./bin/myCli.js'则是引导文件
{
"bin": {
"myCli": "./bin/myCli.js"
}
}
复制代码
在npm install
时npm会分析这个字段github
node_modules
外,还会在npm安装目录的bin文件下建立一个软链,链向node_modules/my-cli/bin/myCli.js
,此时在PATH
中也会加入相关的路径映射,在使用myCli create myProject
时,至关于在执行node /npm安装路径/bin/myCli create myProject
。node_modules/.bin
下建立软链,链向node_modules/my-cli/bin/myCli.js
,在npm run
时,会将相关路径映射加入到PATH
中。这样就能作到在命令行中使用这个全局命令了web
先来看看项目结构sql
结构并不复杂npm
sc.js
就是上面介绍的引导文件cli.js
将匹配到的命令分发到不一样的处理文件(cmd
目录下一堆增删改查处理程序)format.js
工具函数templates.json
保存相关配置记录这一步须要处理的功能点有几个json
仔细一想,从命令行获的命令和分步处理输入的命令彷佛不是那么容易完成,但问题不大。后端
站在巨人的肩膀上,在完成前3步以前,先介绍一个很关键的插件。prompts
这个包对命令行相关处理进行了很好的封装,配置简单,支持分步输入。安全
// 简单的配置便可得到相关参数
(async () => {
const preOption = [{
type: 'text',
name: 'project',
message: '项目名称?',
validate: value => value ? true : '请输入内容'
}, {
type: 'text',
name: 'url',
message: '项目仓库地址?',
validate: value => value ? true : '请输入内容'
}]
const preResponse = await prompts(preOption);
})()
复制代码
// 还能设置命令提示信息
program
.version(packageInfo.version)
program
.command('init')
.description('初始化一个项目')
.alias('i')
.action(() => {
require('./cmd/init').init();
});
program
.command('add')
.description('新建一个项目')
.alias('a')
.action(() => {
require('./cmd/add').add();
});
复制代码
这里最核心的功能就是自动初始化项目了,除此以外还有对项目列表及配置的增删改查,毕竟咱们的模板不止一个,须要维护的一个项目列表。下面看一下几段核心的代码。
(async () => {
const preOption = [{
type: 'text',
name: 'project',
message: '项目名称?',
validate: value => value ? true : '请输入内容'
}]
format.table(templates)
const preResponse = await prompts(preOption);
if (!Object.keys(preResponse).length) {
console.log(chalk.yellow('程序中断'))
process.exit();
}
const {
project
} = preResponse
if (!templates[project] || !project) {
console.log(chalk.red('模板名不为空或模板不存在'))
process.exit()
}
const {
url,
branch
} = templates[project]
spinner.start('正在初始化项目');
exec(`git clone ${url} ${project} && cd ${project} && git checkout ${branch}`, (err) => {
// 删除 git 文件
exec('cd ' + project + ' && rm -rf .git', (err, out) => {
spinner.succeed('模板拉取成功')
process.exit()
});
});
})()
复制代码
(async () => {
const option = [{
type: 'text',
name: 'project',
message: '项目名称?',
validate: value => value ? true : '请输入内容'
}, {
type: 'text',
name: 'url',
message: '项目仓库地址?',
validate: value => value ? true : '请输入内容'
}, {
type: 'text',
name: 'branch',
message: '项目分支?',
initial: 'master',
validate: value => value ? true : '请输入内容'
}, {
type: 'text',
name: 'des',
message: '项目描述?',
validate: value => value ? true : '请输入内容'
}]
const response = await prompts(option);
if (!Object.keys(response).length) {
console.log(chalk.yellow('程序中断'))
process.exit();
}
const {
project
} = response
if (templates[project] || !project) {
console.log(chalk.red('模板名不为空或模板已存在'))
process.exit()
}
templates[project] = response
fs.writeFile(__dirname + '/../../templates.json', JSON.stringify(templates), 'utf-8', (err) => {
console.log(chalk.green('添加成功'))
process.exit();
});
})();
复制代码
(async () => {
const option = [{
type: 'text',
name: 'project',
message: '项目名称?',
validate: value => value ? true : '请输入内容'
}]
const response = await prompts(option);
if (!Object.keys(response).length) {
console.log(chalk.yellow('程序中断'))
process.exit();
}
const {
project
} = response
if (!templates[project] || !project) {
console.log(chalk.red('模板名不为空或模板不存在'))
process.exit()
}
if (project in templates) {
delete templates[project]
fs.writeFile(__dirname + '/../../templates.json', JSON.stringify(templates), 'utf-8', (err) => {
console.log(chalk.green('删除成功'))
format.table(templates)
process.exit();
});
} else {
console.log(chalk.red('没有该模板'))
process.exit();
}
})()
复制代码
例如像更新功能就同上面的差很少,有些细节能够查看完整代码。固然还有不少功能没有实现,这只是个简易版本,帮助理解像相似@vue/cli
这样的脚手架工具是怎么作的。
不会吧,不会吧,2020年了竟然有人不会在npm上发布项目,直接npm publish
就行了,什么竟然报错?详情可看《Re从零开始的组件库构建与发布流程》
仓库地址 https://github.com/GoldWorker/slucky-cli
其实这样的工具仍是很简单的,实现上借用prompts
这个包过程跳过了相对繁琐的处理命令行的部分。固然若是想要像真正的@vue/cli
那样的功能,只须要继续添加相关的命令与处理过程便可。