仿vue-cli搭建属于自己的脚手架的方法步骤


Posted in Javascript onApril 17, 2019

脚手架是啥

从前我总觉得脚手架是个很高大上的东西,好像得牛叉:ox:一点的人才写的出来,可望而不可即。其实并不是因为困难使我们放弃,而是因为放弃才显得困难(这是个好词好句:see_no_evil:)。只要你肯花个一天半天的时间:fist:,也能写出属于你自己的脚手架。

早前脚手架这个词是从 vue-cli 这里认识的,我们通过 npm install -g vue-cli 命令全局安装脚手架后, 再执行 vue init webpack project-name 就能初始化好一个自己的项目,真是尼玛的神奇:hushed:。但你有没有想过为什么我们执行 vue init 这个命令就能有个自己的项目呢。今天,就让我们一起来揭开庐山真面目吧!

等等:hand:,扯了一堆,你好像还没说下啥是脚手架?emmm... 它就是个工具,方便我们新建项目用的,有了这个项目我们就能直接开发了。其实我们本可以用 git clone url 来新建(复制)项目,再 cuo 一点的方法就是复制粘贴整个文件夹,一样也能达到初始化的目的。脚手架的本质也是从远程下载一个模板来进行一个新项目。额。所以。。。有什么不同呢?就高大上啊:anguished:。当然不止于此啦,脚手架可是高级版的克隆,它主要是提供了交互式的命令让我们可以动态的更改模板,然后用一句命令就可以一劳永逸了(当然还是要维护的),这应该是最主要的区别吧,反正现在我是这么想的:cry:。

好了,本章的目的就是带领大家写一个简易版的脚手架 xr-cli(名字爱取啥取啥),目标是实现一个 xr init template-name project-name 这样的命令,废话少说,开始进入正题吧:rocket::rocket::rocket:。

源码地址: https://github.com/lgq627628/xr-cli

前置知识

其实一个简易版的 xr-cli 的代码量并不多,所以这里我们先来小小介绍一下其中要依赖的包,如果你用过这些工具可以跳过,没用过的请务必一定要瞟一眼。

commander

这是用来编写指令和处理命令行的,具体用法如下:

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 的命令就是这样声明的。

inquirer

这是个强大的交互式命令行工具,具体用法如下:

const inquirer = require('inquirer');
inquirer
 .prompt([
 // 一些交互式的问题
 ])
 .then(answers => {
 // 回调函数,answers 就是用户输入的内容,是个对象
 });

想象一下我们用 vue init webpack project-name 之后是不是会有几个交互问题,问你文件名啊、作者啊、描述啊、要不要用 eslint 啊等等之类的,就是用这个来写的。

chalk

这是用来修改控制台输出内容样式的,比如颜色啊,具体用法如下:

const chalk = require('chalk');
console.log(chalk.green('success'));
console.log(chalk.red('error'));

ora

这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法如下:

const ora = require('ora')
let spinner = ora('downloading template ...')
spinner.start()

download-git-repo

看名字很明显了,这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等,用法如下:

const download = require('download-git-repo')
download(repository, destination, options, callback)

其中 repository 是远程仓库地址;destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录;options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载。

目录搭建

ok,有了上面的知识储备之后,我们就正式开始撸了。

首先我们要创建一个文件夹,并取名叫 xr-cli;

在该目录下执行 npm init 命令(你应该有安装 node 吧:joy:),一路回车,就会生成一个生成 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"
}

新建一个 bin 文件夹,并在 bin 目录下新建一个无后缀名的 xr 文件,并写上:

#!/usr/bin/env node
console.log('hello');

这个文件就是我们整个脚手架的入口文件,我们用 node ./bin/xr 运行一下,就能在控制台打印出 hello,如下图:

仿vue-cli搭建属于自己的脚手架的方法步骤

这里要注意开头的 #!/usr/bin/env node

这个语句必须加上,主要是为了让系统看到这一行的时候,会沿着该路径去查找 node 并执行,主要是为了兼容 Mac ,确保可执行。

bin 目录初始化

当前,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 运行一下,就能看到如下结果:

仿vue-cli搭建属于自己的脚手架的方法步骤

当然,你可能会觉得每次输入 node ./bin/xr

这个命令有点麻烦,没关系,我们可以在 package.json 里面写入已下内容:

// bin 用来指定每个命令所对应的可执行文件的位置
"bin": {
 "xr": "bin/xr"
}

然后在根目录下执行 npm link (就是把命令挂载到全局的意思),这样我们每次只要输入 xr,就可以直接运行了,so cool,就像下面这样:

仿vue-cli搭建属于自己的脚手架的方法步骤

