(编辑:jimmy 日期: 2024/11/18 浏览:2)
脚手架是啥
从前我总觉得脚手架是个很高大上的东西,好像得牛叉: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,如下图:
这里要注意开头的 #!/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
运行一下,就能看到如下结果:
当然,你可能会觉得每次输入 node ./bin/xr
这个命令有点麻烦,没关系,我们可以在 package.json 里面写入已下内容:
// bin 用来指定每个命令所对应的可执行文件的位置 "bin": { "xr": "bin/xr" }
然后在根目录下执行 npm link
(就是把命令挂载到全局的意思),这样我们每次只要输入 xr,就可以直接运行了,so cool,就像下面这样:
是不是好像有点样子了呢:grin::grin::grin:,那就让我们继续完善下 bin 目录吧!ok,让我们在 bin 目录下再新建四个文件,分别对应上面的四个指令,然后分别处理四个指令要做的事情,如下图:
同样的,我们修改一下 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
重新把命令绑定到全局,就像下面这样:
最后顺便在根目录下新建一个 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 init webpack project-name
当中的 webpack
;模板地址要注意一下,像下面这样写就可以,这里以 github 为例:
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
看下效果:
xr-list
这个更简单了,两行代码搞定:
#!/usr/bin/env node const tplObj = require(`${__dirname}/../template`) console.log(tplObj)
是不是简单到爆:boom:。我们执行 xr list
看看效果:
因为刚才一添加一删除,所以目前没有模板,就输出 {}
。
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
:
现在我们就可以在左侧的目录中看到 test 项目了,如下图:
至此,一个小小的脚手架就做完了。:rose::rose::rose:此处应该有鲜花和掌声:clap::clap::clap:
发布到 npm
既然以上命令都执行成功了,那接下来我们就把它发布到 npm 上吧(写都写了,不能浪费:grimacing:)。
/node_modules
,意思就是发布的时候忽略 node_modules 文件夹,现在让我们回到项目根目录,执行 npm login
登入 npm 账号,再执行 npm publish
发布,就像下面这样:
没错,就是这样两个简单的命令,我们就发布成功啦,真是可喜可贺:beer::beer::beer:。大概过一分钟左右(反正挺快的),我们再去 npm 官网搜下 xr-cli,就可以看到自己的脚手架啦,哈哈哈哈,贼开心:+1::+1::+1:。
这里补充说明一点:根据规范,只有在发包的24小时内才允许撤销发布的包,所以为了不污染 npm 网站,如果只是测试的话就执行 npm unpublish --force
删除吧,毕竟我们都是有素质的人。
小试牛刀
别急,还没有结束:no_good:"text-align: center">
接下来进入到桌面,执行 xr init simple xr-test
,不一会就可以在桌面上看到自己的项目啦。
:six::six::six:,大赞无疆,大。。赞。。。无疆!!!
结语
上面的操作只要你熟悉了几遍之后,再去看看vue-cli 的源码结构,你就会有种拨开云雾见月明的感觉(它只是比我们这个脚手架完善很多很多很多而已:sob::sob::sob:)。
当然了,这只是渣渣版本。你可以往里面添加更多的东西,比如自动化构建和动态模板啊(其实动态模板是个大头),然后尝试写下更多更好的交互和功能,这样你就也能拥有一个属于自己的脚手架啦,心动不如行动,还等什么呢,不要998,只要有键盘,赶紧敲吧同志们,Let's go!:rainbow:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。