【中高级前端必备】手摸手教你撸一个脚手架
0 条评论原文地址:https://github.com/YvetteLau/Blog/issues/39
脚手架
vue-cli
, create-react-app
、react-native-cli
等都是非常优秀的脚手架,通过脚手架,我们可以快速初始化一个项目,无需自己从零开始一步步配置,有效提升开发体验。尽管这些脚手架非常优秀,但是未必是符合我们的实际应用的,我们可以定制一个属于自己的脚手架(或公司通用脚手架),来提升自己的开发效率。
脚手架的作用
- 减少重复性的工作,不需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
- 可以根据交互动态生成项目结构和配置文件。
- 多人协作更为方便,不需要把文件传来传去。
实现的功能
在开始之前,我们需要明确自己的脚手架需要哪些功能。vue init template-name project-name
、create-react-app project-name
。我们这次编写的脚手架(eos-cli)具备以下能力(脚手架的名字爱叫啥叫啥,我选用了Eos黎明女神):
eos init template-name project-name
根据远程模板,初始化一个项目(远程模板可配置)eos config set <key> <value>
修改配置信息eos config get [<key>]
查看配置信息eos --version
查看当前版本号eos -h
大家可以自行扩展其它的 commander
,本篇文章旨在教大家如何实现一个脚手架。
本项目完整代码请戳(建议先clone代码): https://github.com/YvetteLau/Blog/tree/master/eos-cli
效果展示
初始化一个项目
修改.eosrc文件,从 vuejs-template 下载模板
需要使用的第三方库
- babel-cli/babel-env: 语法转换
- commander: 命令行工具
- download-git-repo: 用来下载远程模板
- ini: 格式转换
- inquirer: 交互式命令行工具
- ora: 显示loading动画
- chalk: 修改控制台输出内容样式
- log-symbols: 显示出 √ 或 × 等的图标
关于这些第三方库的说明,可以直接npm上查看相应的说明,此处不一一展开。
初始化项目
创建一个空项目(eos-cli),使用 npm init
进行初始化。
安装依赖
npm install babel-cli babel-env chalk commander download-git-repo ini inquirer log-symbols ora
目录结构
├── bin
│ └── www //可执行文件
├── dist
├── ...//生成文件
└── src
├── config.js//管理eos配置文件
├── index.js//主流程入口文件
├── init.js//init command
├── main.js//入口文件
└── utils
├── constants.js//定义常量
├── get.js//获取模板
└── rc.js//配置文件
├── .babelrc//babel配置文件
├── package.json
├── README.md
babel 配置
开发使用了ES6语法,使用 babel
进行转义,
.bablerc
{
"presets": [
[
"env",
{
"targets": {
"node": "current"
}
}
]
]
}
eos
命令
node.js 内置了对命令行操作的支持,package.json
中的 bin
字段可以定义命令名和关联的执行文件。在 package.json
中添加 bin
字段
package.json
{
"name": "eos-cli",
"version": "1.0.0",
"description": "脚手架",
"main": "index.js",
"bin": {
"eos": "./bin/www"
},
"scripts": {
"compile": "babel src -d dist",
"watch": "npm run compile -- --watch"
}
}
www 文件
行首加入一行 #!/usr/bin/env node
指定当前脚本由node.js进行解析
#! /usr/bin/env noderequire('../dist/main.js');
链接到全局环境
开发过程中为了方便调试,在当前的 eos-cli
目录下执行 npm link
,将 eos
命令链接到全局环境。
启动项目
npm run watch
处理命令行
利用 commander
来处理命令行。
main
importprogramfrom'commander';
import { VERSION } from'./utils/constants';
importapplyfrom'./index';
importchalkfrom'chalk';
/** * eos commands * - config * - init */let actionMap = {
init: {
description:'generate a new project from a template',
usages: [
'eos init templateName projectName'
]
},
config: {
alias:'cfg',
description:'config .eosrc',
usages: [
'eos config set <k> <v>',
'eos config get <k>',
'eos config remove <k>'
]
},
//other commands
}
// 添加 init / config 命令Object.keys(actionMap).forEach((action) => {
program.command(action)
.description(actionMap[action].description)
.alias(actionMap[action].alias) //别名
.action(() => {
switch (action) {
case'config':
//配置apply(action, ...process.argv.slice(3));
break;
case'init':
apply(action, ...process.argv.slice(3));
break;
default:
break;
}
});
});
functionhelp() {
console.log('\r\nUsage:');
Object.keys(actionMap).forEach((action) => {
actionMap[action].usages.forEach(usage=> {
console.log(' - '+ usage);
});
});
console.log('\r');
}
program.usage('<command> [options]');
// eos -h program.on('-h', help);
program.on('--help', help);
// eos -V VERSION 为 package.json 中的版本号program.version(VERSION, '-V --version').parse(process.argv);
// eos 不带参数时if (!process.argv.slice(2).length) {
program.outputHelp(make_green);
}
functionmake_green(txt) {
returnchalk.green(txt);
}
下载模板
download-git-repo
支持从 Github、Gitlab 下载远程仓库到本地。
get.js
import { getAll } from'./rc';
importdownloadGitfrom'download-git-repo';
exportconstdownloadLocal=async (templateName, projectName) => {
let config =awaitgetAll();
let api =`${config.registry}/${templateName}`;
returnnewPromise((resolve, reject) => {
//projectName 为下载到的本地目录downloadGit(api, projectName, (err) => {
if (err) {
reject(err);
}
resolve();
});
});
}
init
命令
命令行交互
在用户执行 init 命令后,向用户提出问题,接收用户的输入并作出相应的处理。命令行交互利用 inquirer
来实现:
inquirer.prompt([
{
name:'description',
message:'Please enter the project description: '
},
{
name:'author',
message:'Please enter the author name: '
}
]).then((answer) => {
//...
});
视觉美化
在用户输入之后,开始下载模板,这时候使用 ora
来提示用户正在下载模板,下载结束之后,也给出提示。
importorafrom'ora';
let loading =ora('downloading template ...');
loading.start();
//downloadloading.succeed(); //或 loading.fail();
init.js
import { downloadLocal } from'./utils/get';
importorafrom'ora';
importinquirerfrom'inquirer';
importfsfrom'fs';
importchalkfrom'chalk';
importsymbolfrom'log-symbols';
let init =async (templateName, projectName) => {
//项目不存在if (!fs.existsSync(projectName)) {
//命令行交互inquirer.prompt([
{
name:'description',
message:'Please enter the project description: '
},
{
name:'author',
message:'Please enter the author name: '
}
]).then(async (answer) => {
//下载模板 选择模板//通过配置文件,获取模板信息let loading =ora('downloading template ...');
loading.start();
downloadLocal(templateName, projectName).then(() => {
loading.succeed();
constfileName=`${projectName}/package.json`;
if(fs.existsSync(fileName)){
constdata=fs.readFileSync(fileName).toString();
let json =JSON.parse(data);
json.name= projectName;
json.author=answer.author;
json.description=answer.description;
//修改项目文件夹中 package.json 文件fs.writeFileSync(fileName, JSON.stringify(json, null, '\t'), 'utf-8');
console.log(symbol.success, chalk.green('Project initialization finished!'));
}
}, () => {
loading.fail();
});
});
}else {
//项目已经存在console.log(symbol.error, chalk.red('The project already exists'));
}
}
module.exports= init;
config
配置
eos config set registry vuejs-templates
config 配置,支持我们使用其它仓库的模板,例如,我们可以使用 vuejs-templates 中的仓库作为模板。这样有一个好处:更新模板无需重新发布脚手架,使用者无需重新安装,并且可以自由选择下载目标。
config.js
// 管理 .eosrc 文件 (当前用户目录下)import { get, set, getAll, remove } from'./utils/rc';
let config =async (action, key, value) => {
switch (action) {
case'get':
if (key) {
let result =awaitget(key);
console.log(result);
} else {
let obj =awaitgetAll();
Object.keys(obj).forEach(key=> {
console.log(`${key}=${obj[key]}`);
})
}
break;
case'set':
set(key, value);
break;
case'remove':
remove(key);
break;
default:
break;
}
}
module.exports= config;
rc.js
.eosrc 文件的增删改查
import { RC, DEFAULTS } from'./constants';
import { decode, encode } from'ini';
import { promisify } from'util';
importchalkfrom'chalk';
importfsfrom'fs';
constexits=promisify(fs.exists);
constreadFile=promisify(fs.readFile);
constwriteFile=promisify(fs.writeFile);
//RC 是配置文件//DEFAULTS 是默认的配置exportconstget=async (key) => {
constexit=awaitexits(RC);
let opts;
if (exit) {
opts =awaitreadFile(RC, 'utf8');
opts =decode(opts);
return opts[key];
}
return'';
}
exportconstgetAll=async () => {
constexit=awaitexits(RC);
let opts;
if (exit) {
opts =awaitreadFile(RC, 'utf8');
opts =decode(opts);
return opts;
}
return {};
}
exportconstset=async (key, value) => {
constexit=awaitexits(RC);
let opts;
if (exit) {
opts =awaitreadFile(RC, 'utf8');
opts =decode(opts);
if(!key) {
console.log(chalk.red(chalk.bold('Error:')), chalk.red('key is required'));
return;
}
if(!value) {
console.log(chalk.red(chalk.bold('Error:')), chalk.red('value is required'));
return;
}
Object.assign(opts, { [key]: value });
} else {
opts =Object.assign(DEFAULTS, { [key]: value });
}
awaitwriteFile(RC, encode(opts), 'utf8');
}
exportconstremove=async (key) => {
constexit=awaitexits(RC);
let opts;
if (exit) {
opts =awaitreadFile(RC, 'utf8');
opts =decode(opts);
delete opts[key];
awaitwriteFile(RC, encode(opts), 'utf8');
}
}
发布
npm publish
将本脚手架发布至npm上。其它用户可以通过 npm install eos-cli -g
全局安装。
即可使用 eos
命令。
项目地址
本项目完整代码请戳: https://github.com/YvetteLau/Blog/tree/master/eos-cli
编写本文,虽然花费了一定时间,但是在这个过程中,我也学习到了很多知识,谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的最大动力。
https://github.com/YvetteLau/Blog
参考文章:
[1] 珠峰架构课(墙裂推荐)
[2] npm依赖文档(https://www.npmjs.com/package/download-git-repo)
- 本文链接:https://xuehuayu.cn/article/10624.html
- 版权声明:① 标为原创的文章为博主原创,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接。② 标为转载的文章来自网络,已标明出处,侵删。