是不是好像有点样子了呢:grin::grin::grin:,那就让我们继续完善下 bin 目录吧!ok,让我们在 bin 目录下再新建四个文件,分别对应上面的四个指令,然后分别处理四个指令要做的事情,如下图:

仿vue-cli搭建属于自己的脚手架的方法步骤

同样的,我们修改一下 package.json 里面的 bin 内容,如下:

"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 重新把命令绑定到全局,就像下面这样:

仿vue-cli搭建属于自己的脚手架的方法步骤

最后顺便在根目录下新建一个 template.json 文件,里面的内容就是一个 {}

编写具体指令

好了,一切准备就绪,接下来就让我们来写下具体的四个指令吧。

xr-add

这个内容也是比较少,直接看代码:

#!/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-cli搭建属于自己的脚手架的方法步骤

这里的模板名称(自己随便取)相当于 vue init webpack project-name 当中的 webpack

;模板地址要注意一下,像下面这样写就可以,这里以 github 为例:

仿vue-cli搭建属于自己的脚手架的方法步骤

xr-delete

如果你理解了上面的那个步骤,这步对你来说应该也是洒洒水啦!上代码:

#!/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 看下效果:

仿vue-cli搭建属于自己的脚手架的方法步骤

xr-list

这个更简单了,两行代码搞定:

#!/usr/bin/env node

const tplObj = require(`${__dirname}/../template`)
console.log(tplObj)

是不是简单到爆:boom:。我们执行 xr list 看看效果:

仿vue-cli搭建属于自己的脚手架的方法步骤

因为刚才一添加一删除,所以目前没有模板,就输出 {}

xr-init

这应该是最主要(但不难)的一步了,毕竟我们写到现在还没有通过命令初始化过一个项目呢:sob:。所以这步的重点就是执行 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

仿vue-cli搭建属于自己的脚手架的方法步骤

现在我们就可以在左侧的目录中看到 test 项目了,如下图:

仿vue-cli搭建属于自己的脚手架的方法步骤

至此,一个小小的脚手架就做完了。:rose::rose::rose:此处应该有鲜花和掌声:clap::clap::clap:

发布到 npm

既然以上命令都执行成功了,那接下来我们就把它发布到 npm 上吧(写都写了,不能浪费:grimacing:)。

  • 删除 test 文件夹,它就本地测试用的,用完就抛弃它(当然做人不能这样)
  • 在根目录下新建 README.md 文件,随便写点使用说明,假装正经一下
  • 在根目录下新建 .npmignore 文件,并写入 /node_modules ,意思就是发布的时候忽略 node_modules 文件夹,
  • 去 npm 官网注册个账号(很简单的),同时搜索一下 xr-cli 这个名字,看看有没有人用,有的话就换一个罗

仿vue-cli搭建属于自己的脚手架的方法步骤

现在让我们回到项目根目录,执行 npm login 登入 npm 账号,再执行 npm publish 发布,就像下面这样:

仿vue-cli搭建属于自己的脚手架的方法步骤

没错,就是这样两个简单的命令,我们就发布成功啦,真是可喜可贺:beer::beer::beer:。大概过一分钟左右(反正挺快的),我们再去 npm 官网搜下 xr-cli,就可以看到自己的脚手架啦,哈哈哈哈,贼开心:+1::+1::+1:。

仿vue-cli搭建属于自己的脚手架的方法步骤

这里补充说明一点:根据规范,只有在发包的24小时内才允许撤销发布的包,所以为了不污染 npm 网站,如果只是测试的话就执行 npm unpublish --force 删除吧,毕竟我们都是有素质的人。

小试牛刀

别急,还没有结束:no_good:‍♀️。发都发出去了,怎么也得验证一波撒。嗯,说的有道理,无法反驳,那就赶紧验收吧!这里我们记得先用 npm unlink 解绑一下命令,不然会相互影响。下面我们打开终端,输入 npm i xr-cli -g 全局安装一下脚手架,然后执行 xr ,如果出现下图中的模样就说明已经安装成功了。

仿vue-cli搭建属于自己的脚手架的方法步骤

接下来进入到桌面,执行 xr init simple xr-test,不一会就可以在桌面上看到自己的项目啦。

仿vue-cli搭建属于自己的脚手架的方法步骤仿vue-cli搭建属于自己的脚手架的方法步骤

:six::six::six:,大赞无疆,大。。赞。。。无疆!!!

结语

上面的操作只要你熟悉了几遍之后,再去看看vue-cli 的源码结构,你就会有种拨开云雾见月明的感觉(它只是比我们这个脚手架完善很多很多很多而已:sob::sob::sob:)。

