原文地址: github.com/jiangtao/bl…,轉載請註明出處。css
在16年年末的時候,同事聊起腳手架。因爲公司業務的多樣性
,前端的靈活性
,讓咱們不得不思考更通用的腳手架。而不是伴隨着前端技術的發展,不斷的把時間花在配置
上。因而chef-cli誕生了。 18年年初,把過往一年的東西整理和總結下,從新加強了原有的腳手架project-next-cli, 不僅僅知足咱們團隊的需求,也能夠知足其餘人的需求。前端
面向的目標用戶:vue
從本人作前端開始(13年),前端這幾年處於高速發展,主要表現:react
備註:如下發展過程出現,請不要糾結出現順序 [捂臉]webpack
當咱們真實開發中,會遇到各類各樣的業務需求(場景),根據需求和場景選用不一樣的技術棧,因爲技術的進步和不一樣瀏覽器運行時的限制,不得不配置對應的環境等,致使咱們從而知足業務需求。git
畫了一張圖來表示,業務,配置(環境),技術之間的關係github
因而明見流傳了一個新的職業,前端配置工程師 O(∩_∩)O~web
社區中存在着大量的專注型框架,主要針對一個目標任務作定製。好比下列腳手架vue-cli
vue-cli
提供利用vue開發webpack
, pwa
等模板,本文腳手架參考了vue-cli
的實現。typescript
dva-cli
主要針對dva開發使用的腳手架
labrador
是一種微信小程序
組件化開發框架, 雖然說小程序目前已經支持組件,但該腳手架的其餘特性,也很贊。感興趣的能夠了解。
社區中有不少優秀的專注型腳手架出現,這裏不在列舉。前端社區的火爆,讓我輩前端汲取精華,不斷前進。
yeoman
是一款強壯的且有一系列工具的通用型腳手架,但yeoman發佈指定package名稱,和用其開發工具。具體可點擊這裏查看yeoman添加生成器規則
因爲金融公司形態決定了,業務類型多樣,前端技術發展迭代,爲了跟進社區發展,更好的完成下列目標而誕生。
依託於Github,根據Github API
來實現,以下:
curl -i https://api.github.com/orgs/project-scaffold/repos
複製代碼
curl -i https://api.github.com/repos/project-scaffold/cli/tags
複製代碼
根據github api
獲取到項目列表和版本號以後,就能夠作一個通用的腳手架. 如下代碼是核心代碼,便於理解。
>=6.0.0
遵照單一職責原則
,每一個文件爲一個單獨模塊,解決獨立的問題。能夠自由組合,從而實現複用。如下是最終的目錄結構:
├── LICENSE
├── README.md
├── bin
│ └── project
├── package.json
├── src
│ ├── clear.js
│ ├── config.js
│ ├── helper
│ │ ├── metalAsk.js
│ │ ├── metalsimth.js
│ │ └── render.js
│ ├── index.js
│ ├── init.js
│ ├── install.js
│ ├── list.js
│ ├── project.js
│ ├── search.js
│ ├── uninstall.js
│ ├── update.js
│ └── utils
│ ├── betterRequire.js
│ ├── check.js
│ ├── copy.js
│ ├── defs.js
│ ├── git.js
│ ├── loading.js
│ └── rc.js
└── yarn.lock
複製代碼
下面咱們說一下每一個命令的實現邏輯。
project i
複製代碼
Github API ===> 獲取項目列表 ===> 選擇一個項目 ===> 獲取項目版本號 ===> 選擇一個版本號 ===> 下載到本地倉庫
複製代碼
若中間每一步 數據爲空/文件不存在 則給予提示
// 獲取github項目列表
const repos = await repoList();
choices = repos.map(({ name }) => name);
answers = await inquirer.prompt([
{
type : 'list',
name : 'repo',
message: 'which repo do you want to install?',
choices
}
]);
// 選擇的項目
const repo = answers.repo;
// 項目的版本號劣幣愛哦
const tags = await tagList(repo);
if (tags.length === 0) {
version = '';
} else {
choices = tags.map(({ name }) => name);
answers = await inquirer.prompt([
{
type : 'list',
name : 'version',
message: 'which version do you want to install?',
choices
}
]);
version = answers.version;
}
// 下載
await download([repo, version].join('@'));
複製代碼
project init
複製代碼
獲取本地倉庫列表 ===> 選擇一個本地項目 ===> 輸入基本信息 ===> 編譯生成到臨時文件 ===> 複製並重名到目標目錄
複製代碼
若中間每一步 數據爲空/文件不存在/生成目錄已重複 則給予提示
// 獲取本地倉庫項目
const list = await readdir(dirs.download);
// 基本信息
const answers = await inquirer.prompt([
{
type : 'list',
name : 'scaffold',
message: 'which scaffold do you want to init?',
choices: list
}, {
type : 'input',
name : 'dir',
message: 'project name',
// 必要的驗證
async validate(input) {
const done = this.async();
if (input.length === 0) {
done('You must input project name');
return;
}
const dir = resolve(process.cwd(), input);
if (await exists(dir)) {
done('The project name is already existed. Please change another name');
}
done(null, true);
}
}
]);
const metalsmith = await rc('metalsmith');
if (metalsmith) {
const tmp = `${dirs.tmp}/${answers.scaffold}`;
// 複製一份到臨時目錄,在臨時目錄編譯生成
await copy(`${dirs.download}/${answers.scaffold}`, tmp);
await metal(answers.scaffold);
await copy(`${tmp}/${dirs.metalsmith}`, answers.dir);
// 刪除臨時目錄
await rmfr(tmp);
} else {
await copy(`${dirs.download}/${answers.scaffold}`, answers.dir);
}
複製代碼
其中模板引擎編譯實現核心代碼以下:
// metalsmith邏輯
function metal(answers, tmpBuildDir) {
return new Promise((resolve, reject) => {
metalsmith
.metadata(answers)
.source('./')
.destination(tmpBuildDir)
.clean(false)
.use(render())
.build((err) => {
if (err) {
reject(err);
return;
}
resolve(true);
});
});
}
// metalsmith render中間件實現
function render() {
return function _render(files, metalsmith, next) {
const meta = metalsmith.metadata();
/* eslint-disable */
Object.keys(files).forEach(function(file){
const str = files[file].contents.toString();
consolidate.swig.render(str, meta, (err, res) => {
if (err) {
return next(err);
}
files[file].contents = new Buffer(res);
next();
});
})
}
}
複製代碼
project update
複製代碼
獲取本地倉庫列表 ===> 選擇一個本地項目 ===> 獲取版本信息列表 ===> 選擇一個版本 ===> 覆蓋原有的版本文件
複製代碼
若中間每一步 數據爲空/文件不存在 則給予提示
// 獲取本地倉庫列表
const list = await readdir(dirs.download);
// 選擇一個要升級的項目
answers = await inquirer.prompt([
{
type : 'list',
name : 'scaffold',
message: 'which scaffold do you want to update?',
choices: list,
async validate(input) {
const done = this.async();
if (input.length === 0) {
done('You must choice one scaffold to update the version. If not update, Ctrl+C');
return;
}
done(null, true);
}
}
]);
const repo = answers.scaffold;
// 獲取該項目的版本信息
const tags = await tagList(repo);
if (tags.length === 0) {
version = '';
} else {
choices = tags.map(({ name }) => name);
answers = await inquirer.prompt([
{
type : 'list',
name : 'version',
message: 'which version do you want to install?',
choices
}
]);
version = answers.version;
}
// 下載覆蓋文件
await download([repo, version].join('@'))
複製代碼
配置用來獲取腳手架的基本設置, 如registry, type等基本信息。
project config set registry koajs # 設置本地倉庫下載源
project config get registry # 獲取本地倉庫設置的屬性
project config delete registry # 刪除本地設置的屬性
複製代碼
斷定本地設置文件存在 ===> 讀/寫
複製代碼
若中間每一步 數據爲空/文件不存在 則給予提示
switch (action) {
case 'get':
console.log(await rc(k));
console.log('');
return true;
case 'set':
await rc(k, v);
return true;
case 'remove':
await rc(k, v, true);
return true;
default:
console.log(await rc());
複製代碼
搜索遠程的github倉庫有哪些項目列表
project search
複製代碼
獲取github項目列表 ===> 輸入搜索的內容 ===> 返回匹配的列表
複製代碼
若中間每一步 數據爲空 則給予提示
const answers = await inquirer.prompt([
{
type : 'input',
name : 'search',
message: 'search repo'
}
]);
if (answers.search) {
let list = await searchList();
list = list
.filter(item => item.name.indexOf(answers.search) > -1)
.map(({ name }) => name);
console.log('');
if (list.length === 0) {
console.log(`${answers.search} is not found`);
}
console.log(list.join('\n'));
console.log('');
}
複製代碼
以上是這款通用腳手架產生的背景,針對用戶以及具體實現,該腳手架目前還有一些能夠優化的地方:
硬廣:若是您以爲project-next-cli好用,歡迎star,也歡迎fork一塊維護。