(如下阅读将花费10分钟)javascript
平常开发中,咱们都只专一在业务上的开发,拿起一套开箱即用的模板项目就直接开搞了,不知道你们有没有思考过,平时咱们使用的脚手架里面到底作了什么,而且若是是本身来搭一套脚手架,应该怎么去搭呢? css
在本博客中,将记录做者搭建脚手架的过程,总体将分为两个部分,第一部分是cli的搭建,第二部分是模板项目的搭建;java
在搭建脚手架cli以前,咱们首先思考一下,一个cli,须要什么能力呢?答案是初始化能力,那么初始化的功能须要怎样去实现呢,这里,咱们先梳理一下思路。node
咱们在使用其余cli时,会发现,它们有问询的功能,好比询问项目名称,项目描述等;而后还能够选择模板项目将要使用什么css预处理器等的问题;因此,cli首先就要具有问询功能,用以获取定制化信息;
问询结束后,咱们的cli会得到即将建立的项目的基本信息,接下来咱们就须要以一个模板项目做为模板去建立,模板项目将会在第二部分讲解,这里一笔带过~;OK,既然有了模板项目,cli就须要下载这个项目,而后复制这个项目,同时将前面问询所得的定制化信息写入项目配置中,因此cli还须要有下载和复制,写入模板功能;
到此,一个模板项目基本已经建立成功了,那后面咱们还但愿脚手架能够帮忙进行git初始化以及安装依赖的功能,因此最终,还须要添加git初始化和安装依赖的功能;
总结一下,实现一个cli的初始化功能咱们须要有以下能力:
问询 ==> 下载模板 ==> 复制,写入模板 ==> git初始化 ==> 安装依赖git
好了,为了实现上述思路,cli将会引入以下依赖去实现对应的功能:github
const program = require('commander'); // commander负责读取命令 const inquirer = require('inquirer'); // inquirer负责问询 const download = require('download-git-repo'); // download-git-repo负责下载对应模板项目的git仓库 const fse = require('fs-extra'); // fs-extra负责文件的复制 const memFs = require('mem-fs'); const editor = require('mem-fs-editor'); // mem-fs-editor负责模板的复制以及嵌入模板字符串,它须要依赖mem-fs const { exec } = require('child_process'); // child_process负责执行命令行
固然除了上述必须的依赖外,为了更好的交互体验,还引入了以下依赖:shell
const chalk = require('chalk'); // 改变命令行输出样式 const ora = require('ora'); // 一个优雅地命令行交互spinner
至此,准备的工做已经完毕,下面,就开始一步步来编写cli了npm
万事开头难,有了思路后,实现思路才是真正的开始,下面将介绍如何组织cli项目json
首先看一下,工程的组织数组
├── bin | ├── zero | ├── zero-init ├── src | ├── constants.js | ├── project.js | └── utils.js ├── .gitignore ├── .npmrc ├── README.md └── package.json
在bin/zero中引入commander,而且声明init命令,commander会在同级目录中寻找zero-init文件
const program = require('commander'); program .usage('<command> [options]') .command('init [name]', 'init a project') .parse(process.argv);
有了第一步的解析后,咱们能够在zero-init文件中编写建立逻辑,这里,咱们再抽象project文件到src目录底下,以便更好地分离职责,bin文件只负责解析命令就好啦~
下面就看看project文件中,如何实现问询
inquirer.prompt([{ type: 'input', name: 'projectName', message: '请输入项目名:', validate(input) { if (!input) { return '项目名不能为空'; } if (fse.existsSync(input)) { return '当前目录已存在同名项目,请更换项目名'; } return true; } }]);
inquirer提供prompt函数来实现问询,其参数为数组,问询顺序将按照数组的顺序来进行;
在问询结束后,咱们基本知道须要建立的项目的名称、描述等信息了,下一步,就是下载仓库了,下面咱们来看看核心代码:
const downloadPath = path.join(projectPath, '__download__'); download(TEMPLATE_GIT_REPO, downloadPath, { clone: true }, (err) => { // 拷贝 // 此处省略若干代码 // 拷贝完成后删除临时文件 fse.remove(downloadPath); }
这里须要注意的是,模板工程所在的仓库,即TEMPLATE_GIT_REPO,最好是public的
下载仓库只是把模板工程存放在一个临时文件夹内,真正的工程文件须要等信息写入模板后再生成;
这里须要注意的是,模板写入时遵循ejs规范
const memFs = require('mem-fs'); const editor = require('mem-fs-editor'); // 这里须要mem-fs进行内存优化 const store = memFs.create(); this.memFsEditor = editor.create(store); // 这里source表示源文件,dest表示目标文件,data表示须要写入的数据 this.memFsEditor.copyTpl( source, dest, data );
在调用copyTpl时,data字段中的key-value将被写入到模板中,举个栗子:
// 模板文件中,字段是这样的 { name: "<%= projectName %>" } // data就要这样去写 { projectName: 'this is your project name' }
这两步的核心都是使用nodejs提供的child_process中exec方法去执行命令来实现,exec能帮助咱们在命令行中执行shell命令,exec函数有对应的回调方法来让咱们判断命令执行是否成功,核心代码以下:
exec('npm install', (error, stdout, stderr) => { if (error) { // 出错了 } else { // 成功了 } })
是否是很简单呢,想了解更多的操做能够搜索官方文档查看哈,这里就不详解了。
到这里,咱们的cli已经实现的差很少了,这里咱们总结一下如何测试和发布吧
npm link // 本地调试 npm publish // 发布
经过npm link命令,便可以在命令行工具测试你的cli了,注意在package.json的bin字段中定义好入口命令和文件
// package.json { "bin": { "zero": "bin/zero" } }
OK,文章主要总结了编写一个脚手架中init功能的思路以及一些实现的方法,具体一些细节仍有优化的空间,欢迎你们讨论!
文章的具体代码能够参考个人仓库zero-cli