前言
从前我总觉得脚手架是个很高大上的东西,只有那种牛叉的大佬才写的出来,可望而不可即。其实并不是因为困难使我们放弃,而是因为放弃才显得困难。只要你肯花个一天半天的时间去研究摸索,也能写出属于你自己的脚手架。
脚手架这个词是相信大家都不陌生,或多或少都有了解或者使用过,诸如vue的vue-cli,reat中的create-react-app等等。用脚手架来开发,给我们的感觉就是便捷,迅速。但是我们需要去了解脚手架内置的一些东西,否则的话修改起来是相对不灵活的。而且现成的脚手架是不一定能够满足我们日常的工作环境需求的,所以有时候我们不得不自己从头去搭建一个项目的框架。如果一个框架要给多个项目使用,这当中就免不了很多的copy,git clone ...等一系列的操作。脚手架就是用来帮我们省掉这些操作的,它本质也是从远程下载一个模板来进行一个新项目,可以把它认为是一种高级克隆,提供一些交互式的命令让我们去动态的修改我们的模板项目。
本篇文章主要是基于之前学习工作中自己搭建的框架,搭建的一个脚手架。这当中包含了所用到工具,以及详细的代码步骤。学习过程中也查阅了很多资料借鉴了很多大佬,写的详细方便以后自己进行review,以及一些想要搭建脚手架的伙伴一起学习。其实真正实现一个基础功能的脚手架,代码量是不多的。
用到的工具
开发脚手架所需要用到的一些工具,如果之前有使用过的话可以略过,如果没用过的话建议做下了解,也欢迎推荐更好用的工具哈。
commander 命令行开发工具
commander是一个提供用户命令行输入和参数解析的强大功能,是前端开发node cli 必备技能。这边着重介绍一些基础的用法,不做深入拓展,有需要的可以阅读相关的库文档。
-
version 定义脚手架的版本
-
option 定义commander的选项options,参数解析:
2.1 自定义标志<必须>:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用
<>包含,后者用[]包含2.2 选项描述<省略不报错>:在使用 --help 命令时显示标志描述
2.3 默认值<可省略>
var program = require('commander');
program
.version('0.0.1', '-v, --version')
.option('-i --init <name>[option]', 'init a project', 'myFirstProject')
.parse(process.argv); // 解析命令行参数
-
command 添加命令名称,参数解析:
3.1 命令名称<必须>:命令后面可跟用
<>或[]包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入...标志;在命令后面传入的参数会被传入到 action 的回调函数以及 program.args 数组中3.2 命令描述<可省略>:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错
3.3 配置选项<可省略>:可配置noHelp、isDefault等
-
action 定义命令的回调函数
-
alias 定义命令的别名
-
description 定义命令的描述
var program = require('commander');
program
.version('0.0.1', '-v, --version')
.command('init <name>[option]')
.description('初始化项目')
.action((name)=>{
console.log(name)
})
.parse(process.argv); // 解析命令行参数
download-git-repo 下载模版仓库
这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等。
- 安装
npm i download-git-repo - 使用
const download = require('download-git-repo')
download(repository, destination, options, callback)
// repository 是远程仓库地址
// destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录
//options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载
//callback 下载之后回调函数,接受一个参数
ora 命令行加载进度标识
这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法如下:
const ora = require('ora')
let spinner = ora('downloading template ...')
// spinner.start()
// spinner.fail('项目模板下载失败');
// spinner.succeed('项目模板下载成功');
...
inquirer 命令行交互工具
这是个强大的交互式命令行工具,如果你想要和用户进行交互,那么你需要使用它,具体用法如下:
const inquirer = require('inquirer');
inquirer.prompt([
// 一些交互式的问题
{
type:'input', // 提问的类型
name:'author', // 存储在返回对象的 key
message:'请输入作者', // 问题的描述;
default:'' //默认值
...
}
])
.then(answers => {
// 回调函数,answers 就是用户输入的内容,是个对象
});
还有很多的配置项,和交互类型input, confirm, list, rawlist, expand, checkbox, password, editor等等,有兴趣的同学可以自行了解尝试.
chalk 命令行输出字符样式
这是用来修改控制台输出内容样式的,比如颜色,加粗,字体背景色等等,具体用法如下:
const chalk = require('chalk');
console.log(chalk.green('success'));
...
起步
了解了以上的知识之后我们就可以正式开始搭建脚手架了。
- 新建文件夹,
npm init创建项目package.json文件 - 修改
package.json文件的bin字段,设置脚手架的入口文件
"bin": {
"me-cli": "./index.js"
},
- 新建入口文件
index.js,在入口文件顶部添加代码#! /usr/bin/env node,声明该命令行脚本是node.js写的 - 下载所需要的工具依赖包,因为是之前查阅资料决定使用的这些工具,这边不做解释,直接下载再说
npm i commander download-git-repo inquirer ora chalk
设计指令
这边我暂且吧脚手架命令定义为me-cli,其实叫啥名字问题不大哈!
me-cli -v / me-cli --version` //查看脚手架的版本号
me-cli -h / me-cli --help` //查看脚手架的配置项和具有的功能
me-cli init 模板名 项目名 //脚手架初始化模板项目
me-cli list //查看脚手架可用的模板列表
...
本次实现最基础的功能,后续可新增.
入口文件代码如下:
#! /usr/bin/env node
const program = require('commander'); //命令行开发工具
program
.version('0.0.1', '-v, --version')
.command('list')
.description('查看所有可用的项目模板')
program
.command('init <template> <project>')
.description('初始化项目模板工程')
安装和调试
- 当我们已经上传到npm仓库的时候,我们可以通过
npm i -g me-cli进行安装 - 我们开发本地调试的时候可以在项目目录执行
npm link创建一个软连接,然后也可以正常使用指令了。但是别忘了最后要冲npm库下载之前需要执行npm unlink进行解绑,以避免指令冲突。 npm link之后,我们可以执行me-cli -v和me-cli -h进行查看,结果如下图:
具体指令编写
me-cli list
具体代码如下:
#! /usr/bin/env node
const program = require('commander'); //命令行开发工具
program
.version('0.0.1', '-v, --version')
.command('list')
.description('查看所有可用的项目模板')
.action(()=>{
console.log(`
reactCli react基础模板
reactReduxCli react+redux模板
reactMobxCli react+mobx模板
reactTsCli react+typescript模板
`)
})
program
.command('init <template> <project>')
.description('初始化项目模板工程')
program.parse(process.argv); // 解析命令行参数
然后我们可以运行看效果
me-cli init
这部分是我们主要部分了,实现从远端拉取项目模版功能。
- 首先我们需要定义一个模版对象
// 可用模板
const templates = {
'reactCli': {
url: 'https://github.com/1149308443/react',
downloadUrl: '1149308443/react#master',
description: 'me-cli脚手架react'
},
...
}
注意这边有个坑,对象里面的downloadUrl才是我们真正用来下载的地址,**#**后面的代表分支,是不需要携带前缀的。至于其他两项是我们用来自己更直观的看的,没有什么特殊作用。
- 首先我们定义了
<template> <project>模版名称和项目名称,这两个必传参数,在回调中打印,并判断模版名称必须在模版列表内部。
program
.command('init <template> <project>')
.description('初始化项目模板工程')
.action(( templateName, projectName)=>{
console.log(templateName, projectName)
if(!(templateName in templates)){
console.log('下载模板出错,请输入正确的模板');
return;
}
let { downloadUrl } = templates[templateName];
console.log(downloadUrl)
})
此时做正确的输入就可以拿到我们模版的下载地址和项目名称。
- 引入
download-git-repo进行模版的下载,配置如下:
const download = require('download-git-repo'); // 模板仓库下载工具
...
download(downloadUrl, projectName, err => {
if(err){
console.log('下载模板出错');
}else{
console.log('下载模板成功');
}
})
...
到这边其实我们已经可以正常的下载一个模版了,这还并没有结束。
- 引入
ora增加一个下载的状态标识。 - 引入
chalk为命令行的输出增加样式。 - 引入
inquirer,当模板下载成功之后,通过与用户的修改获取用户的输入,以此进行模板项目内容的修改。 - 引入
fs模块根据用户的输入信息修改模板项目的package.json
至此,我们的me-cli init 模板名 项目名指令就完成了,配置如下:
program
.command('init <template> <project>')
.description('初始化项目模板工程')
.action(( templateName, projectName)=>{
const spinner = ora('正在下载模板...').start();
if(!(templateName in templates)){
spinner.fail('下载模板出错,请输入正确的模板');
return;
}
let { downloadUrl } = templates[templateName];
//第一个参数是github仓库地址,第二个参数是创建的项目目录名,第三个参数是clone(是clone还是正常下载,可省略)
download(downloadUrl, projectName, err => {
console.log(downloadUrl, projectName,err);
if(err){
spinner.fail('下载模板出错');
}else{
spinner.succeed('下载模板成功');
//命令行交互
inquirer.prompt([
{
type:'input',
name:'name',
message: '请输入项目名称',
default: projectName
},
{
type: 'input',
name: 'description',
message: '请输入项目简介',
default: ''
},
{
type: 'input',
name: 'author',
message: '请输入作者名称',
default: ''
}
]).then((answers)=>{
//根据命令行答询结果修改package.json文件
fs.readFile(`${projectName}/package.json`, 'utf8', function(err, data){
if(err){
console.log(chalk.red('读取配置失败'));
return;
}
let package = JSON.parse(data)
Object.assign(package,answers);
package = JSON.stringify(package,null, 4);
fs.writeFile(`${projectName}/package.json`, package, 'utf8', (err) => {
if(err){
console.log(chalk.red('修改配置失败'));
return;
}
console.log(chalk.green('项目初始化成功'))
});
})
})
}
})
})
发布npm
我们的基础功能已经实现了,接下来就是上传到npm仓库了。
- 首先我们需要注册一个npm的账号
- 在根目录下新建
.npmignore文件,效果类似于.gitignore,是我们忽略要发到npm上的内容。 - 然后回到我们的根目录,执行
npm login登陆自己的npm账号,之后再执行npm publish进行发布。
这边需要注意,发布npm仓库的时候,仓库的源必须是npm源,如果不是要先设置回去,发布之后再重新设置回来。
npm config get registry // 执行此命令查看仓库源
npm config set registry=http://registry.npmjs.org // 执行此命令设置仓库源
- 如果只是作为测试,想从
npm仓库删除,可以执行npm unpublish --force从仓库中删除。好像只有在发包的24小时内才允许撤销发布的包,而且24小时内是不可以重复发包的... - 发布完了我们可以直接从
npm库拉取,全局安装,执行npm unlink解绑本地调试,执行npm i -g me-cli全局安装,然后就可以正常使用我们的脚手架命令了,这边注意在解绑的时候最好加上模块名字
结束语
经过实践,会发现实现一个基础功能的脚手架真的不难,难的是要把它做精细。还有很多的功能要慢慢去发掘,深究。才能进一步去完善我们的脚手架,这有受限于本人的技术水平,有待于后期的学习完善。
本篇学习笔记,希望各位大佬写的不好的地方可以指正,有什么好的建议可以在评论区提出,也希望各位小伙伴一起学习,一起进步。。。