vue-cli
, create-react-app
、react-native-cli
等都是很是優秀的腳手架,經過腳手架,咱們能夠快速初始化一個項目,無需本身從零開始一步步配置,有效提高開發體驗。儘管這些腳手架很是優秀,可是未必是符合咱們的實際應用的,咱們能夠定製一個屬於本身的腳手架(或公司通用腳手架),來提高本身的開發效率。javascript
腳手架的做用前端
本項目完整代碼請戳: github.com/YvetteLau/B…vue
在開始以前,咱們須要明確本身的腳手架須要哪些功能。vue init template-name project-name
、create-react-app project-name
。咱們此次編寫的腳手架(eos-cli)具有如下能力(腳手架的名字愛叫啥叫啥,我選用了Eos黎明女神):java
eos init template-name project-name
根據遠程模板,初始化一個項目(遠程模板可配置)eos config set <key> <value>
修改配置信息eos config get [<key>]
查看配置信息eos --version
查看當前版本號eos -h
你們能夠自行擴展其它的 commander
,本篇文章旨在教你們如何實現一個腳手架。node
本項目完整代碼請戳(建議先clone代碼): github.com/YvetteLau/B…react
效果展現git
初始化一個項目github
修改.eosrc文件,從 vuejs-template 下載模板vue-cli
關於這些第三方庫的說明,能夠直接npm上查看相應的說明,此處不一一展開。shell
建立一個空項目(eos-cli),使用 npm init
進行初始化。
npm install @babel/cli @babel/core @babel/preset-env chalk commander download-git-repo ini inquirer log-symbols ora -D
複製代碼
├── 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
複製代碼
開發使用了ES6語法,使用 babel
進行轉義,
.bablerc
{
"presets": [
[
"@babel/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 node
require('../dist/main.js');
複製代碼
開發過程當中爲了方便調試,在當前的 eos-cli
目錄下執行 npm link
,將 eos
命令連接到全局環境。
npm run watch
複製代碼
利用 commander
來處理命令行。
main
import program from 'commander';
import { VERSION } from './utils/constants';
import apply from './index';
import chalk from '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;
}
});
});
function help() {
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);
}
function make_green(txt) {
return chalk.green(txt);
}
複製代碼
download-git-repo
支持從 Github、Gitlab 下載遠程倉庫到本地。
get.js
import { getAll } from './rc';
import downloadGit from 'download-git-repo';
export const downloadLocal = async (templateName, projectName) => {
let config = await getAll();
let api = `${config.registry}/${templateName}`;
return new Promise((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
來提示用戶正在下載模板,下載結束以後,也給出提示。
import ora from 'ora';
let loading = ora('downloading template ...');
loading.start();
//download
loading.succeed(); //或 loading.fail();
複製代碼
init.js
import { downloadLocal } from './utils/get';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs';
import chalk from 'chalk';
import symbol from '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();
const fileName = `${projectName}/package.json`;
if(fs.existsSync(fileName)){
const data = 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 = await get(key);
console.log(result);
} else {
let obj = await getAll();
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';
import chalk from 'chalk';
import fs from 'fs';
const exits = promisify(fs.exists);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
//RC 是配置文件
//DEFAULTS 是默認的配置
export const get = async (key) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
return opts[key];
}
return '';
}
export const getAll = async () => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
return opts;
}
return {};
}
export const set = async (key, value) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(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 });
}
await writeFile(RC, encode(opts), 'utf8');
}
export const remove = async (key) => {
const exit = await exits(RC);
let opts;
if (exit) {
opts = await readFile(RC, 'utf8');
opts = decode(opts);
delete opts[key];
await writeFile(RC, encode(opts), 'utf8');
}
}
複製代碼
npm publish
將本腳手架發佈至npm上。其它用戶能夠經過 npm install eos-cli -g
全局安裝。 便可使用 eos
命令。
本項目完整代碼請戳: github.com/YvetteLau/B…
編寫本文,雖然花費了必定時間,可是在這個過程當中,我也學習到了不少知識,謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。 github.com/YvetteLau/B…
[1] npm依賴文檔(www.npmjs.com/package/dow…)
增長參考文章:[簡單搭建前端腳手架 ICE] (link.juejin.im/?target=htt…)