当然了,这只是渣渣版本。你可以往里面添加更多的东西,比如自动化构建和动态模板啊(其实动态模板是个大头),然后尝试写下更多更好的交互和功能,这样你就也能拥有一个属于自己的脚手架啦,心动不如行动,还等什么呢,不要998,只要有键盘,赶紧敲吧同志们,Let's go!:rainbow:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript设计模式开发中组合模式的使用教程
May 18 Javascript
微信小程序  简单实例(阅读器)的实例开发
Sep 29 Javascript
js 原型对象和原型链理解
Feb 09 Javascript
单击按钮发送验证码,出现倒计时的简单实例
Mar 17 Javascript
vue中如何实现变量和字符串拼接
Jun 19 Javascript
Vue学习笔记进阶篇之单元素过度
Jul 19 Javascript
深入浅析Vue全局组件与局部组件的区别
Jun 15 Javascript
Vue.directive使用注意(小结)
Aug 31 Javascript
详解三种方式解决vue中v-html元素中标签样式
Nov 22 Javascript
详解React项目中碰到的IE问题
Mar 14 Javascript
使用mixins实现elementUI表单全局验证的解决方法
Apr 02 Javascript
微信小程序iBeacon测距及稳定程序的实现解析
Jul 31 Javascript
一篇文章,教你学会Vue CLI 插件开发
Apr 17 #Javascript
ES6知识点整理之函数数组参数的默认值及其解构应用示例
Apr 17 #Javascript
mpvue性能优化实战技巧(小结)
Apr 17 #Javascript
node.js监听文件变化的实现方法
Apr 17 #Javascript
vue中格式化时间过滤器代码实例
Apr 17 #Javascript
postman自定义函数实现 时间函数的思路详解
Apr 17 #Javascript
vue指令之表单控件绑定v-model v-model与v-bind结合使用
Apr 17 #Javascript
You might like
PHP模板引擎Smarty的缓存使用总结
2014/04/24 PHP
Zend Framework教程之模型Model基本规则和使用方法
2016/03/04 PHP
Javascript Cookie读写删除操作的函数
2010/03/02 Javascript
javascript中的undefined 与 null 的区别  补充篇
2010/03/17 Javascript
ASP.NET jQuery 实例6 (实现CheckBoxList成员全选或全取消)
2012/01/13 Javascript
Jquery倒数计时按钮setTimeout的实例代码
2013/07/04 Javascript
浅谈JavaScript Date日期和时间对象
2014/12/29 Javascript
JavaScript中实现sprintf、printf函数
2015/01/27 Javascript
AngularJS通过$sce输出html的方法
2016/09/22 Javascript
jQuery实现表格元素动态创建功能
2017/01/09 Javascript
Vue非父子组件通信详解
2017/06/12 Javascript
jQuery使用动画队列自定义动画操作示例
2018/06/16 jQuery
JS实现鼠标拖拽盒子移动及右键点击盒子消失效果示例
2019/01/29 Javascript
js实现拾色器插件(ColorPicker)
2020/05/21 Javascript
[04:10]2018年度CS GO玩家最喜爱的主播-完美盛典
2018/12/16 DOTA
Python实现的一个简单LRU cache
2014/09/26 Python
python分析apache访问日志脚本分享
2015/02/26 Python
教你用Type Hint提高Python程序开发效率
2016/08/08 Python
pygame实现五子棋游戏
2019/10/29 Python
Python实现对adb命令封装
2020/03/06 Python
Selenium常见异常解析及解决方案示范
2020/04/10 Python
css3实现动画的三种方式
2020/08/24 HTML / CSS
整理HTML5中表单的常用属性及新属性
2016/02/19 HTML / CSS
韩国休闲女装品牌网站:ANAIS
2016/08/24 全球购物
红色连衣裙精品店:Red Dress Boutique
2018/08/11 全球购物
韩国乐天网上商城:Lotte iMall
2021/02/03 全球购物
初中三年学生的学习自我评价
2013/11/13 职场文书
党员培训思想汇报
2014/01/07 职场文书
开会迟到检讨书
2014/02/03 职场文书
抗洪救灾先进集体事迹材料
2014/05/26 职场文书
交通安全标语
2014/06/06 职场文书
图书室标语
2014/06/21 职场文书
基于go interface{}==nil 的几种坑及原理分析
2021/04/24 Golang
Python中的np.argmin()和np.argmax()函数用法
2021/06/02 Python
浅谈Redis位图(Bitmap)及Redis二进制中的问题
2021/07/15 Redis
Java中的随机数Random
2022/03/17 Java/Android