从前我总以为脚手架是个很高大上的东西,好像得牛叉🐂一点的人才写的出来,可望而不可即。其实并非由于困难使咱们放弃,而是由于放弃才显得困难(这是个好词好句🙈)。只要你肯花个一天半天的时间✊,也能写出属于你本身的脚手架。
早前脚手架这个词是从 vue-cli 这里认识的,咱们经过 npm install -g vue-cli
命令全局安装脚手架后, 再执行 vue init webpack project-name
就能初始化好一个本身的项目,真是尼玛的神奇😯。但你有没有想过为何咱们执行 vue init
这个命令就能有个本身的项目呢。今天,就让咱们一块儿来揭开庐山真面目吧!
等等✋,扯了一堆,你好像还没说下啥是脚手架?emmm... 它就是个工具,方便咱们新建项目用的,有了这个项目咱们就能直接开发了。其实咱们本能够用 git clone url
来新建(复制)项目,再 cuo 一点的方法就是复制粘贴整个文件夹,同样也能达到初始化的目的。脚手架的本质也是从远程下载一个模板来进行一个新项目。额。因此。。。有什么不一样呢?就高大上啊😧。固然不止于此啦,脚手架但是高级版的克隆,它主要是提供了交互式的命令让咱们能够动态的更改模板,而后用一句命令就能够一劳永逸了(固然仍是要维护的),这应该是最主要的区别吧,反正如今我是这么想的😢。
好了,本章的目的就是带领你们写一个简易版的脚手架 xr-cli(名字爱取啥取啥),目标是实现一个 xr init template-name project-name
这样的命令,废话少说,开始进入正题吧🚀🚀🚀。vue
源码地址:github.com/lgq627628/x…node
其实一个简易版的 xr-cli 的代码量并很少,因此这里咱们先来小小介绍一下其中要依赖的包,若是你用过这些工具能够跳过,没用过的请务必必定要瞟一眼。webpack
这是用来编写指令和处理命令行的,具体用法以下:git
const program = require("commander");
// 定义指令
program
.version('0.0.1')
.command('init', 'Generate a new project from a template')
.action(() => {
// 回调函数
})
// 解析命令行参数
program.parse(process.argv);
复制代码
回忆一下,咱们曾用过的 vue init
的命令就是这样声明的。github
这是个强大的交互式命令行工具,具体用法以下:web
const inquirer = require('inquirer');
inquirer
.prompt([
// 一些交互式的问题
])
.then(answers => {
// 回调函数,answers 就是用户输入的内容,是个对象
});
复制代码
想象一下咱们用 vue init webpack project-name
以后是否是会有几个交互问题,问你文件名啊、做者啊、描述啊、要不要用 eslint 啊等等之类的,就是用这个来写的。vue-cli
这是用来修改控制台输出内容样式的,好比颜色啊,具体用法以下:npm
const chalk = require('chalk');
console.log(chalk.green('success'));
console.log(chalk.red('error'));
复制代码
这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法以下:json
const ora = require('ora')
let spinner = ora('downloading template ...')
spinner.start()
复制代码
看名字很明显了,这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等,用法以下:bash
const download = require('download-git-repo')
download(repository, destination, options, callback)
复制代码
其中 repository 是远程仓库地址;destination 是存放下载的文件路径,也能够直接写文件名,默认就是当前目录;options 是一些选项,好比 { clone:boolean }
表示用 http download 仍是 git clone 的形式下载。
ok,有了上面的知识储备以后,咱们就正式开始撸了。
npm init
命令(你应该有安装 node 吧😂),一路回车,就会生成一个生成 package.json 文件,在 package.json 里面写入如下依赖并执行 npm install
安装,以下:"dependencies": {
"chalk": "^2.4.2",
"commander": "^2.19.0",
"download-git-repo": "^1.1.0",
"inquirer": "^6.2.2",
"ora": "^3.2.0"
}
复制代码
#!/usr/bin/env node
console.log('hello');
复制代码
这个文件就是咱们整个脚手架的入口文件,咱们用 node ./bin/xr
运行一下,就能在控制台打印出 hello,以下图:
#!/usr/bin/env node
这个语句必须加上,主要是为了让系统看到这一行的时候,会沿着该路径去查找 node 并执行,主要是为了兼容 Mac ,确保可执行。
当前,bin 目录下就只有一个文件,就是入口文件 xr。因此如今咱们先来编写这个文件,因为内容较少,咱们直接看代码:
#!/usr/bin/env node
const program = require('commander')
// 定义当前版本
// 定义使用方法
// 定义四个指令
program
.version(require('../package').version)
.usage('<command> [options]')
.command('add', 'add a new template')
.command('delete', 'delete a template')
.command('list', 'list all the templates')
.command('init', 'generate a new project from a template')
// 解析命令行参数
program.parse(process.argv)
复制代码
这个文件的主要做用就是定义指令,如今咱们用 node ./bin/xr
运行一下,就能看到以下结果:
node ./bin/xr
这个命令有点麻烦,不要紧,咱们能够在 package.json 里面写入已下内容:
// bin 用来指定每一个命令所对应的可执行文件的位置
"bin": {
"xr": "bin/xr"
}
复制代码
而后在根目录下执行 npm link
(就是把命令挂载到全局的意思),这样咱们每次只要输入 xr,就能够直接运行了,so cool,就像下面这样:
"bin": {
"xr": "bin/xr",
"xr-add": "bin/xr-add",
"xr-delete": "bin/xr-delete",
"xr-list": "bin/xr-list",
"xr-init": "bin/xr-init"
}
复制代码
而后执行 npm unlink
解绑全局命令,再执行 npm link
从新把命令绑定到全局,就像下面这样:
{}
。
好了,一切准备就绪,接下来就让咱们来写下具体的四个指令吧。
这个内容也是比较少,直接看代码:
#!/usr/bin/env node
// 交互式命令行
const inquirer = require('inquirer')
// 修改控制台字符串的样式
const chalk = require('chalk')
// node 内置文件模块
const fs = require('fs')
// 读取根目录下的 template.json
const tplObj = require(`${__dirname}/../template`)
// 自定义交互式命令行的问题及简单的校验
let question = [
{
name: "name",
type: 'input',
message: "请输入模板名称",
validate (val) {
if (val === '') {
return 'Name is required!'
} else if (tplObj[val]) {
return 'Template has already existed!'
} else {
return true
}
}
},
{
name: "url",
type: 'input',
message: "请输入模板地址",
validate (val) {
if (val === '') return 'The url is required!'
return true
}
}
]
inquirer
.prompt(question).then(answers => {
// answers 就是用户输入的内容,是个对象
let { name, url } = answers;
// 过滤 unicode 字符
tplObj[name] = url.replace(/[\u0000-\u0019]/g, '')
// 把模板信息写入 template.json 文件中
fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
if (err) console.log(err)
console.log('\n')
console.log(chalk.green('Added successfully!\n'))
console.log(chalk.grey('The latest template list is: \n'))
console.log(tplObj)
console.log('\n')
})
})
复制代码
这个文件主要目的就是添加模板并存储起来,上面的注释应该都写的挺清楚了。咱们执行 xr add
来看看效果:
vue init webpack project-name
当中的
webpack
;模板地址要注意一下,像下面这样写就能够,这里以 github 为例:
xr add
怎么对应到 xr-add 的:咱们前面在定义
program.command('add').action(() => {})
的时候没有写 action 这个回调函数,而当咱们执行
xr add
的时候,commander 会尝试在入口脚本的目录中搜索可执行文件,找到形如
program-command
(这里就是
xr-add
)的命令来执行,大概是这么个意思,下面的命令也是同样的道理。
若是你理解了上面的那个步骤,这步对你来讲应该也是洒洒水啦!上代码:
#!/usr/bin/env node
const inquirer = require('inquirer')
const chalk = require('chalk')
const fs = require('fs')
const tplObj = require(`${__dirname}/../template`)
let question = [
{
name: "name",
message: "请输入要删除的模板名称",
validate (val) {
if (val === '') {
return 'Name is required!'
} else if (!tplObj[val]) {
return 'Template does not exist!'
} else {
return true
}
}
}
]
inquirer
.prompt(question).then(answers => {
let { name } = answers;
delete tplObj[name]
// 更新 template.json 文件
fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(tplObj), 'utf-8', err => {
if (err) console.log(err)
console.log('\n')
console.log(chalk.green('Deleted successfully!\n'))
console.log(chalk.grey('The latest template list is: \n'))
console.log(tplObj)
console.log('\n')
})
})
复制代码
应该很好理解,就不过多解释了,咱们直接执行 xr delete
看下效果:
这个更简单了,两行代码搞定:
#!/usr/bin/env node
const tplObj = require(`${__dirname}/../template`)
console.log(tplObj)
复制代码
是否是简单到爆💥。咱们执行 xr list
看看效果:
{}
。
这应该是最主要(但不难)的一步了,毕竟咱们写到如今尚未经过命令初始化过一个项目呢😭。因此这步的重点就是执行 download
方法,并传入相应参数,具体看代码:
#!/usr/bin/env node
const program = require('commander')
const chalk = require('chalk')
const ora = require('ora')
const download = require('download-git-repo')
const tplObj = require(`${__dirname}/../template`)
program
.usage('<template-name> [project-name]')
program.parse(process.argv)
// 当没有输入参数的时候给个提示
if (program.args.length < 1) return program.help()
// 比如 vue init webpack project-name 的命令同样,第一个参数是 webpack,第二个参数是 project-name
let templateName = program.args[0]
let projectName = program.args[1]
// 小小校验一下参数
if (!tplObj[templateName]) {
console.log(chalk.red('\n Template does not exit! \n '))
return
}
if (!projectName) {
console.log(chalk.red('\n Project should not be empty! \n '))
return
}
url = tplObj[templateName]
console.log(chalk.white('\n Start generating... \n'))
// 出现加载图标
const spinner = ora("Downloading...");
spinner.start();
// 执行下载方法并传入参数
download (
url,
projectName,
err => {
if (err) {
spinner.fail();
console.log(chalk.red(`Generation failed. ${err}`))
return
}
// 结束加载图标
spinner.succeed();
console.log(chalk.green('\n Generation completed!'))
console.log('\n To get started')
console.log(`\n cd ${projectName} \n`)
}
)
复制代码
ok,咱们执行一下 xr init simple test
,记得先执行一下 xr add
:
既然以上命令都执行成功了,那接下来咱们就把它发布到 npm 上吧(写都写了,不能浪费😬)。
/node_modules
,意思就是发布的时候忽略 node_modules 文件夹,npm login
登入 npm 帐号,再执行 npm publish
发布,就像下面这样:
这里补充说明一点:根据规范,只有在发包的24小时内才容许撤销发布的包,因此为了避免污染 npm 网站,若是只是测试的话就执行 npm unpublish --force
删除吧,毕竟咱们都是有素质的人。
别急,尚未结束🙅♀️。发都发出去了,怎么也得验证一波撒。嗯,说的有道理,没法反驳,那就赶忙验收吧!这里咱们记得先用 npm unlink
解绑一下命令,否则会相互影响。下面咱们打开终端,输入 npm i xr-cli -g
全局安装一下脚手架,而后执行 xr
,若是出现下图中的模样就说明已经安装成功了。
xr init simple xr-test
,不一会就能够在桌面上看到本身的项目啦。
上面的操做只要你熟悉了几遍以后,再去看看 vue-cli 的源码结构,你就会有种拨开云雾见月明的感受(它只是比咱们这个脚手架完善不少不少不少而已😭😭😭)。 固然了,这只是渣渣版本。你能够往里面添加更多的东西,好比自动化构建和动态模板啊(其实动态模板是个大头),而后尝试写下更多更好的交互和功能,这样你就也能拥有一个属于本身的脚手架啦,心动不如行动,还等什么呢,不要998,只要有键盘,赶忙敲吧同志们,Let's go!🌈