vue-cli
, create-react-app
、react-native-cli
等都是很是優秀的腳手架,經過腳手架,咱們能夠快速初始化一個項目,無需本身從零開始一步步配置,有效提高開發體驗。儘管這些腳手架很是優秀,可是未必是符合咱們的實際應用的,咱們能夠定製一個屬於本身的腳手架(或公司通用腳手架),來提高本身的開發效率。javascript
更多優質文章可戳: https://github.com/YvetteLau/...前端
腳手架的做用
在開始以前,咱們須要明確本身的腳手架須要哪些功能。vue init template-name project-name
、create-react-app project-name
。咱們此次編寫的腳手架(eos-cli)具有如下能力(腳手架的名字愛叫啥叫啥,我選用了Eos黎明女神):vue
eos init template-name project-name
根據遠程模板,初始化一個項目(遠程模板可配置)eos config set <key> <value>
修改配置信息eos config get [<key>]
查看配置信息eos --version
查看當前版本號eos -h
你們能夠自行擴展其它的 commander
,本篇文章旨在教你們如何實現一個腳手架。java
效果展現
初始化一個項目node
修改.eosrc文件,從 vuejs-template 下載模板react
關於這些第三方庫的說明,能夠直接npm上查看相應的說明,此處不一一展開。git
建立一個空項目(eos-cli),使用 npm init
進行初始化。github
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
開發使用了ES6語法,使用 babel
進行轉義,vue-cli
.bablerc
{ "presets": [ [ "env", { "targets": { "node": "current" } } ] ] }
eos
命令node.js 內置了對命令行操做的支持,package.json
中的 bin
字段能夠定義命令名和關聯的執行文件。在 package.json
中添加 bin
字段shell
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();
index.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
命令。
本項目完整代碼請戳: https://github.com/YvetteLau/...
編寫本文,雖然花費了必定時間,可是在這個過程當中,我也學習到了不少知識,謝謝各位小夥伴願意花費寶貴的時間閱讀本文,若是本文給了您一點幫助或者是啓發,請不要吝嗇你的贊和Star,您的確定是我前進的最大動力。
https://github.com/YvetteLau/...
[1] npm依賴文檔(https://www.npmjs.com/package...
增長參考文章:[簡單搭建前端腳手架 ICE] (https://link.juejin.im/?targe...
推薦關注本人公衆號