搭一个属于自己的脚手架
1.什么是脚手架
脚手架用于快速生成新项目的目录模板,并集成一系列体系化工具的安装,无需自己从零开始一步步配置,减少copy操作,有效提升开发体验和效率,尽管这些脚手架非常优秀,但是未必是符合我们的实际应用的,所以我们可以定制一个属于自己的脚手架,来提升自己的开发效率。平时我们在开发React-Native的时候就会使用到raect-native-cli这个脚手架为我们创建项目
脚手架的作用
- 减少重复性的工作,不需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
- 可以根据交互动态生成项目结构和配置文件。
- 多人协作更为方便,不需要把文件传来传去。
目前比较主流的脚手架
- React-Native脚手架 react-native-cli
- React.js脚手架 cract-react-app
- Vue.js脚手架 vue-cli
- Webpack脚手架 webpack-cli
2.实现思路
- 项目模板放在github上
- 用户通过命令交互的方式下载不同的模版
- 经过模版引擎渲染定制项目模版
- 模版变动,只需更新模版即可,不需要用户更新脚手架
设计模块知识点
- commander.js命令行工具
- download-git-repo: 用来下载远程模板
- inquirer: 交互式命令行工具
- ora: 显示loading动画
- chalk: 修改控制台输出内容样式
- log-symbols: 显示出 √ 或 × 等的图标
3.项目初始化
这里假设我们的脚手架名字是qiyitest-cli,以下都用这个名字。我们在命令行使用脚手架命令为qiyitest-cli
1.创建一个空项目qiyitest-cli
mkdir qiyitest-cli cd qiyitest-cli npm init -y
2.安装相关的依赖
npm install babel-cli babel-env chalk commander download-git-repo ini inquirer log-symbols ora
3. 新建一个bin文件夹里面添加 index.js
行首加入一行 #!/usr/bin/env node 指定当前脚本由node.js进行解析
#! /usr/bin/env node console.log('hello demo')
4.配置package.js中的bin字段 (node.js 内置了对命令行操作的支持,package.json
中的 bin
字段可以定义命令名和关联的执行文件)
{ "name": "qiyitest-cli", "version": "1.0.0", "description": "", "main": "index.js", "bin": { "qiyitest": "bin/index.js" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "babel-cli": "^6.26.0", "babel-env": "^2.4.1", "chalk": "^4.0.0", "commander": "^5.1.0", "download-git-repo": "^3.0.2", "ini": "^1.3.5", "inquirer": "^7.1.0", "log-symbols": "^3.0.0", "ora": "^4.0.4" } }
这样当我们发布上npm,别人下载下来后,就可以直接使用qiyitest命令了。
5.目录结构
├── bin │ └── index.js //可执行文件 ├── .babelrc //babel配置文件 ├── package.json ├── README.md
6.执行npm link链接命令到全局(npm unlin移除命令)
执行bin中配置的命令测试。
例如在终端输入:
npm link qiyitest
输出
hello demo
4.处理命令行
使用commander处理控制台命令(github查看如何使用), commander提供解析命令行
在index.js文件下面添加
#! /usr/bin/env node // 使用Node开发命令行工具所执行JavaScript脚本必须在顶部加入 #! /usr/bin/env node const { program } = require('commander'); program.version('1.0.0') // -v 或者 --versions输出版本号 program .command('init <template> <project>') .description('初始化项目模版') .action((templateName, projectName) => { console.log(templateName, projectName) }) program .command('list') .description('查看所有可用的模版') .action(() => { console.log( `template-A A模板 template-B B模板 template-C C模板` ) }) program.parse(process.argv);
在控制台输入
qiyitest -V(或者qiyitest --version)
qiyitest -h(或者 qiyitest --help)
qiyitest init template-A A
qiyitest list
输出结果如下:
5. 准备模版
在github上面建立需要使用到的模版,这里分别建立了template-A template-B template-C三个模版(https://github.com/dongtaotao/-template-C)
添加下载模版
当输入qiyitest init template-A a-name时候下载基于template-A模版进行初始化
当输入qiyitest init template-B b-name时候下载基于template-B模版进行初始化
当输入qiyitest init template-C c-name时候下载基于template-A模版进行初始化
注:项目名可以随便取
模版下载地址
const templates = { 'template-A' : { url: 'https://github.com/dongtaotao/template-A', downloadUrl: 'http://github.com:dongtaotao/template-A#master', description: 'A模版' }, 'template-B' : { url: 'https://github.com/dongtaotao/template-B', downloadUrl: 'http://github.com:dongtaotao/template-B#master', description: 'B模版' }, 'template-C' : { url: 'https://github.com/dongtaotao/template-C', downloadUrl: 'http://github.com:dongtaotao/template-C#master', description: 'C模版' }, };
6 根据init指定的模版名和项目名下载生成到本地
download-git-repo 支持从 Github、Gitlab 下载远程仓库到本地。
const download = require('download-git-repo'); program .command('init <template> <project>') .description('初始化项目模版') .action((templateName, projectName) => { const {downloadUrl} = templates[templateName]; //download // 第一个参数: 仓库地址 // 第二个参数: 下载路径 download(downloadUrl, projectName, {clone: true}, (err) => { if(err) { console.log('下载失败') } else { console.log('下载成功') } }) })
在控制台输入qiyitest init template-A ademo
这时候就会下载对应的template-A模版,同时在桌面上多了一个ademo的文件。
文件ademo里面的内容就是template-A模版的内容
download函数
- 第一个参数就是仓库地址
- 端口号后面的'/'在参数中要改成':'
- master 代表分之名
不同的模版可以放在不同的分枝中,更改分之便可以实现下载不同的模版文件了
第二个参数是路径
- 上面我们直接在当前路径下创建一个ademo的文件存放模版。
7 命令行交互
命令行交互功能可以在用户执行init命令之后,向用户提出问题,接收用户输入并作出相应的处理,这里使用inquirer.js来实现
安装:
npm install inquirer
const inquirer = require('inquirer'); inquirer.prompt([ { type: 'inpute', name: 'name', message: '请输入项目名称' }]).then((answers) => { console.log(answers) })
- 问题就放在prompt()中
- 问题的类型为input就是输入类型
- name就是作为答案对象中的key
- message就是问题了
- 用户输入的答案就在answers中
8.模板引擎
- 我们通过询问交互后,肯定内部做了些改变。这里我们可以把package.json的作者和描述简单改了
- 可以使用handlebars,模板语法简单
在模版中准备package.json文件
{ "name": "{{name}}", "version": "1.0.0", "description": "{{description}}", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "{{author}}", "license": "ISC", "dependencies": { } }
并在下载模版完成只有将用户输入的答案渲染到package.json中
const { program } = require('commander'); const download = require('download-git-repo'); const handlebars = require('handlebars'); const inquirer = require('inquirer'); const ora = require('ora'); const logSymbols = require('log-symbols'); const chalk = require('chalk'); const fs = require('fs'); const templates = { 'template-A' : { url: 'https://github.com/dongtaotao/template-A', downloadUrl: 'http://github.com:dongtaotao/template-A#master', description: 'A模版' }, 'template-B' : { url: 'https://github.com/dongtaotao/template-B', downloadUrl: 'http://github.com:dongtaotao/template-B#master', description: 'B模版' }, 'template-C' : { url: 'https://github.com/dongtaotao/template-C', downloadUrl: 'http://github.com:dongtaotao/template-C#master', description: 'C模版' }, }; //qiyitest init template-A a-name基于template-A模版进行初始化 //qiyitest init template-B a-name基于template-A模版进行初始化 program.version('1.0.0') // -v 或者 --versions输出版本号 program .command('init <template> <project>') .description('初始化项目模版') .action((templateName, projectName) => { // 下载之前做loading提示 const spinner = ora('正在下载模版...').start() const {downloadUrl} = templates[templateName]; //download // 第一个参数: 仓库地址 // 第二个参数: 下载路径 download(downloadUrl, projectName, {clone: true}, (err) => { if(err) { spinner.fail(); console.log(logSymbols.error, chalk.red(err)) return; } spinner.succeed(); // 下载成功提示 // 把项目下的package.json文件读取出来 // 使用向导的方式采集用户输入的数据解析导 // 使用模板引擎把用户输入的数据解析到package.json 文件中 // 解析完毕,把解析之后的结果重新写入package.json wenjianzhong inquirer.prompt([ { type: 'inpute', name: 'name', message: '请输入项目名称' }, { type: 'inpute', name: 'description', message: '请输入项目简介' }, { type: 'inpute', name: 'author', message: '请输入作者名称' } ]).then((answers) => { const packagePath = `${projectName}/package.json` const packageContent = fs.readFileSync(packagePath, 'utf8') const packageResult = handlebars.compile(packageContent)(answers); fs.writeFileSync(packagePath, packageResult) console.log(chalk.yellow('初始化模版成功')) }) }) }) program.parse(process.argv);
这里使用了node.js的文件模块fs, 将handlebars渲染后的模版重新写入到文件中
这时候打开demob 文件夹下面的package.json,可以看到里面的name,description,author变成我们输入的
9.视觉美化
在用户输入答案之后,开始下载模版,这时候使用ora来提示用户正在下载中。
安装:
npm install ora
使用
const ora = require('ora'); // 开始下载 const spinner = ora('正在下载模版...'); spinner.start(); // 下载失败 spinner.fail(); // 下载成功 spinner.succeed();
也可以使用chalk和logSymbols增加文本样式
chalk
这是用来修改控制台输出内容样式的,比如颜色啊,具体用法如下:
const chalk = require('chalk'); console.log(chalk.green('success')); console.log(chalk.red('error'));
然后通过chalk来为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子让用户更容易分辨,同事也让终端的显示更加好看。
这时候在控制台输入命令可以看出带了图标和颜色
10.npm 发包
- 打开npmjs.com官网
- 注册一个npm账号
- 在npm检索是否有重名的包
- 将package.json中的name修改发不到npm上的包名
- 打开控制台,执行npm login,在控制台登录npm
- 登录成功后,在项目下执行npm publish发布
- 发布成功,就可以在本地安装测试了
npm install -g qiyitest-cli
这时候就可以使用qiyitest命令了,至此一个简单的脚手架搭建过程结束了